Version 1.6.77
Cherry-pick: Allow unreachable ret instructioins in the input.
CL: https://r8-review.googlesource.com/c/r8/+/49261/
Bug: 150274427
Change-Id: I7970cca28deb88413d802947318cc05742d811fd
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 86ce757..81a04f2 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.76";
+ public static final String LABEL = "1.6.77";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 0248418..a523a94 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfJsrRet;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfLogicalBinop;
@@ -654,6 +655,11 @@
builder.append(type.getType().toString());
}
+ public void print(CfJsrRet ret) {
+ indent();
+ builder.append("ret ").append(ret.getLocal());
+ }
+
private String getLabel(CfLabel label) {
return labelToIndex != null ? ("L" + labelToIndex.getInt(label)) : "L?";
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
new file mode 100644
index 0000000..7ca6b8a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -0,0 +1,59 @@
+// 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfJsrRet extends CfInstruction {
+
+ private static CompilationError error() {
+ throw new CompilationError(
+ "Invalid compilation of code with reachable jump subroutine RET instruction");
+ }
+
+ private final int local;
+
+ public CfJsrRet(int local) {
+ this.local = local;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor, NamingLens lens) {
+ throw error();
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ throw error();
+ }
+
+ @Override
+ public ConstraintWithTarget inliningConstraint(
+ InliningConstraints inliningConstraints,
+ DexType invocationContext,
+ GraphLense graphLense,
+ AppView<?> appView) {
+ throw error();
+ }
+
+ public int getLocal() {
+ return local;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 76dd396..13695f8 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfJsrRet;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfLogicalBinop;
@@ -649,7 +650,10 @@
type = ValueType.OBJECT;
break;
case Opcodes.RET:
- throw new JsrEncountered("RET should be handled by the ASM jsr inliner");
+ {
+ instructions.add(new CfJsrRet(var));
+ return;
+ }
default:
throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
}
diff --git a/src/test/java/com/android/tools/r8/regress/b150274427/JsrRetRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b150274427/JsrRetRegressionTest.java
new file mode 100644
index 0000000..1a49e85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b150274427/JsrRetRegressionTest.java
@@ -0,0 +1,100 @@
+// 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.b150274427;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.MethodTransformer;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class JsrRetRegressionTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public JsrRetRegressionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testUnreachableJsrRet() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(getTransformClass(false))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class);
+ } else {
+ testForD8()
+ .addProgramClassFileData(getTransformClass(false))
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class);
+ }
+ }
+
+ @Test
+ public void testReachableJsrRet() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(getTransformClass(true))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(VerifyError.class);
+ return;
+ }
+ try {
+ testForD8()
+ .addProgramClassFileData(getTransformClass(true))
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorsCount(1);
+ });
+ fail();
+ } catch (CompilationFailedException e) {
+ // Expected error.
+ }
+ }
+
+ private byte[] getTransformClass(boolean replaceThrow) throws IOException {
+ return transformer(TestClass.class)
+ .setVersion(50)
+ .addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitInsn(int opcode) {
+ if (opcode == Opcodes.ATHROW) {
+ if (!replaceThrow) {
+ super.visitInsn(opcode);
+ }
+ super.visitVarInsn(Opcodes.RET, 0);
+ } else {
+ super.visitInsn(opcode);
+ }
+ }
+ })
+ .transform();
+ }
+
+ private static class TestClass {
+
+ public static void main(String[] args) {
+ throw new RuntimeException();
+ // Reachable or unreachable JSR inserted here.
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 8157f47..66eb1d9 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -6,16 +6,21 @@
import static org.objectweb.asm.Opcodes.ASM7;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
import com.android.tools.r8.utils.DescriptorUtils;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@@ -161,6 +166,104 @@
});
}
+ public ClassFileTransformer setVersion(int newVersion) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(newVersion, access, name, signature, superName, interfaces);
+ }
+ });
+ }
+
+ public ClassFileTransformer setMinVersion(int minVersion) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(
+ Integer.max(version, minVersion), access, name, signature, superName, interfaces);
+ }
+ });
+ }
+
+ public ClassFileTransformer setPublic(Method method) {
+ return setAccessFlags(
+ method,
+ accessFlags -> {
+ accessFlags.unsetPrivate();
+ accessFlags.unsetProtected();
+ accessFlags.setPublic();
+ });
+ }
+
+ public ClassFileTransformer setPrivate(Method method) {
+ return setAccessFlags(
+ method,
+ accessFlags -> {
+ accessFlags.unsetPublic();
+ accessFlags.unsetProtected();
+ accessFlags.setPrivate();
+ });
+ }
+
+ public ClassFileTransformer setSynthetic(Method method) {
+ return setAccessFlags(method, AccessFlags::setSynthetic);
+ }
+
+ public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ final MethodReference methodReference = Reference.methodFromMethod(method);
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ boolean isConstructor =
+ name.equals(Constants.INSTANCE_INITIALIZER_NAME)
+ || name.equals(Constants.CLASS_INITIALIZER_NAME);
+ MethodAccessFlags accessFlags =
+ MethodAccessFlags.fromCfAccessFlags(access, isConstructor);
+ if (name.equals(methodReference.getMethodName())
+ && descriptor.equals(methodReference.getMethodDescriptor())) {
+ setter.accept(accessFlags);
+ }
+ return super.visitMethod(
+ accessFlags.getAsCfAccessFlags(), name, descriptor, signature, exceptions);
+ }
+ });
+ }
+
+ @FunctionalInterface
+ public interface MethodPredicate {
+ boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
+ }
+
+ public ClassFileTransformer removeMethods(MethodPredicate predicate) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ return predicate.test(access, name, descriptor, signature, exceptions)
+ ? null
+ : super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ });
+ }
+
/** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
@FunctionalInterface
public interface MethodInsnTransform {