|  | // Copyright (c) 2016, 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.graph; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertFalse; | 
|  | import static org.junit.Assert.assertNotNull; | 
|  | import static org.junit.Assert.assertNull; | 
|  |  | 
|  | import com.android.tools.r8.TestParameters; | 
|  | import com.android.tools.r8.TestParametersCollection; | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.ToolHelper.DexVm; | 
|  | import com.android.tools.r8.ToolHelper.ProcessResult; | 
|  | import com.android.tools.r8.graph.invokesuper2.C0; | 
|  | import com.android.tools.r8.graph.invokesuper2.C1; | 
|  | import com.android.tools.r8.graph.invokesuper2.C2; | 
|  | import com.android.tools.r8.graph.invokesuper2.I0; | 
|  | import com.android.tools.r8.graph.invokesuper2.I1; | 
|  | import com.android.tools.r8.graph.invokesuper2.I2; | 
|  | import com.android.tools.r8.graph.invokesuper2.I3; | 
|  | import com.android.tools.r8.graph.invokesuper2.I4; | 
|  | import com.android.tools.r8.graph.invokesuper2.Main; | 
|  | import com.android.tools.r8.smali.SmaliBuilder; | 
|  | import com.android.tools.r8.smali.SmaliTestBase; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import java.nio.file.Path; | 
|  | import java.util.Collections; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.Parameterized; | 
|  | import org.junit.runners.Parameterized.Parameters; | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class TargetLookupTest extends SmaliTestBase { | 
|  |  | 
|  | @Parameters(name = "{0}") | 
|  | public static TestParametersCollection data() { | 
|  | return getTestParameters().withNoneRuntime().build(); | 
|  | } | 
|  |  | 
|  | public TargetLookupTest(TestParameters parameters) { | 
|  | parameters.assertNoneRuntime(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void lookupDirect() throws Exception { | 
|  | SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); | 
|  |  | 
|  | builder.addDefaultConstructor(); | 
|  |  | 
|  | builder.addMethodRaw( | 
|  | "  .method private static x()I", | 
|  | "    .locals 1", | 
|  | "    const v0, 0", | 
|  | "    return v0", | 
|  | "  .end method" | 
|  | ); | 
|  |  | 
|  | // Instance method invoking static method using invoke-direct. This does not run on Art, but | 
|  | // results in an IncompatibleClassChangeError. | 
|  | builder.addMethodRaw( | 
|  | "  .method public y()I", | 
|  | "    .locals 1", | 
|  | "    invoke-direct       {p0}, " + builder.getCurrentClassDescriptor() + "->x()I", | 
|  | "    move-result         v0", | 
|  | "    return              v0", | 
|  | "  .end method" | 
|  | ); | 
|  |  | 
|  | builder.addMainMethod( | 
|  | 2, | 
|  | "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", | 
|  | "    new-instance        v1, LTest;", | 
|  | "    invoke-direct       {v1}, " + builder.getCurrentClassDescriptor() + "-><init>()V", | 
|  | "    :try_start", | 
|  | "    invoke-virtual      {v1}, " + builder.getCurrentClassDescriptor() + "->y()I", | 
|  | "    :try_end", | 
|  | "    const-string        v1, \"ERROR\"", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V", | 
|  | "    :return", | 
|  | "    return-void", | 
|  | "    .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :catch", | 
|  | "    :catch", | 
|  | "    const-string        v1, \"OK\"", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V", | 
|  | "    goto :return" | 
|  | ); | 
|  |  | 
|  | AndroidApp application = buildApplication(builder); | 
|  | AppInfoWithClassHierarchy appInfo = computeAppInfoWithClassHierarchy(application); | 
|  | CodeInspector inspector = new CodeInspector(appInfo.app()); | 
|  | ProgramMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x", ImmutableList.of()); | 
|  | assertFalse( | 
|  | appInfo.resolveMethodOnClass(method.getReference()).getSingleTarget().isVirtualMethod()); | 
|  | assertNull(appInfo.lookupDirectTarget(method.getReference(), method)); | 
|  | assertNotNull(appInfo.lookupStaticTarget(method.getReference(), method)); | 
|  |  | 
|  | if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(DexVm.Version.V4_4_4)) { | 
|  | // Dalvik rejects at verification time instead of producing the | 
|  | // expected IncompatibleClassChangeError. | 
|  | try { | 
|  | runArt(application); | 
|  | } catch (AssertionError e) { | 
|  | assert e.toString().contains("VerifyError"); | 
|  | } | 
|  | } else { | 
|  | assertEquals("OK", runArt(application)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void lookupDirectSuper() throws Exception { | 
|  | SmaliBuilder builder = new SmaliBuilder("TestSuper"); | 
|  |  | 
|  | builder.addDefaultConstructor(); | 
|  |  | 
|  | builder.addMethodRaw( | 
|  | "  .method private static x()I", | 
|  | "    .locals 1", | 
|  | "    const               v0, 0", | 
|  | "    return              v0", | 
|  | "  .end method" | 
|  | ); | 
|  |  | 
|  | builder.addClass("Test", "TestSuper"); | 
|  |  | 
|  | builder.addDefaultConstructor(); | 
|  |  | 
|  | // Instance method invoking static method in superclass using invoke-direct. This does not run | 
|  | // on Art, but results in an IncompatibleClassChangeError. | 
|  | builder.addMethodRaw( | 
|  | "  .method public y()I", | 
|  | "    .locals 1", | 
|  | "    invoke-direct       {p0}, " + builder.getCurrentClassDescriptor() + "->x()I", | 
|  | "    move-result         v0", | 
|  | "    return              v0", | 
|  | "  .end method" | 
|  | ); | 
|  |  | 
|  | builder.addMainMethod( | 
|  | 2, | 
|  | "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", | 
|  | "    new-instance        v1, LTest;", | 
|  | "    invoke-direct       {v1}, " + builder.getCurrentClassDescriptor() + "-><init>()V", | 
|  | "    :try_start", | 
|  | "    invoke-virtual      {v1}, " + builder.getCurrentClassDescriptor() + "->y()I", | 
|  | "    :try_end", | 
|  | "    const-string        v1, \"ERROR\"", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V", | 
|  | "    :return", | 
|  | "    return-void", | 
|  | "    .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :catch", | 
|  | "    :catch", | 
|  | "    const-string        v1, \"OK\"", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V", | 
|  | "    goto :return" | 
|  | ); | 
|  |  | 
|  | AndroidApp application = buildApplication(builder); | 
|  | AppInfoWithClassHierarchy appInfo = computeAppInfoWithClassHierarchy(application); | 
|  | CodeInspector inspector = new CodeInspector(appInfo.app()); | 
|  |  | 
|  | ProgramMethod methodXOnTestSuper = | 
|  | getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()); | 
|  | ProgramMethod methodYOnTest = getMethod(inspector, "Test", "int", "y", ImmutableList.of()); | 
|  |  | 
|  | DexType classTestSuper = methodXOnTestSuper.getHolderType(); | 
|  | DexType classTest = methodYOnTest.getHolderType(); | 
|  | DexProto methodXProto = methodXOnTestSuper.getReference().proto; | 
|  | DexString methodXName = methodXOnTestSuper.getReference().name; | 
|  | DexMethod methodXOnTestReference = | 
|  | appInfo.dexItemFactory().createMethod(classTest, methodXProto, methodXName); | 
|  |  | 
|  | assertFalse( | 
|  | appInfo | 
|  | .resolveMethodOnClass(methodXOnTestSuper.getReference(), classTestSuper) | 
|  | .getSingleTarget() | 
|  | .isVirtualMethod()); | 
|  | assertNull( | 
|  | appInfo | 
|  | .resolveMethodOnClass(methodXOnTestSuper.getReference(), classTest) | 
|  | .getSingleTarget()); | 
|  | assertNull(appInfo.resolveMethodOnClass(methodXOnTestReference, classTest).getSingleTarget()); | 
|  |  | 
|  | assertNull(appInfo.lookupDirectTarget(methodXOnTestSuper.getReference(), methodXOnTestSuper)); | 
|  | assertNull(appInfo.lookupDirectTarget(methodXOnTestReference, methodYOnTest)); | 
|  |  | 
|  | assertNotNull( | 
|  | appInfo.lookupStaticTarget(methodXOnTestSuper.getReference(), methodXOnTestSuper)); | 
|  | // Accessing a private target on a different type will fail resolution outright. | 
|  | assertNull(appInfo.lookupStaticTarget(methodXOnTestReference, methodYOnTest)); | 
|  |  | 
|  | assertEquals("OK", runArt(application)); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void lookupFieldWithDefaultInInterface() throws Exception { | 
|  | SmaliBuilder builder = new SmaliBuilder(); | 
|  |  | 
|  | builder.addInterface("Interface"); | 
|  | builder.addStaticField("aField", "I", "42"); | 
|  |  | 
|  | builder.addClass("SuperClass"); | 
|  | builder.addStaticField("aField", "I", "123"); | 
|  |  | 
|  | builder.addClass("SubClass", "SuperClass", Collections.singletonList("Interface")); | 
|  |  | 
|  | builder.addClass(DEFAULT_CLASS_NAME); | 
|  | builder.addMainMethod(2, | 
|  | "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", | 
|  | "    sget                v1, LSubClass;->aField:I", | 
|  | "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V", | 
|  | "    return-void" | 
|  | ); | 
|  |  | 
|  | AndroidApp application = buildApplication(builder); | 
|  | AppInfoWithClassHierarchy appInfo = computeAppInfoWithClassHierarchy(application); | 
|  | DexItemFactory factory = appInfo.dexItemFactory(); | 
|  |  | 
|  | DexField aFieldOnSubClass = factory | 
|  | .createField(factory.createType("LSubClass;"), factory.intType, "aField"); | 
|  | DexField aFieldOnInterface = factory | 
|  | .createField(factory.createType("LInterface;"), factory.intType, "aField"); | 
|  |  | 
|  | assertEquals(aFieldOnInterface, appInfo.lookupStaticTarget(aFieldOnSubClass).field); | 
|  |  | 
|  | assertEquals("42", runArt(application)); | 
|  |  | 
|  | AndroidApp processedApp = processApplication(application); | 
|  | assertEquals("42", runArt(processedApp)); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testLookupSuperTarget() throws Exception { | 
|  | String pkg = Main.class.getPackage().getName().replace('.', '/'); | 
|  |  | 
|  | AndroidApp.Builder builder = AndroidApp.builder(); | 
|  | for (Class clazz : new Class[]{ | 
|  | I0.class, I1.class, I2.class, I3.class, I4.class, | 
|  | C0.class, C1.class, C2.class, | 
|  | Main.class}) { | 
|  | builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)); | 
|  | // At least java.lang.Object is needed as interface method lookup have special handling | 
|  | // of methods on java.lang.Object. | 
|  | builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar()); | 
|  | } | 
|  | AndroidApp application = builder.build(); | 
|  | AppInfoWithClassHierarchy appInfo = computeAppInfoWithClassHierarchy(application); | 
|  | DexItemFactory factory = appInfo.dexItemFactory(); | 
|  |  | 
|  | DexType i0 = factory.createType("L" + pkg + "/I0;"); | 
|  | DexType i1 = factory.createType("L" + pkg + "/I1;"); | 
|  | DexType i2 = factory.createType("L" + pkg + "/I2;"); | 
|  | DexType i3 = factory.createType("L" + pkg + "/I3;"); | 
|  | DexType i4 = factory.createType("L" + pkg + "/I4;"); | 
|  | DexType c0 = factory.createType("L" + pkg + "/C0;"); | 
|  | DexProgramClass c1 = appInfo.definitionForProgramType(factory.createType("L" + pkg + "/C1;")); | 
|  | DexProgramClass c2 = appInfo.definitionForProgramType(factory.createType("L" + pkg + "/C2;")); | 
|  |  | 
|  | DexProto mProto = factory.createProto(factory.intType); | 
|  | DexString m = factory.createString("m"); | 
|  | DexMethod mOnC0 = factory.createMethod(c0, mProto, m); | 
|  | DexMethod mOnC1 = factory.createMethod(c1.type, mProto, m); | 
|  | DexMethod mOnI0 = factory.createMethod(i0, mProto, m); | 
|  | DexMethod mOnI1 = factory.createMethod(i1, mProto, m); | 
|  | DexMethod mOnI2 = factory.createMethod(i2, mProto, m); | 
|  | DexMethod mOnI3 = factory.createMethod(i3, mProto, m); | 
|  | DexMethod mOnI4 = factory.createMethod(i4, mProto, m); | 
|  |  | 
|  | assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC0, c1).getReference()); | 
|  | assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).getReference()); | 
|  | assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).getReference()); | 
|  |  | 
|  | assertNull(appInfo.lookupSuperTarget(mOnC1, c2)); // C2 is not a subclass of C1. | 
|  | assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).getReference()); | 
|  | assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).getReference()); | 
|  |  | 
|  | // Copy classes to run on the Java VM. | 
|  | Path out = temp.newFolder().toPath(); | 
|  | copyTestClasses(out, I0.class, I1.class, I2.class, I3.class, I4.class); | 
|  | copyTestClasses(out, C0.class, C1.class, C2.class, Main.class); | 
|  | ProcessResult result = ToolHelper.runJava(out, Main.class.getCanonicalName()); | 
|  | assertEquals(0, result.exitCode); | 
|  |  | 
|  | // Process the application and expect the same result on Art. | 
|  | AndroidApp processedApp = processApplication(application); | 
|  | assertEquals(result.stdout, runArt(processedApp, Main.class.getCanonicalName())); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  |