|  | // 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.bridgeremoval; | 
|  |  | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; | 
|  | import static org.hamcrest.MatcherAssert.assertThat; | 
|  | import static org.junit.Assert.assertEquals; | 
|  |  | 
|  | import com.android.tools.r8.TestBase; | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.ToolHelper.ProcessResult; | 
|  | import com.android.tools.r8.code.InvokeVirtual; | 
|  | import com.android.tools.r8.code.ReturnVoid; | 
|  | import com.android.tools.r8.graph.DexCode; | 
|  | import com.android.tools.r8.jasmin.JasminBuilder; | 
|  | import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | 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 com.google.common.collect.ImmutableList; | 
|  | import java.nio.file.Path; | 
|  | import org.junit.Test; | 
|  |  | 
|  | public class B77836766 extends TestBase { | 
|  |  | 
|  | /** | 
|  | * The below Jasmin code mimics the following Kotlin code: | 
|  | * | 
|  | * package java_pkg; | 
|  | * | 
|  | * public interface Itf1 { | 
|  | *   void foo(String arg); | 
|  | * } | 
|  | * | 
|  | * public interface Itf2 { | 
|  | *   void foo(Integer arg); | 
|  | * } | 
|  | * | 
|  | * package kt_pkg; | 
|  | * | 
|  | * internal abstract class AbsCls<T> { | 
|  | *   void foo(T obj) { ... } | 
|  | * } | 
|  | * | 
|  | * internal class Cls1() : AbsCls<String>(), Itf1 | 
|  | * | 
|  | * internal class Cls2() : AbsCls<Integer>(), Itf2 | 
|  | * | 
|  | * | 
|  | * kotlinc introduced bridge methods Cls?#foo to AbsCls#foo: | 
|  | * | 
|  | * class Cls1 extends AbsCls implements Itf1 { | 
|  | *   public bridge synthetic void foo(String arg) { | 
|  | *     invoke-virtual Cls1#foo(Object)V | 
|  | *   } | 
|  | * } | 
|  | * | 
|  | * Note that we can't write such code in Java because javac requires Itf?#foo, which are | 
|  | * technically abstract methods, to be explicitly overridden. | 
|  | */ | 
|  | @Test | 
|  | public void test_bridgeTargetInBase_differentBridges() throws Exception { | 
|  | JasminBuilder jasminBuilder = new JasminBuilder(); | 
|  |  | 
|  | ClassBuilder absCls = jasminBuilder.addClass("AbsCls"); | 
|  | absCls.setAccess("public abstract"); | 
|  | absCls.addFinalMethod("foo", ImmutableList.of("Ljava/lang/Object;"), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 2", | 
|  | "getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "aload_1", | 
|  | "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder itf1 = jasminBuilder.addInterface("Itf1"); | 
|  | itf1.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V"); | 
|  |  | 
|  | ClassBuilder cls1 = jasminBuilder.addClass("Cls1", absCls.name, itf1.name); | 
|  | // Mimic Kotlin's "internal" class | 
|  | cls1.setAccess(""); | 
|  | cls1.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 2", | 
|  | "aload_0", | 
|  | "aload_1", | 
|  | "invokevirtual " + cls1.name + "/foo(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder itf2 = jasminBuilder.addInterface("Itf2"); | 
|  | itf2.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V"); | 
|  |  | 
|  | ClassBuilder cls2 = jasminBuilder.addClass("Cls2", absCls.name, itf2.name); | 
|  | // Mimic Kotlin's "internal" class | 
|  | cls2.setAccess(""); | 
|  | cls2.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 2", | 
|  | "aload_0", | 
|  | "aload_1", | 
|  | "invokevirtual " + cls2.name + "/foo(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder mainClass = jasminBuilder.addClass("Main"); | 
|  | mainClass.addMainMethod( | 
|  | ".limit stack 5", | 
|  | ".limit locals 2", | 
|  | "new " + cls1.name, | 
|  | "dup", | 
|  | "invokespecial " + cls1.name + "/<init>()V", | 
|  | "astore_0", | 
|  | "aload_0", | 
|  | "ldc \"Hello\"", | 
|  | "invokevirtual " + cls1.name + "/foo(Ljava/lang/String;)V", | 
|  | "new " + cls2.name, | 
|  | "dup", | 
|  | "invokespecial " + cls2.name + "/<init>()V", | 
|  | "astore_0", | 
|  | "aload_0", | 
|  | "iconst_0", | 
|  | "invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;", | 
|  | "invokevirtual " + cls2.name + "/foo(Ljava/lang/Integer;)V", | 
|  | "return" | 
|  | ); | 
|  |  | 
|  | final String mainClassName = mainClass.name; | 
|  | String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false); | 
|  |  | 
|  | AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig); | 
|  |  | 
|  | CodeInspector inspector = new CodeInspector(processedApp); | 
|  | ClassSubject absSubject = inspector.clazz(absCls.name); | 
|  | assertThat(absSubject, isPresent()); | 
|  | ClassSubject cls1Subject = inspector.clazz(cls1.name); | 
|  | assertThat(cls1Subject, isPresent()); | 
|  | ClassSubject cls2Subject = inspector.clazz(cls2.name); | 
|  | assertThat(cls2Subject, isPresent()); | 
|  |  | 
|  | // Cls1#foo and Cls2#foo should not refer to each other. | 
|  | // They can invoke their own bridge method or AbsCls#foo (via member rebinding). | 
|  |  | 
|  | MethodSubject fooInCls2 = | 
|  | cls2Subject.method("void", "foo", ImmutableList.of("java.lang.Integer")); | 
|  | assertThat(fooInCls2, isPresent()); | 
|  | DexCode code = fooInCls2.getMethod().getCode().asDexCode(); | 
|  | checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class)); | 
|  | InvokeVirtual invoke = (InvokeVirtual) code.instructions[0]; | 
|  | assertEquals(absSubject.getDexClass().type, invoke.getMethod().holder); | 
|  |  | 
|  | MethodSubject fooInCls1 = | 
|  | cls1Subject.method("void", "foo", ImmutableList.of("java.lang.String")); | 
|  | assertThat(fooInCls1, isPresent()); | 
|  | code = fooInCls1.getMethod().getCode().asDexCode(); | 
|  | checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class)); | 
|  | invoke = (InvokeVirtual) code.instructions[0]; | 
|  | assertEquals(absSubject.getDexClass().type, invoke.getMethod().holder); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * class Base { | 
|  | *   void foo(Object o) {...} | 
|  | * } | 
|  | * interface ItfInteger { | 
|  | *   void foo(Integer o); | 
|  | * } | 
|  | * class DerivedInteger extends Base implements ItfInteger { | 
|  | *   // Bridge method deferring to Base#foo(Object): | 
|  | *   public bridge synthetic void foo(Integer o) { | 
|  | *     foo((Object) o); | 
|  | *   } } | 
|  | * class DerivedString extends Base { | 
|  | *   // Regular non-bridge method calling Base#foo(Object): | 
|  | *   public void bar(String o) { | 
|  | *     foo(o); | 
|  | *   } } | 
|  | */ | 
|  | @Test | 
|  | public void test_bridgeTargetInBase_bridgeAndNonBridge() throws Exception { | 
|  | JasminBuilder jasminBuilder = new JasminBuilder(); | 
|  |  | 
|  | ClassBuilder baseCls = jasminBuilder.addClass("Base"); | 
|  | baseCls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/Object;"), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 2", | 
|  | "getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "aload_1", | 
|  | "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder itf = jasminBuilder.addInterface("ItfInteger"); | 
|  | itf.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V"); | 
|  |  | 
|  | ClassBuilder cls1 = jasminBuilder.addClass("DerivedInteger", baseCls.name, itf.name); | 
|  | cls1.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 2", | 
|  | "aload_0", | 
|  | "aload_1", | 
|  | "invokevirtual " + cls1.name + "/foo(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder cls2 = jasminBuilder.addClass("DerivedString", baseCls.name); | 
|  | cls2.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 2", | 
|  | "aload_0", | 
|  | "aload_1", | 
|  | "invokevirtual " + cls2.name + "/foo(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder mainClass = jasminBuilder.addClass("Main"); | 
|  | mainClass.addMainMethod( | 
|  | ".limit stack 5", | 
|  | ".limit locals 2", | 
|  | "new " + cls1.name, | 
|  | "dup", | 
|  | "invokespecial " + cls1.name + "/<init>()V", | 
|  | "astore_0", | 
|  | "aload_0", | 
|  | "iconst_0", | 
|  | "invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;", | 
|  | "invokevirtual " + cls1.name + "/foo(Ljava/lang/Integer;)V", | 
|  | "new " + cls2.name, | 
|  | "dup", | 
|  | "invokespecial " + cls2.name + "/<init>()V", | 
|  | "astore_0", | 
|  | "aload_0", | 
|  | "ldc \"Bar\"", | 
|  | "invokevirtual " + cls2.name + "/bar(Ljava/lang/String;)V", | 
|  | "return" | 
|  | ); | 
|  |  | 
|  | final String mainClassName = mainClass.name; | 
|  | String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false); | 
|  |  | 
|  | AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig); | 
|  |  | 
|  | CodeInspector inspector = new CodeInspector(processedApp); | 
|  | ClassSubject baseSubject = inspector.clazz(baseCls.name); | 
|  | assertThat(baseSubject, isPresent()); | 
|  | ClassSubject cls1Subject = inspector.clazz(cls1.name); | 
|  | assertThat(cls1Subject, isPresent()); | 
|  | ClassSubject cls2Subject = inspector.clazz(cls2.name); | 
|  | assertThat(cls2Subject, isPresent()); | 
|  |  | 
|  | // Cls1#foo and Cls2#bar should refer to Base#foo. | 
|  |  | 
|  | MethodSubject barInCls2 = | 
|  | cls2Subject.method("void", "bar", ImmutableList.of("java.lang.String")); | 
|  | assertThat(barInCls2, isPresent()); | 
|  | DexCode code = barInCls2.getMethod().getCode().asDexCode(); | 
|  | checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class)); | 
|  | InvokeVirtual invoke = (InvokeVirtual) code.instructions[0]; | 
|  | assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder); | 
|  |  | 
|  | MethodSubject fooInCls1 = | 
|  | cls1Subject.method("void", "foo", ImmutableList.of("java.lang.Integer")); | 
|  | assertThat(fooInCls1, isPresent()); | 
|  | code = fooInCls1.getMethod().getCode().asDexCode(); | 
|  | checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class)); | 
|  | invoke = (InvokeVirtual) code.instructions[0]; | 
|  | assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * class Base { | 
|  | *   protected void foo(Object o) { ... } | 
|  | *   // Bridge method deferring to Base#foo(Object): | 
|  | *   public bridge synthetic void foo(Integer o) { | 
|  | *     foo((Object) o); | 
|  | *   } } | 
|  | * class DerivedString extends Base { | 
|  | *   // Regular non-bridge method calling Base#foo(Object): | 
|  | *   public void bar(String o) { | 
|  | *     foo(o); | 
|  | *   } } | 
|  | */ | 
|  | @Test | 
|  | public void test_nonBridgeInSubType() throws Exception { | 
|  | JasminBuilder jasminBuilder = new JasminBuilder(); | 
|  |  | 
|  | ClassBuilder baseCls = jasminBuilder.addClass("Base"); | 
|  | baseCls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/Object;"), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 2", | 
|  | "getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "aload_1", | 
|  | "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  | baseCls.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 2", | 
|  | "aload_0", | 
|  | "aload_1", | 
|  | "invokevirtual " + baseCls.name + "/foo(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder subCls = jasminBuilder.addClass("DerivedString", baseCls.name); | 
|  | subCls.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 2", | 
|  | "aload_0", | 
|  | "aload_1", | 
|  | "invokevirtual " + subCls.name + "/foo(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder mainClass = jasminBuilder.addClass("Main"); | 
|  | mainClass.addMainMethod( | 
|  | ".limit stack 5", | 
|  | ".limit locals 2", | 
|  | "new " + baseCls.name, | 
|  | "dup", | 
|  | "invokespecial " + baseCls.name + "/<init>()V", | 
|  | "astore_0", | 
|  | "aload_0", | 
|  | "iconst_0", | 
|  | "invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;", | 
|  | "invokevirtual " + baseCls.name + "/foo(Ljava/lang/Integer;)V", | 
|  | "new " + subCls.name, | 
|  | "dup", | 
|  | "invokespecial " + subCls.name + "/<init>()V", | 
|  | "astore_0", | 
|  | "aload_0", | 
|  | "ldc \"Bar\"", | 
|  | "invokevirtual " + subCls.name + "/bar(Ljava/lang/String;)V", | 
|  | "return" | 
|  | ); | 
|  |  | 
|  | final String mainClassName = mainClass.name; | 
|  | String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false); | 
|  |  | 
|  | AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig); | 
|  |  | 
|  | CodeInspector inspector = new CodeInspector(processedApp); | 
|  | ClassSubject baseSubject = inspector.clazz(baseCls.name); | 
|  | assertThat(baseSubject, isPresent()); | 
|  | ClassSubject subSubject = inspector.clazz(subCls.name); | 
|  | assertThat(subSubject, isPresent()); | 
|  |  | 
|  | // DerivedString2#bar should refer to Base#foo. | 
|  |  | 
|  | MethodSubject barInSub = | 
|  | subSubject.method("void", "bar", ImmutableList.of("java.lang.String")); | 
|  | assertThat(barInSub, isPresent()); | 
|  | DexCode code = barInSub.getMethod().getCode().asDexCode(); | 
|  | checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class)); | 
|  | InvokeVirtual invoke = (InvokeVirtual) code.instructions[0]; | 
|  | assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * public class Base { | 
|  | *  public bridge void foo(Integer i) { foo((Object) i); } | 
|  | *  public foo(Object o) { print(o); } | 
|  | *  public bar(String s) { foo(s); } | 
|  | * } | 
|  | */ | 
|  | @Test | 
|  | public void test_bridgeTargetInsideTheSameClass() throws Exception { | 
|  | JasminBuilder jasminBuilder = new JasminBuilder(); | 
|  |  | 
|  | ClassBuilder cls = jasminBuilder.addClass("Base"); | 
|  | cls.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 2", | 
|  | "aload_0", | 
|  | "aload_1", | 
|  | "invokevirtual " + cls.name + "/foo(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  | cls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/Object;"), "V", | 
|  | ".limit stack 3", | 
|  | ".limit locals 2", | 
|  | "getstatic java/lang/System/out Ljava/io/PrintStream;", | 
|  | "aload_1", | 
|  | "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  | cls.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V", | 
|  | ".limit stack 2", | 
|  | ".limit locals 2", | 
|  | "aload_0", | 
|  | "aload_1", | 
|  | "invokevirtual " + cls.name + "/foo(Ljava/lang/Object;)V", | 
|  | "return"); | 
|  |  | 
|  | ClassBuilder mainClass = jasminBuilder.addClass("Main"); | 
|  | mainClass.addMainMethod( | 
|  | ".limit stack 5", | 
|  | ".limit locals 2", | 
|  | "new " + cls.name, | 
|  | "dup", | 
|  | "invokespecial " + cls.name + "/<init>()V", | 
|  | "astore_0", | 
|  | "aload_0", | 
|  | "iconst_0", | 
|  | "invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;", | 
|  | "invokevirtual " + cls.name + "/foo(Ljava/lang/Integer;)V", | 
|  | "new " + cls.name, | 
|  | "dup", | 
|  | "invokespecial " + cls.name + "/<init>()V", | 
|  | "astore_0", | 
|  | "aload_0", | 
|  | "ldc \"Bar\"", | 
|  | "invokevirtual " + cls.name + "/bar(Ljava/lang/String;)V", | 
|  | "return" | 
|  | ); | 
|  | final String mainClassName = mainClass.name; | 
|  | String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false); | 
|  | AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig); | 
|  |  | 
|  | CodeInspector inspector = new CodeInspector(processedApp); | 
|  | ClassSubject baseSubject = inspector.clazz(cls.name); | 
|  | assertThat(baseSubject, isPresent()); | 
|  |  | 
|  | // Base#bar should remain as-is, i.e., refer to Base#foo(Object). | 
|  |  | 
|  | MethodSubject barInSub = | 
|  | baseSubject.method("void", "bar", ImmutableList.of("java.lang.String")); | 
|  | assertThat(barInSub, isPresent()); | 
|  | DexCode code = barInSub.getMethod().getCode().asDexCode(); | 
|  | checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class)); | 
|  | InvokeVirtual invoke = (InvokeVirtual) code.instructions[0]; | 
|  | assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder); | 
|  | } | 
|  |  | 
|  | private AndroidApp runAndVerifyOnJvmAndArt( | 
|  | JasminBuilder jasminBuilder, String mainClassName, String proguardConfig) throws Exception { | 
|  | // Run input program on java. | 
|  | Path outputDirectory = temp.newFolder().toPath(); | 
|  | jasminBuilder.writeClassFiles(outputDirectory); | 
|  | ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName); | 
|  | assertEquals(0, javaResult.exitCode); | 
|  |  | 
|  | AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig, this::configure); | 
|  |  | 
|  | // Run processed (output) program on ART | 
|  | ProcessResult artResult = runOnArtRaw(processedApp, mainClassName); | 
|  | assertEquals(javaResult.stdout, artResult.stdout); | 
|  | assertEquals(-1, artResult.stderr.indexOf("VerifyError")); | 
|  |  | 
|  | return processedApp; | 
|  | } | 
|  |  | 
|  | private void configure(InternalOptions options) { | 
|  | // Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees | 
|  | // bothers what the tests want to check, such as exact instructions in the body that include | 
|  | // invocation kinds, like virtual call to a bridge. | 
|  | options.enablePropagationOfConstantsAtCallSites = false; | 
|  | // Disable inlining to avoid the (short) tested method from being inlined and then removed. | 
|  | options.enableInlining = false; | 
|  | } | 
|  | } |