Version 1.6.61.
Cherry pick: Allow the local variable table to contain unobservable info.
CL: https://r8-review.googlesource.com/c/r8/+/47780
Cherry pick: Make sure to have an IR instruction materialized for local ends.
CL: https://r8-review.googlesource.com/c/r8/+/47789
BUG=147865212
R=zerny@google.com
Change-Id: I7d8e0a2ad4ba8456641d4d6650bf5eb9e41f198e
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 467fc64..034b2f8 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.6.60";
+ public static final String LABEL = "1.6.61";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 75f6be9..39eb34e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -30,7 +30,7 @@
@Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
- // Intentionally left empty.
+ builder.addNop();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 995a583..883fb9f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -974,6 +974,12 @@
if (currentBlock.getInstructions().isEmpty()) {
addInstruction(new DebugLocalRead());
} else {
+ // We do not want to add the out value of an instructions as a debug value for
+ // the same instruction. Debug values are there to keep values alive until that
+ // instruction. Therefore, they make no sense on the instruction that defines
+ // the value. There should always be a DebugLocalRead in the IR for situations
+ // where an introduced local's scope ends immediately.
+ assert !debugLocalEnds.contains(currentBlock.getInstructions().getLast().outValue());
attachLocalValues(currentBlock.getInstructions().getLast());
}
}
@@ -1204,6 +1210,11 @@
addInstruction(new DebugLocalWrite(out, in));
return;
}
+ // If this move ends locals, add a DebugLocalRead to make sure the end point
+ // is registered in the right place.
+ if (!debugLocalEnds.isEmpty()) {
+ addInstruction(new DebugLocalRead());
+ }
}
currentBlock.writeCurrentDefinition(dest, in, ThrowingInfo.NO_THROW);
}
@@ -1227,6 +1238,15 @@
addInstruction(instruction);
}
+ public void addNop() {
+ // If locals end on a nop, insert a debug local read as the ending point.
+ // This avoids situations where the end of the local could be the instruction
+ // that introduced it when the local only spans a nop in the input.
+ if (!debugLocalEnds.isEmpty()) {
+ addInstruction(new DebugLocalRead());
+ }
+ }
+
public void addRem(NumericType type, int dest, int left, int right) {
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(left, type);
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java b/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java
new file mode 100644
index 0000000..aa2ac6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java
@@ -0,0 +1,203 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b147865212;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generate a class file with the following code, line number table,
+// and local variable table.
+//
+// public static final java.lang.String box(int);
+// descriptor: (I)Ljava/lang/String;
+// flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+// Code:
+// stack=1, locals=3, args_size=1
+// 0: nop
+// 1: ldc #9 // String OK
+// 3: astore_1
+// 4: nop
+// 5: iload_0
+// 6: istore_2
+// 7: nop
+// 8: nop
+// 9: iload_0
+// 10: istore_2
+// 11: nop
+// 12: aload_1
+// 13: areturn
+// 14: astore_1
+// 15: nop
+// 16: iload_0
+// 17: istore_2
+// 18: nop
+// 19: nop
+// 20: iload_0
+// 21: istore_2
+// 22: nop
+// 23: aload_1
+// 24: athrow
+// Exception table:
+// from to target type
+// 0 4 14 any
+// 14 15 14 any
+// StackMapTable: number_of_entries = 1
+// frame_type = 78 /* same_locals_1_stack_item */
+// stack = [ class java/lang/Throwable ]
+// LineNumberTable:
+// line 2: 0
+// line 3: 1
+// line 5: 4
+// line 6: 5
+// line 8: 8
+// line 9: 9
+// line 10: 11
+// line 3: 13
+// line 12: 14
+// line 5: 15
+// line 6: 16
+// line 8: 19
+// line 9: 20
+// line 10: 22
+// LocalVariableTable:
+// Start Length Slot Name Signature
+// 7 1 2 z I
+// 11 1 2 z I
+// 18 1 2 z I
+// 22 1 2 z I
+// 0 25 0 x I
+public class Flaf2Dump implements Opcodes {
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+ AnnotationVisitor annotationVisitor0;
+
+ classWriter.visit(
+ V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Flaf.kt", null);
+
+ {
+ annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+ annotationVisitor0.visit("mv", new int[] {1, 1, 17});
+ annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+ annotationVisitor0.visit("k", new Integer(2));
+ {
+ AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+ annotationVisitor1.visit(
+ null,
+ "\u0000\u0006\n\u0000\n\u0002\u0010\u000e\u001a\u0006\u0010\u0000\u001a\u00020\u0001");
+ annotationVisitor1.visitEnd();
+ }
+ {
+ AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+ annotationVisitor1.visit(null, "box");
+ annotationVisitor1.visit(null, "");
+ annotationVisitor1.visitEnd();
+ }
+ annotationVisitor0.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "box", "()Ljava/lang/String;", null, null);
+ {
+ annotationVisitor0 =
+ methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false);
+ annotationVisitor0.visitEnd();
+ }
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+ Label label3 = new Label();
+ methodVisitor.visitTryCatchBlock(label2, label3, label2, null);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitInsn(NOP);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(3, label4);
+ methodVisitor.visitLdcInsn("OK");
+ methodVisitor.visitVarInsn(ASTORE, 0);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(5, label1);
+ methodVisitor.visitInsn(NOP);
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(6, label5);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitInsn(NOP);
+ Label label7 = new Label();
+ methodVisitor.visitLabel(label7);
+ methodVisitor.visitLineNumber(8, label7);
+ methodVisitor.visitInsn(NOP);
+ Label label8 = new Label();
+ methodVisitor.visitLabel(label8);
+ methodVisitor.visitLineNumber(9, label8);
+ methodVisitor.visitInsn(ICONST_4);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label9 = new Label();
+ methodVisitor.visitLabel(label9);
+ methodVisitor.visitInsn(NOP);
+ Label label10 = new Label();
+ methodVisitor.visitLabel(label10);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ Label label11 = new Label();
+ methodVisitor.visitLabel(label11);
+ methodVisitor.visitLineNumber(3, label11);
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(11, label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+ methodVisitor.visitVarInsn(ASTORE, 0);
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(5, label3);
+ methodVisitor.visitInsn(NOP);
+ Label label12 = new Label();
+ methodVisitor.visitLabel(label12);
+ methodVisitor.visitLineNumber(6, label12);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label13 = new Label();
+ methodVisitor.visitLabel(label13);
+ methodVisitor.visitInsn(NOP);
+ Label label14 = new Label();
+ methodVisitor.visitLabel(label14);
+ methodVisitor.visitLineNumber(8, label14);
+ methodVisitor.visitInsn(NOP);
+ Label label15 = new Label();
+ methodVisitor.visitLabel(label15);
+ methodVisitor.visitLineNumber(9, label15);
+ methodVisitor.visitInsn(ICONST_4);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label16 = new Label();
+ methodVisitor.visitLabel(label16);
+ methodVisitor.visitInsn(NOP);
+ Label label17 = new Label();
+ methodVisitor.visitLabel(label17);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLocalVariable("z", "I", null, label6, label7, 1);
+ methodVisitor.visitLocalVariable("z", "I", null, label9, label10, 1);
+ methodVisitor.visitLocalVariable("z", "I", null, label13, label14, 1);
+ methodVisitor.visitLocalVariable("z", "I", null, label16, label17, 1);
+ methodVisitor.visitMaxs(1, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java b/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java
new file mode 100644
index 0000000..d0a4f6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b147865212;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generate a class file containing the following method, line number table,
+// and local variable table. Conditionally add an extra entry to the line
+// number table for the nop at instruction 5. When generateLineNumberForLocal
+// is true, a `line 7: 5` entry is generated in the line number table
+// making the local observable in the debugger.
+//
+// public static final java.lang.String box();
+// descriptor: ()Ljava/lang/String;
+// flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+// Code:
+// stack=1, locals=1, args_size=0
+// 0: nop
+// 1: ldc #11 // String A
+// 3: areturn
+// 4: astore_0
+// 5: nop
+// 6: ldc #13 // String B
+// 8: areturn
+// Exception table:
+// from to target type
+// 0 4 4 Class java/lang/IllegalStateException
+// StackMapTable: number_of_entries = 1
+// frame_type = 68 /* same_locals_1_stack_item */
+// stack = [ class java/lang/IllegalStateException ]
+// LineNumberTable:
+// line 2: 0
+// line 3: 1
+// line 4: 4
+// line 5: 6
+// line 6: 6
+// LocalVariableTable:
+// Start Length Slot Name Signature
+// 5 1 0 e Ljava/lang/IllegalStateException;
+public class FlafDump implements Opcodes {
+ public static byte[] dump(boolean generateLineNumberForLocal) throws Exception {
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+ AnnotationVisitor annotationVisitor0;
+
+ classWriter.visit(
+ V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Flaf.kt", null);
+
+ {
+ annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+ annotationVisitor0.visit("mv", new int[] {1, 1, 16});
+ annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+ annotationVisitor0.visit("k", new Integer(2));
+ {
+ AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+ annotationVisitor1.visit(
+ null,
+ "\u0000\u0006\n\u0000\n\u0002\u0010\u000e\u001a\u0006\u0010\u0000\u001a\u00020\u0001");
+ annotationVisitor1.visitEnd();
+ }
+ {
+ AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+ annotationVisitor1.visit(null, "box");
+ annotationVisitor1.visit(null, "");
+ annotationVisitor1.visitEnd();
+ }
+ annotationVisitor0.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "box", "()Ljava/lang/String;", null, null);
+ {
+ annotationVisitor0 =
+ methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false);
+ annotationVisitor0.visitEnd();
+ }
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label1, "java/lang/IllegalStateException");
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitInsn(NOP);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(3, label2);
+ methodVisitor.visitLdcInsn("A");
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitFrame(
+ Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IllegalStateException"});
+ methodVisitor.visitVarInsn(ASTORE, 0);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ if (generateLineNumberForLocal) {
+ methodVisitor.visitLineNumber(7, label3);
+ }
+ methodVisitor.visitInsn(NOP);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(5, label4);
+ methodVisitor.visitLineNumber(6, label4);
+ methodVisitor.visitLdcInsn("B");
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitLocalVariable(
+ "e", "Ljava/lang/IllegalStateException;", null, label3, label4, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
new file mode 100644
index 0000000..d7133e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b147865212;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class Regress147865212 extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public Regress147865212(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean hasLocal(MethodSubject method) {
+ return Arrays.stream(method.getMethod().getCode().asDexCode().getDebugInfo().events)
+ .anyMatch(event -> event instanceof StartLocal);
+ }
+
+ private MethodSubject build(byte[] classFile) throws Exception {
+ CodeInspector inspector =
+ testForD8()
+ .addProgramClassFileData(classFile)
+ .setMinApi(parameters.getApiLevel())
+ .debug()
+ .compile()
+ .inspector();
+ ClassSubject clazz = inspector.clazz("FlafKt");
+ assertTrue(clazz.isPresent());
+ MethodSubject method = clazz.uniqueMethodWithName("box");
+ assertTrue(method.isPresent());
+ return method;
+ }
+
+ @Test
+ public void testFlafWithLineNumberForLocal() throws Exception {
+ // Generate the FlafKt class file adding a line number for the single nop instruction
+ // that a local spans. That will make the local observable in the debugger as there
+ // is now a breakpoint on the nop. Therefore, the local stays in the output.
+ MethodSubject method = build(FlafDump.dump(true));
+ assertTrue(hasLocal(method));
+ }
+
+ @Test
+ public void testFlafWithoutLineNumberForLocal() throws Exception {
+ // Generate the FlafKt class file where all locals are live only on a nop instruction
+ // with no line number. Therefore, the locals are not observable in the debugger and
+ // are removed.
+ MethodSubject method = build(FlafDump.dump(false));
+ assertFalse(hasLocal(method));
+ }
+
+ @Test
+ public void testFlaf2() throws Exception {
+ MethodSubject method = build(Flaf2Dump.dump());
+ assertFalse(hasLocal(method));
+ }
+}