blob: 7d227824062814e4f60683c4d21f9fd83c17af95 [file] [log] [blame]
// 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).method);
assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).method);
assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).method);
assertNull(appInfo.lookupSuperTarget(mOnC1, c2)); // C2 is not a subclass of C1.
assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).method);
assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).method);
// 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()));
}
}