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 {