Merge "One step closer to actual JVM field resolution."
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 62c5b7b..fcbd853 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -152,15 +152,15 @@
*/
public DexEncodedField lookupInstanceTarget(DexType type, DexField field) {
assert type.isClassType();
- return lookupTargetAlongSuperChain(type, field, DexClass::findInstanceTarget);
+ return resolveFieldOn(type, field, false);
}
/**
- * Lookup static field starting in type and following the super chain.
+ * Lookup static field starting in type and following the interface and super chain.
*/
public DexEncodedField lookupStaticTarget(DexType type, DexField field) {
assert type.isClassType();
- return lookupTargetAlongInterfaceAndSuperChain(type, field, DexClass::findStaticTarget);
+ return resolveFieldOn(type, field, true);
}
/**
@@ -226,27 +226,37 @@
}
/**
- * Traverse along the super chain and the interface chains until lookup returns non-null value.
+ * Implements resolution of a field descriptor against a type.
+ * <p>
+ * See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.2">
+ * Section 5.4.3.2 of the JVM Spec</a>.
*/
- private <S extends DexItem, T extends Descriptor<S, T>> S lookupTargetAlongInterfaceAndSuperChain(
- DexType type,
- T desc,
- BiFunction<DexClass, T, S> lookup) {
+ private DexEncodedField resolveFieldOn(DexType type, DexField desc, boolean isStatic) {
DexClass holder = definitionFor(type);
if (holder == null) {
return null;
}
- S result = lookup.apply(holder, desc);
+ // Step 1: Class declares the field.
+ DexEncodedField result =
+ isStatic ? holder.findStaticTarget(desc) : holder.findInstanceTarget(desc);
if (result != null) {
return result;
}
- result = applyToInterfacesAndDisambiguate(holder.interfaces.values, desc, lookup,
- this::lookupTargetAlongSuperAndInterfaceChain);
- if (result != null) {
- return result;
+ // Step 2: Apply recursively to direct superinterfaces. First match succeeds.
+ //
+ // We short-cut here, as we know that interfaces cannot have instance fields.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.5.
+ if (isStatic) {
+ for (DexType iface : holder.interfaces.values) {
+ result = resolveFieldOn(iface, desc, isStatic);
+ if (result != null) {
+ return result;
+ }
+ }
}
+ // Step 3: Apply recursively to superclass.
if (holder.superType != null) {
- result = lookupTargetAlongInterfaceAndSuperChain(holder.superType, desc, lookup);
+ result = resolveFieldOn(holder.superType, desc, isStatic);
if (result != null) {
return result;
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index caada82..94f1deb 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -29,17 +29,26 @@
public static class ClassBuilder {
public final String name;
public final String superName;
+ public final ImmutableList<String> interfaces;
private final List<String> methods = new ArrayList<>();
private final List<String> fields = new ArrayList<>();
private boolean makeInit = false;
+ private boolean isInterface = false;
public ClassBuilder(String name) {
this(name, "java/lang/Object");
}
- public ClassBuilder(String name, String superName) {
+ private ClassBuilder(String name, String superName) {
this.name = name;
this.superName = superName;
+ this.interfaces = ImmutableList.of();
+ }
+
+ private ClassBuilder(String name, String superName, String... interfaces) {
+ this.name = name;
+ this.superName = superName;
+ this.interfaces = ImmutableList.copyOf(interfaces);
}
public String getSourceFile() {
@@ -121,9 +130,17 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(".source ").append(getSourceFile()).append("\n");
- builder.append(".class public ").append(name).append("\n");
- builder.append(".super ").append(superName).append("\n");
+ builder.append(".source ").append(getSourceFile()).append('\n');
+ if (isInterface) {
+ builder.append(".interface");
+ } else {
+ builder.append(".class");
+ }
+ builder.append(" public ").append(name).append('\n');
+ builder.append(".super ").append(superName).append('\n');
+ for (String iface : interfaces) {
+ builder.append(".implements ").append(iface).append('\n');
+ }
if (makeInit) {
builder
.append(".method public <init>()V\n")
@@ -142,6 +159,19 @@
}
return builder.toString();
}
+
+ void setIsInterface() {
+ isInterface = true;
+ }
+
+ public MethodSignature addDefaultConstructor() {
+ return addMethod("public", "<init>", Collections.emptyList(), "V",
+ ".limit stack 1",
+ ".limit locals 1",
+ " aload_0",
+ " invokenonvirtual " + superName + "/<init>()V",
+ " return");
+ }
}
private final List<ClassBuilder> classes = new ArrayList<>();
@@ -160,6 +190,19 @@
return builder;
}
+ public ClassBuilder addClass(String name, String superName, String... interfaces) {
+ ClassBuilder builder = new ClassBuilder(name, superName, interfaces);
+ classes.add(builder);
+ return builder;
+ }
+
+ public ClassBuilder addInterface(String name, String... interfaces) {
+ ClassBuilder builder = new ClassBuilder(name, "java/lang/Object", interfaces);
+ builder.setIsInterface();
+ classes.add(builder);
+ return builder;
+ }
+
public ImmutableList<ClassBuilder> getClasses() {
return ImmutableList.copyOf(classes);
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 5579380..6d4a501 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -87,6 +87,10 @@
return runOnArt(compileWithD8(builder), main);
}
+ protected ProcessResult runOnArtD8Raw(JasminBuilder builder, String main) throws Exception {
+ return runOnArtRaw(compileWithD8(builder), main);
+ }
+
protected AndroidApp compileWithR8(JasminBuilder builder) throws Exception {
return compileWithR8(builder, null);
}
@@ -108,6 +112,13 @@
return runOnArt(result, main);
}
+ protected ProcessResult runOnArtR8Raw(JasminBuilder builder, String main,
+ Consumer<InternalOptions> optionsConsumer)
+ throws Exception {
+ AndroidApp result = compileWithR8(builder, optionsConsumer);
+ return runOnArtRaw(result, main);
+ }
+
private ProcessResult runDx(JasminBuilder builder, File classes, Path dex) throws Exception {
for (ClassBuilder clazz : builder.getClasses()) {
ClassFile file = new ClassFile();
@@ -124,7 +135,7 @@
return ToolHelper.runDX(args.toArray(new String[args.size()]));
}
- protected ProcessResult runOnArtDxRaw(JasminBuilder builder) throws Exception {
+ protected ProcessResult runDX(JasminBuilder builder) throws Exception {
return runDx(builder, temp.newFolder("classes_for_dx"),
temp.getRoot().toPath().resolve("classes.dex"));
}
@@ -142,6 +153,19 @@
return ToolHelper.runArtNoVerificationErrors(dex.toString(), main);
}
+ protected ProcessResult runOnArtDxRaw(JasminBuilder builder, String main) throws Exception {
+ Path dex = temp.getRoot().toPath().resolve("classes.dex");
+ ProcessResult result = runDx(builder, temp.newFolder("classes_for_dx"), dex);
+ if (result.exitCode != 0) {
+ System.out.println("Std out:");
+ System.out.println(result.stdout);
+ System.out.println("Std err:");
+ System.out.println(result.stderr);
+ assertEquals(0, result.exitCode);
+ }
+ return ToolHelper.runArtRaw(dex.toString(), main);
+ }
+
protected ProcessResult runOnArtRaw(AndroidApp app, String main) throws IOException {
Path out = temp.getRoot().toPath().resolve("out.zip");
app.writeToZip(out, OutputMode.Indexed);
@@ -154,14 +178,6 @@
return ToolHelper.runArtNoVerificationErrors(out.toString(), main);
}
- protected AndroidApp buildApplication(JasminBuilder builder) {
- try {
- return AndroidApp.fromClassProgramData(builder.buildClasses());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
protected DexEncodedMethod getMethod(AndroidApp application, String clazz,
MethodSignature signature) {
return getMethod(application,
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index 779fea0..c07f125 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -30,7 +30,7 @@
// Uncaught translation error: com.android.dex.util.ExceptionWithContext: returning from
// invalid subroutine
// 1 error; aborting
- ProcessResult result = runOnArtDxRaw(builder);
+ ProcessResult result = runDX(builder);
assertNotEquals(0, result.exitCode);
assertTrue(result.stderr.contains("Uncaught translation error"));
assertTrue(result.stderr.contains("invalid subroutine"));
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
new file mode 100644
index 0000000..8f32184
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2017, 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.jasmin;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class MemberResolutionTest extends JasminTestBase {
+
+ private static final String MAIN_CLASS = "Main";
+
+ @Test
+ public void lookupStaticFieldFromDiamondInterface() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder interfaceA = builder.addInterface("InterfaceA");
+ interfaceA.addStaticFinalField("aField", "I", "42");
+
+ ClassBuilder interfaceB = builder.addInterface("InterfaceB");
+ interfaceB.addStaticFinalField("aField", "I", "123");
+
+ builder.addInterface("SubInterface", "InterfaceB", "InterfaceA");
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " getstatic SubInterface/aField I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ ensureSameOutput(builder);
+ }
+
+ @Test
+ public void lookupStaticFieldFromInterfaceNotSuper() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addStaticFinalField("aField", "I", "42");
+
+ ClassBuilder iface = builder.addInterface("Interface");
+ iface.addStaticFinalField("aField", "I", "123");
+
+ builder.addClass("SubClass", "SuperClass", "Interface");
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " getstatic SubClass/aField I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ ensureSameOutput(builder);
+ }
+
+ @Test
+ public void lookupStaticFieldFromSupersInterfaceNotSupersSuper() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder superSuperClass = builder.addClass("SuperSuperClass");
+ superSuperClass.addStaticFinalField("aField", "I", "123");
+
+ ClassBuilder iface = builder.addInterface("Interface");
+ iface.addStaticFinalField("aField", "I", "42");
+
+ builder.addClass("SuperClass", "SuperSuperClass", "Interface");
+
+ builder.addClass("SubClass", "SuperClass");
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " getstatic SubClass/aField I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ ensureSameOutput(builder);
+ }
+
+ @Test
+ @Ignore("b/69101406")
+ public void lookupInstanceFieldWithShadowingStatic() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addField("public", "aField", "I", "42");
+ superClass.addDefaultConstructor();
+
+ ClassBuilder interfaceA = builder.addInterface("Interface");
+ interfaceA.addStaticFinalField("aField", "I", "123");
+
+ ClassBuilder subClass = builder.addClass("SubClass", "SuperClass", "Interface");
+ subClass.addDefaultConstructor();
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " new SubClass",
+ " dup",
+ " invokespecial SubClass/<init>()V",
+ " getfield SubClass/aField I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ ensureICCE(builder);
+ }
+
+
+ private void ensureSameOutput(JasminBuilder app) throws Exception {
+ String dxOutput = runOnArtDx(app, MAIN_CLASS);
+ String d8Output = runOnArtD8(app, MAIN_CLASS);
+ Assert.assertEquals(dxOutput, d8Output);
+ String r8Output = runOnArtR8(app, MAIN_CLASS);
+ Assert.assertEquals(dxOutput, r8Output);
+ String javaOutput = runOnJava(app, MAIN_CLASS);
+ Assert.assertEquals(javaOutput, r8Output);
+ }
+
+ private void ensureICCE(JasminBuilder app) throws Exception {
+ ProcessResult dxOutput = runOnArtDxRaw(app, MAIN_CLASS);
+ Assert.assertTrue(dxOutput.stderr.contains("IncompatibleClassChangeError"));
+ ProcessResult d8Output = runOnArtD8Raw(app, MAIN_CLASS);
+ Assert.assertTrue(d8Output.stderr.contains("IncompatibleClassChangeError"));
+ ProcessResult r8Output = runOnArtR8Raw(app, MAIN_CLASS, null);
+ Assert.assertTrue(r8Output.stderr.contains("IncompatibleClassChangeError"));
+ ProcessResult javaOutput = runOnJavaRaw(app, MAIN_CLASS);
+ Assert.assertTrue(javaOutput.stderr.contains("IncompatibleClassChangeError"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
index 4919b9e..df0d6e2 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
@@ -56,7 +56,7 @@
String expected = runOnJava(builder, clazz.name);
- AndroidApp originalApplication = buildApplication(builder);
+ AndroidApp originalApplication = builder.build();
AndroidApp processedApplication = ToolHelper.runR8(originalApplication);
DexEncodedMethod method = getMethod(processedApplication, clazz.name, signature);