blob: 289a0ba691853027e957b43d0aaceaf367ce3233 [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.assertNotNull;
import static org.junit.Assert.assertNull;
import com.android.tools.r8.CompilationFailedException;
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;
public class TargetLookupTest extends SmaliTestBase {
@Test
public void lookupDirect() {
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);
AppInfo appInfo = getAppInfo(application);
CodeInspector inspector = new CodeInspector(appInfo.app());
DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
ImmutableList.of());
assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method));
assertNull(appInfo.lookupDirectTarget(method.method));
assertNotNull(appInfo.lookupStaticTarget(method.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() {
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);
AppInfo appInfo = getAppInfo(application);
CodeInspector inspector = new CodeInspector(appInfo.app());
DexMethod methodXOnTestSuper =
getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()).method;
DexMethod methodYOnTest =
getMethod(inspector, "Test", "int", "y", ImmutableList.of()).method;
DexType classTestSuper = methodXOnTestSuper.holder;
DexType classTest = methodYOnTest.holder;
DexProto methodXProto = methodXOnTestSuper.proto;
DexString methodXName = methodXOnTestSuper.name;
DexMethod methodXOnTest =
appInfo.dexItemFactory().createMethod(classTest, methodXProto, methodXName);
assertNull(appInfo.lookupVirtualTarget(classTestSuper, methodXOnTestSuper));
assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTestSuper));
assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTest));
assertNull(appInfo.lookupDirectTarget(methodXOnTestSuper));
assertNull(appInfo.lookupDirectTarget(methodXOnTest));
assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper));
// Accessing a private target on a different type will fail resolution outright.
assertNull(appInfo.lookupStaticTarget(methodXOnTest));
assertEquals("OK", runArt(application));
}
@Test
public void lookupFieldWithDefaultInInterface() throws CompilationFailedException {
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);
AppInfo appInfo = getAppInfo(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.holder, 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();
AppInfo appInfo = getAppInfo(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;");
DexType c1 = factory.createType("L" + pkg + "/C1;");
DexType c2 = 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, 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);
assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC1, c2).method);
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()));
}
}