Version 1.2.51.
Merge: Implement workaround for Dalvik tracing JIT tracing past the end of instructions.
CL: https://r8-review.googlesource.com/c/r8/+/29242
R=christofferqa@google.com
Bug: 117907456
Change-Id: I390087e9ff64808bbc9d9f03fa46db7e80298837
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 7a8c088..e8dd31c 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.2.50";
+ public static final String LABEL = "1.2.51";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0d47537..733ebdf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -267,7 +267,7 @@
RegisterAllocator registerAllocator,
InternalOptions options) {
final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
- code = builder.build(method.getArity());
+ code = builder.build();
}
@Override
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 07107a5..c426a29 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
@@ -32,6 +32,7 @@
import com.android.tools.r8.code.MoveWide16;
import com.android.tools.r8.code.MoveWideFrom16;
import com.android.tools.r8.code.Nop;
+import com.android.tools.r8.code.Throw;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
@@ -112,6 +113,9 @@
// The string reference in the code with the highest index.
private DexString highestSortingReferencedString = null;
+ // Whether or not the generated code has a backwards branch.
+ private boolean hasBackwardsBranch = false;
+
BasicBlock nextBlock;
public DexBuilder(
@@ -144,7 +148,7 @@
* This is a two pass construction that will first compute concrete offsets and then construct
* the concrete instructions.
*/
- public DexCode build(int numberOfArguments) {
+ public DexCode build() {
int numberOfInstructions;
int offset;
@@ -204,6 +208,23 @@
debugEventBuilder.add(instructionStartOffset, instructionOffset, ir);
}
+ // Workaround dalvik tracing bug, where the dalvik tracing JIT can end up tracing
+ // past the end of the instruction stream if the instruction streams ends with a throw.
+ // We could have also changed the block order, however, moving the throwing block higher
+ // led to larger code in all experiments (multiple gmscore version and R8 run on itself).
+ if (options.canHaveTracingPastInstructionsStreamBug()
+ && dexInstructions.get(dexInstructions.size() - 1) instanceof Throw
+ && hasBackwardsBranch) {
+ Nop nop = new Nop();
+ dexInstructions.add(nop);
+ nop.setOffset(offset);
+ offset += nop.getSize();
+ Goto go = new Goto(-nop.getSize());
+ dexInstructions.add(go);
+ go.setOffset(offset);
+ offset += go.getSize();
+ }
+
// Compute switch payloads.
for (SwitchPayloadInfo switchPayloadInfo : switchPayloadInfos) {
// Align payloads at even addresses.
@@ -243,7 +264,8 @@
registerAllocator.registersUsed(),
inRegisterCount,
outRegisterCount,
- dexInstructions.toArray(new Instruction[dexInstructions.size()]), tryInfo.tries,
+ dexInstructions.toArray(new Instruction[dexInstructions.size()]),
+ tryInfo.tries,
tryInfo.handlers,
debugEventBuilder.build(),
highestSortingReferencedString);
@@ -984,6 +1006,9 @@
int source = builder.getInfo(jump).getOffset();
Info targetInfo = builder.getTargetInfo(jump.getTarget());
int relativeOffset = targetInfo.getOffset() - source;
+ if (relativeOffset < 0) {
+ builder.hasBackwardsBranch = true;
+ }
// 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();
@@ -1069,6 +1094,11 @@
int target = builder.getInfo(branch.getTrueTarget().entry()).getOffset();
int relativeOffset = target - source;
int register1 = builder.allocatedRegister(branch.inValues().get(0), branch.getNumber());
+
+ if (relativeOffset < 0) {
+ builder.hasBackwardsBranch = true;
+ }
+
if (size == 3) {
assert branchesToSelf(builder);
Nop nop = new Nop();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index ca9b1956..989c531 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -709,4 +709,15 @@
public boolean canHaveExceptionTargetingLoopHeaderBug() {
return isGeneratingDex() && !debug && minApiLevel < AndroidApiLevel.M.getLevel();
}
+
+ // The Dalvik tracing JIT can trace past the end of the instruction stream and end up
+ // parsing non-code bytes as code (typically leading to a crash).
+ //
+ // In order to workaround this we insert "nop; goto -1;" after the throw when the instruction
+ // stream ends with a throw and there are backwards branches in the code.
+ //
+ // See b/117907456.
+ public boolean canHaveTracingPastInstructionsStreamBug() {
+ return minApiLevel < AndroidApiLevel.L.getLevel();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/regress/b117907456/B117907456.java b/src/test/java/com/android/tools/r8/regress/b117907456/B117907456.java
new file mode 100644
index 0000000..7417269
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b117907456/B117907456.java
@@ -0,0 +1,74 @@
+// 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.regress.b117907456;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Nop;
+import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+class TestClass {
+ public static void f(boolean loop) {
+ while (loop) {
+ System.out.println("Noooooo!");
+ }
+ throw new RuntimeException();
+ }
+}
+
+public class B117907456 extends TestBase {
+ @Test
+ public void testNopDupInsertionForDalvikTracingBug()
+ throws IOException, CompilationFailedException, ExecutionException {
+ MethodSubject method = getMethodSubject(AndroidApiLevel.K);
+ Instruction[] instructions = method.getMethod().getCode().asDexCode().instructions;
+ Instruction lastInstruction = instructions[instructions.length - 1];
+ assert !(lastInstruction instanceof Throw);
+ assert lastInstruction instanceof Goto;
+ Instruction previous = instructions[instructions.length - 2];
+ assert previous instanceof Nop;
+ }
+
+ @Test
+ public void testNoNopDupInsertionForDalvikTracingBug()
+ throws IOException, CompilationFailedException, ExecutionException {
+ MethodSubject method = getMethodSubject(AndroidApiLevel.L);
+ Instruction[] instructions = method.getMethod().getCode().asDexCode().instructions;
+ Instruction lastInstruction = instructions[instructions.length - 1];
+ assert lastInstruction instanceof Throw;
+ }
+
+ private MethodSubject getMethodSubject(AndroidApiLevel level)
+ throws CompilationFailedException, IOException, ExecutionException {
+ AndroidApp app =
+ ToolHelper.runD8(
+ D8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(TestClass.class), Origin.unknown())
+ .setMinApiLevel(level.getLevel()));
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ assertThat(clazz, isPresent());
+ MethodSubject method = clazz.method("void", "f", ImmutableList.of("boolean"));
+ assertThat(method, isPresent());
+ return method;
+ }
+}