Prevent reordered instructions w/o positions getting random positions.
Moving instructions without positions (e.g. preamble) past
instructions with actual position would make them inherit those
positions. In this change we assign line number = 0 to those
instructions to prevent this.
Bug: 73150368
Change-Id: I564190417a8e11eb04d5bb354de7430fcc035237
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 69fb348..bfc0a9f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -195,11 +195,13 @@
private void emitDebugPosition(int pc, Position position) {
assert !position.equals(emittedPosition);
if (startLine == NO_LINE_INFO) {
- if (position.synthetic) {
+ assert emittedPosition.isNone();
+ if (position.synthetic && position.callerPosition == null) {
// Ignore synthetic positions prior to any actual position.
+ // We do need to preserve synthetic position establishing the stack frame for inlined
+ // methods.
return;
}
- assert emittedPosition.isNone();
startLine = position.line;
emittedPosition = new Position(position.line, null, method.method, null);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index cde22b0..8d522de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -31,6 +31,7 @@
@Override
public void buildDex(DexBuilder builder) {
+ assert getPosition().isSome() && !getPosition().synthetic;
builder.addDebugPosition(this);
}
@@ -73,6 +74,7 @@
@Override
public void buildCf(CfBuilder builder) {
+ assert getPosition().isSome() && !getPosition().synthetic;
// All redundant debug positions are removed. Remaining ones must force a pc advance.
builder.add(new CfNop());
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 96b2122..3ad0168 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -984,14 +984,7 @@
int source = builder.getInfo(jump).getOffset();
Info targetInfo = builder.getTargetInfo(jump.getTarget());
int relativeOffset = targetInfo.getOffset() - source;
- // Emit a return if the target is a return and the size of the return is the computed
- // size of this instruction.
- Return ret = targetInfo.getIR().asReturn();
- if (ret != null && size == targetInfo.getSize() && ret.getPosition().isNone()) {
- Instruction dex = ret.createDexInstruction(builder);
- dex.setOffset(getOffset()); // for better printing of the dex code.
- instructions.add(dex);
- } else if (size == relativeOffset) {
+ if (size == relativeOffset) {
// We should never generate a goto targeting the next instruction. However, if we do
// we replace it with nops. This works around a dalvik bug where the dalvik tracing
// jit crashes on 'goto next instruction' on Android 4.1.1.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 55e4b83..b9bbe5a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -136,7 +136,7 @@
@Override
public void buildPrelude(IRBuilder builder) {
- currentPosition = Position.none();
+ currentPosition = Position.synthetic(0, method, null);
if (code.incomingRegisterSize == 0) {
return;
}
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 98c1015..6364c82 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
@@ -396,10 +396,6 @@
// necessary.
ir.splitCriticalEdges();
- if (options.testing.invertConditionals) {
- invertConditionalsForTesting(ir);
- }
-
// Create block order and make sure that all blocks are immediately followed by their
// fallthrough block if any.
ir.traceBlocks();
@@ -440,7 +436,7 @@
} else {
current = position;
}
- } else if (position.isSome() && !position.equals(current)) {
+ } else if (position.isSome() && !position.synthetic && !position.equals(current)) {
DebugPosition positionChange = new DebugPosition();
positionChange.setPosition(position);
it.previous();
@@ -2006,14 +2002,6 @@
return type != NumericType.FLOAT && type != NumericType.DOUBLE && type != NumericType.LONG;
}
- private static void invertConditionalsForTesting(IRCode code) {
- for (BasicBlock block : code.blocks) {
- if (block.exit().isIf()) {
- block.exit().asIf().invert();
- }
- }
- }
-
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 39cea77..a23505f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -511,6 +512,14 @@
}
}
+ private static void invertConditionalsForTesting(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ if (block.exit().isIf()) {
+ block.exit().asIf().invert();
+ }
+ }
+ }
+
private void rewriteCode(DexEncodedMethod method,
OptimizationFeedback feedback,
Predicate<DexEncodedMethod> isProcessedConcurrently,
@@ -589,6 +598,12 @@
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
codeRewriter.simplifyIf(code, typeEnvironment);
+
+ if (options.testing.invertConditionals) {
+ invertConditionalsForTesting(code);
+ code.traceBlocks();
+ }
+
if (options.enableNonNullTracking && nonNullTracker != null) {
nonNullTracker.cleanupNonNull(code);
assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 69ecb83..12429e9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -186,6 +186,9 @@
// Canonicalized positions to lower memory usage.
private final Int2ReferenceMap<Position> canonicalPositions = new Int2ReferenceOpenHashMap<>();
+ // Synthetic position with line = 0.
+ private Position preamblePosition = null;
+
// Cooked position to indicate positions in synthesized code (ie, for synchronization).
private Position syntheticPosition = null;
@@ -262,6 +265,8 @@
@Override
public void buildPrelude(IRBuilder builder) {
+ currentPosition = getPreamblePosition();
+
// Record types for arguments.
Int2ReferenceMap<ValueType> argumentLocals = recordArgumentTypes();
Int2ReferenceMap<ValueType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
@@ -327,8 +332,6 @@
locals = state.openLocals(getOffset(initialLabel));
}
- currentPosition = Position.none();
-
// Build the actual argument instructions now that type and debug information is known
// for arguments.
buildArgumentInstructions(builder);
@@ -457,7 +460,7 @@
private void buildExceptionalPostlude(IRBuilder builder) {
assert isSynchronized();
generatingMethodSynchronization = true;
- currentPosition = getSyntheticPosition();
+ currentPosition = getExceptionalExitPosition();
buildMonitorExit(builder);
builder.addThrow(getMoveExceptionRegister());
generatingMethodSynchronization = false;
@@ -503,7 +506,9 @@
// writes after the line has changed and thus causing locals to become visible too late.
currentPosition =
getDebugPositionAtOffset(
- insn instanceof LabelNode ? instructionIndex - 1 : instructionIndex);
+ ((instructionIndex > 0) && (insn instanceof LabelNode))
+ ? instructionIndex - 1
+ : instructionIndex);
build(insn, builder);
@@ -2906,10 +2911,11 @@
@Override
public Position getDebugPositionAtOffset(int offset) {
if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
- return getSyntheticPosition();
+ return getExceptionalExitPosition();
}
int index = instructionIndex(offset);
if (index < 0 || instructionCount() <= index) {
+ assert false;
return Position.none();
}
AbstractInsnNode insn = node.instructions.get(index);
@@ -2923,7 +2929,7 @@
LineNumberNode line = (LineNumberNode) insn;
return getCanonicalPosition(line.line);
}
- return Position.none();
+ return getPreamblePosition();
}
@Override
@@ -2936,12 +2942,19 @@
line, l -> new Position(l, null, method, callerPosition));
}
+ private Position getPreamblePosition() {
+ if (preamblePosition == null) {
+ preamblePosition = Position.synthetic(0, method, null);
+ }
+ return preamblePosition;
+ }
+
// If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a
// position that is not actually a valid program position, so as not to incorrectly position the
// user on an exit that is not the actual exit being taken. Our heuristic for this is that if the
// method has at least two positions we use the first position minus one as the synthetic exit.
// If the method only has one position it is safe to just use that position.
- private Position getSyntheticPosition() {
+ private Position getExceptionalExitPosition() {
if (syntheticPosition == null) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index af7f054..0c0cd84 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -949,6 +949,10 @@
return Paths.get(System.getProperty("java.home"), "bin", "java").toString();
}
+ public static ProcessResult runArtRaw(ArtCommandBuilder builder) throws IOException {
+ return runArtProcessRaw(builder);
+ }
+
public static ProcessResult runArtRaw(String file, String mainClass)
throws IOException {
return runArtRaw(Collections.singletonList(file), mainClass, null);
diff --git a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java
new file mode 100644
index 0000000..14f9f98
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestRunner.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2018, 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.debuginfo;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class PreamblePositionTestRunner {
+
+ private static final String TEST_CLASS = "PreamblePositionTestSource";
+ private static final String TEST_PACKAGE = "com.android.tools.r8.debuginfo";
+
+ @ClassRule public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void testBothBranchesDefaultConditionals() throws Exception {
+ testBothBranches(false);
+ }
+
+ @Test
+ public void testBothBranchesInvertConditionals() throws Exception {
+ testBothBranches(true);
+ }
+
+ private void testBothBranches(boolean invertConditionals) throws Exception {
+ Path testClassDir = temp.newFolder(TEST_PACKAGE.split(".")).toPath();
+ Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
+ Path outputDexPath = temp.newFolder().toPath();
+
+ Files.write(testClassPath, PreamblePositionTestSourceDump.dump());
+
+ ToolHelper.runD8(
+ D8Command.builder()
+ .addProgramFiles(testClassPath)
+ .setOutput(outputDexPath, OutputMode.DexIndexed)
+ .setMode(CompilationMode.RELEASE),
+ options -> {
+ options.testing.invertConditionals = invertConditionals;
+ });
+
+ String fileName = TEST_CLASS + ".java";
+
+ for (boolean testTrueBranch : new boolean[] {false, true}) {
+ ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
+ artCommandBuilder.appendClasspath(outputDexPath.resolve("classes.dex").toString());
+ artCommandBuilder.setMainClass(TEST_PACKAGE + "." + TEST_CLASS);
+ if (!testTrueBranch) {
+ artCommandBuilder.appendProgramArgument("1");
+ }
+
+ ProcessResult result = ToolHelper.runArtRaw(artCommandBuilder);
+
+ assertNotEquals(result.exitCode, 0);
+ if (testTrueBranch) {
+ assertTrue(result.stderr.contains("<true-branch-exception>"));
+ // Must have either explicit line = 0 or no line info at all.
+ assertTrue(
+ result.stderr.contains(fileName + ":0")
+ || (result.stderr.contains("at " + TEST_PACKAGE + "." + TEST_CLASS + ".main")
+ && !result.stderr.contains(fileName + ":")));
+ } else {
+ assertTrue(result.stderr.contains("<false-branch-exception>"));
+ assertTrue(
+ result.stderr.contains(
+ fileName + ":" + PreamblePositionTestSourceDump.FALSE_BRANCH_LINE_NUMBER));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
new file mode 100644
index 0000000..5ed2a4e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2018, 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.debuginfo;
+
+import java.util.*;
+import org.objectweb.asm.*;
+
+/*
+Generated from the source code below, line numbers removed, except for the false branch,
+which is set to FALSE_BRANCH_LINE_NUMBER.
+
+ package com.android.tools.r8.debuginfo;
+
+ public class PreamblePositionTestSource {
+ public static void main(String[] args) {
+ System.err.println("<first-line>");
+ if (args.length == 0) {
+ throw new RuntimeException("<true-branch-exception>");
+ } else {
+ throw new RuntimeException("<false-branch-exception>");
+ }
+ }
+ }
+*/
+
+public class PreamblePositionTestSourceDump implements Opcodes {
+
+ static final int FALSE_BRANCH_LINE_NUMBER = 123;
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/debuginfo/PreamblePositionTestSource",
+ null,
+ "java/lang/Object",
+ null);
+
+ cw.visitSource("PreamblePositionTestSource.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable(
+ "this", "Lcom/android/tools/r8/debuginfo/PreamblePositionTestSource;", null, l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("<first-line>");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ Label l2 = new Label();
+ mv.visitJumpInsn(IFNE, l2);
+ Label l3 = new Label();
+ mv.visitLabel(l3);
+ mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("<true-branch-exception>");
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(ATHROW);
+ mv.visitLabel(l2);
+ mv.visitLineNumber(FALSE_BRANCH_LINE_NUMBER, l2);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("<false-branch-exception>");
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(ATHROW);
+ Label l4 = new Label();
+ mv.visitLabel(l4);
+ mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l4, 0);
+ mv.visitMaxs(3, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 64ea9b4..5088aeb 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.code.ConstWideHigh16;
import com.android.tools.r8.code.DivInt;
import com.android.tools.r8.code.DivInt2Addr;
+import com.android.tools.r8.code.Goto;
import com.android.tools.r8.code.InvokeStatic;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.MoveResult;
@@ -1244,7 +1245,7 @@
assertTrue(code.instructions[1] instanceof InvokeStatic);
assertTrue(code.instructions[2] instanceof MoveResult);
assertTrue(code.instructions[3] instanceof DivInt2Addr);
- assertTrue(code.instructions[4] instanceof Return);
+ assertTrue(code.instructions[4] instanceof Goto);
assertTrue(code.instructions[5] instanceof Const4);
assertTrue(code.instructions[6] instanceof Return);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];