Implement proper field resolution order.
The lookup methods on appInfo for fields now always traverse both
field chains (static and instance) and find the first match in the
subtyping chain, as is mandated by the JVM spec. If the looked up
field does not match the requested kind, null is returned.
The tree-shaker, on the other hand, will also mark fields of the
wrong kind live if they are reachable, thus ensuring the correct
runtime semantics (thrown Exception, shadowed other field).
Bug: 69101406
Change-Id: Ie8577c2bfe3aead63a83109468857baf25023ce5
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 fcbd853..c90ac14 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -80,7 +80,7 @@
private DexEncodedMethod lookupDirectStaticOrConstructorTarget(DexMethod method) {
assert method.holder.isClassType();
- return lookupTargetAlongSuperChain(method.holder, method, DexClass::findDirectTarget);
+ return lookupTargetAlongSuperChain(method.holder, method, DexClass::lookupDirectMethod);
}
/**
@@ -118,13 +118,13 @@
public DexEncodedMethod lookupVirtualTarget(DexType type, DexMethod method) {
assert type.isClassType();
DexEncodedMethod result
- = lookupTargetAlongSuperChain(type, method, DexClass::findVirtualTarget);
+ = lookupTargetAlongSuperChain(type, method, DexClass::lookupVirtualMethod);
if (result != null) {
return result;
}
return lookupTargetAlongInterfaceChain(type, method,
(dexClass, dexMethod) -> {
- DexEncodedMethod virtualTarget = dexClass.findVirtualTarget(dexMethod);
+ DexEncodedMethod virtualTarget = dexClass.lookupVirtualMethod(dexMethod);
return virtualTarget != null && virtualTarget.getCode() != null
? virtualTarget
: null;
@@ -140,27 +140,68 @@
public DexEncodedMethod lookupVirtualDefinition(DexType type, DexMethod method) {
assert type.isClassType();
DexEncodedMethod result
- = lookupTargetAlongSuperChain(type, method, DexClass::findVirtualTarget);
+ = lookupTargetAlongSuperChain(type, method, DexClass::lookupVirtualMethod);
if (result != null) {
return result;
}
- return lookupTargetAlongInterfaceChain(type, method, DexClass::findVirtualTarget);
+ return lookupTargetAlongInterfaceChain(type, method, DexClass::lookupVirtualMethod);
}
/**
- * Lookup instance field starting in type and following the super chain.
+ * Lookup instance field starting in type and following the interface and super chain.
+ * <p>
+ * The result is the field that will be hit at runtime, if such field is known. A result
+ * of null indicates that the field is either undefined or not an instance field.
*/
public DexEncodedField lookupInstanceTarget(DexType type, DexField field) {
assert type.isClassType();
- return resolveFieldOn(type, field, false);
+ DexEncodedField result = resolveFieldOn(type, field);
+ return result == null || result.accessFlags.isStatic() ? null : result;
}
/**
* Lookup static field starting in type and following the interface and super chain.
+ * <p>
+ * The result is the field that will be hit at runtime, if such field is known. A result
+ * of null indicates that the field is either undefined or not a static field.
*/
public DexEncodedField lookupStaticTarget(DexType type, DexField field) {
assert type.isClassType();
- return resolveFieldOn(type, field, true);
+ DexEncodedField result = resolveFieldOn(type, field);
+ return result == null || !result.accessFlags.isStatic() ? null : result;
+ }
+
+ /**
+ * 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>.
+ */
+ public DexEncodedField resolveFieldOn(DexType type, DexField desc) {
+ DexClass holder = definitionFor(type);
+ if (holder == null) {
+ return null;
+ }
+ // Step 1: Class declares the field.
+ DexEncodedField result = holder.lookupField(desc);
+ if (result != null) {
+ return result;
+ }
+ // Step 2: Apply recursively to direct superinterfaces. First match succeeds.
+ for (DexType iface : holder.interfaces.values) {
+ result = resolveFieldOn(iface, desc);
+ if (result != null) {
+ return result;
+ }
+ }
+ // Step 3: Apply recursively to superclass.
+ if (holder.superType != null) {
+ result = resolveFieldOn(holder.superType, desc);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
}
/**
@@ -225,45 +266,6 @@
this::lookupTargetAlongSuperAndInterfaceChain);
}
- /**
- * 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 DexEncodedField resolveFieldOn(DexType type, DexField desc, boolean isStatic) {
- DexClass holder = definitionFor(type);
- if (holder == null) {
- return null;
- }
- // Step 1: Class declares the field.
- DexEncodedField result =
- isStatic ? holder.findStaticTarget(desc) : holder.findInstanceTarget(desc);
- 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 = resolveFieldOn(holder.superType, desc, isStatic);
- if (result != null) {
- return result;
- }
- }
- return null;
- }
-
private boolean isDefaultMethod(DexEncodedMethod encodedMethod) {
return encodedMethod != null
&& !encodedMethod.accessFlags.isStatic()
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index ad0f549..7e35766 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -137,7 +137,7 @@
for (DexType type : set) {
DexClass clazz = definitionFor(type);
if (!clazz.isInterface()) {
- DexEncodedMethod t = clazz.findVirtualTarget(method);
+ DexEncodedMethod t = clazz.lookupVirtualMethod(method);
if (t != null) {
result.add(t);
}
@@ -176,7 +176,7 @@
for (DexType type : set) {
DexClass clazz = definitionFor(type);
if (!clazz.isInterface()) {
- DexEncodedMethod t = clazz.findVirtualTarget(method);
+ DexEncodedMethod t = clazz.lookupVirtualMethod(method);
if (t != null) {
if (result != null) {
return null; // We have more than one target method.
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index cb7ff79..d5fc0be 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -178,34 +178,42 @@
}
/**
- * Find direct method in this class matching method
+ * Find static field in this class matching field
*/
- public DexEncodedMethod findDirectTarget(DexMethod method) {
- return findTarget(directMethods(), method);
+ public DexEncodedField lookupStaticField(DexField field) {
+ return lookupTarget(staticFields(), field);
}
/**
- * Find static field in this class matching field
+ * Find instance field in this class matching field.
*/
- public DexEncodedField findStaticTarget(DexField field) {
- return findTarget(staticFields(), field);
+ public DexEncodedField lookupInstanceField(DexField field) {
+ return lookupTarget(instanceFields(), field);
+ }
+
+ /**
+ * Find field in this class matching field.
+ */
+ public DexEncodedField lookupField(DexField field) {
+ DexEncodedField result = lookupInstanceField(field);
+ return result == null ? lookupStaticField(field) : result;
+ }
+
+ /**
+ * Find direct method in this class matching method
+ */
+ public DexEncodedMethod lookupDirectMethod(DexMethod method) {
+ return lookupTarget(directMethods(), method);
}
/**
* Find virtual method in this class matching method
*/
- public DexEncodedMethod findVirtualTarget(DexMethod method) {
- return findTarget(virtualMethods(), method);
+ public DexEncodedMethod lookupVirtualMethod(DexMethod method) {
+ return lookupTarget(virtualMethods(), method);
}
- /**
- * Find instance field in this class matching field
- */
- public DexEncodedField findInstanceTarget(DexField field) {
- return findTarget(instanceFields(), field);
- }
-
- private <T extends DexItem, S extends Descriptor<T, S>> T findTarget(T[] items, S descriptor) {
+ private <T extends DexItem, S extends Descriptor<T, S>> T lookupTarget(T[] items, S descriptor) {
for (T entry : items) {
if (descriptor.match(entry)) {
return entry;
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index ca18978..8c36cb0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -41,6 +41,11 @@
return field;
}
+ /**
+ * Returns the target of this field instruction, if such target is known, or null.
+ * <p>
+ * A result of null indicates that the field is either undefined or not of the right kind.
+ */
abstract DexEncodedField lookupTarget(DexType type, AppInfo appInfo);
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 71d6e46..02a5893 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -207,18 +207,18 @@
public GraphLense run() {
computeMethodRebinding(appInfo.virtualInvokes, this::virtualLookup,
- DexClass::findVirtualTarget, DexProgramClass::addVirtualMethod);
- computeMethodRebinding(appInfo.superInvokes, this::superLookup, DexClass::findVirtualTarget,
+ DexClass::lookupVirtualMethod, DexProgramClass::addVirtualMethod);
+ computeMethodRebinding(appInfo.superInvokes, this::superLookup, DexClass::lookupVirtualMethod,
DexProgramClass::addVirtualMethod);
computeMethodRebinding(appInfo.directInvokes, appInfo::lookupDirectTarget,
- DexClass::findDirectTarget, MemberRebindingAnalysis::privateMethodsCheck);
+ DexClass::lookupDirectMethod, MemberRebindingAnalysis::privateMethodsCheck);
computeMethodRebinding(appInfo.staticInvokes, appInfo::lookupStaticTarget,
- DexClass::findDirectTarget, DexProgramClass::addStaticMethod);
+ DexClass::lookupDirectMethod, DexProgramClass::addStaticMethod);
computeFieldRebinding(Sets.union(appInfo.staticFieldReads, appInfo.staticFieldWrites),
- appInfo::lookupStaticTarget, DexClass::findStaticTarget);
+ appInfo::resolveFieldOn, DexClass::lookupStaticField);
computeFieldRebinding(Sets.union(appInfo.instanceFieldReads, appInfo.instanceFieldWrites),
- appInfo::lookupInstanceTarget, DexClass::findInstanceTarget);
+ appInfo::resolveFieldOn, DexClass::lookupInstanceField);
return builder.build(appInfo.dexItemFactory, lense);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 6f7926c..43b1433 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -50,6 +50,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
@@ -565,13 +566,24 @@
// supertypes as live, so even if the field is actually on a supertype, its class will be live.
markTypeAsLive(field.clazz);
// Find the actual field.
- DexEncodedField encodedField = appInfo.lookupStaticTarget(field.clazz, field);
+ DexEncodedField encodedField = appInfo.resolveFieldOn(field.clazz, field);
if (encodedField == null) {
reportMissingField(field);
return;
}
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Adding static field `%s` to live set.", encodedField.field);
+ // This field might be an instance field reachable from a static context, e.g. a getStatic that
+ // resolves to an instance field. We have to keep the instance field nonetheless, as otherwise
+ // we might unmask a shadowed static field and hence change semantics.
+
+ if (encodedField.accessFlags.isStatic()) {
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Adding static field `%s` to live set.", encodedField.field);
+ }
+ } else {
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Adding instance field `%s` to live set (static context).",
+ encodedField.field);
+ }
}
liveFields.add(encodedField, reason);
// Add all dependent members to the workqueue.
@@ -633,21 +645,29 @@
|| appInfo.subtypes(type).stream().anyMatch(instantiatedTypes::contains);
}
- private void markFieldAsReachable(DexField field, KeepReason reason) {
+ private void markInstanceFieldAsReachable(DexField field, KeepReason reason) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking instance field `%s` as reachable.", field);
}
- DexEncodedField encodedField = appInfo.lookupInstanceTarget(field.clazz, field);
+ DexEncodedField encodedField = appInfo.resolveFieldOn(field.clazz, field);
if (encodedField == null) {
reportMissingField(field);
return;
}
- SetWithReason<DexEncodedField> reachable = reachableInstanceFields
- .computeIfAbsent(encodedField.field.clazz, ignore -> new SetWithReason<>());
- if (reachable.add(encodedField, reason) && isInstantiatedOrHasInstantiatedSubtype(
- encodedField.field.clazz)) {
- // We have at least one live subtype, so mark it as live.
- markInstanceFieldAsLive(encodedField, reason);
+ // We might have a instance field access that is dispatched to a static field. In such case,
+ // we have to keep the static field, so that the dispatch fails at runtime in the same way that
+ // it did before. We have to keep the field even if the receiver has no live inhabitants, as
+ // field resolution happens before the receiver is inspected.
+ if (encodedField.accessFlags.isStatic()) {
+ markStaticFieldAsLive(encodedField.field, reason);
+ } else {
+ SetWithReason<DexEncodedField> reachable = reachableInstanceFields
+ .computeIfAbsent(encodedField.field.clazz, ignore -> new SetWithReason<>());
+ if (reachable.add(encodedField, reason) && isInstantiatedOrHasInstantiatedSubtype(
+ encodedField.field.clazz)) {
+ // We have at least one live subtype, so mark it as live.
+ markInstanceFieldAsLive(encodedField, reason);
+ }
}
}
@@ -717,7 +737,7 @@
DexType current = worklist.pollFirst();
DexClass currentHolder = appInfo.definitionFor(current);
if (currentHolder == null
- || currentHolder.findVirtualTarget(encodedMethod.method) != null) {
+ || currentHolder.lookupVirtualMethod(encodedMethod.method) != null) {
continue;
}
if (instantiatedTypes.contains(current)) {
@@ -808,7 +828,7 @@
processNewlyInstantiatedClass((DexClass) action.target, action.reason);
break;
case MARK_REACHABLE_FIELD:
- markFieldAsReachable((DexField) action.target, action.reason);
+ markInstanceFieldAsReachable((DexField) action.target, action.reason);
break;
case MARK_REACHABLE_VIRTUAL:
markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
@@ -886,7 +906,7 @@
if (target.accessFlags.isStatic()) {
markStaticFieldAsLive(target.field, reason);
} else {
- markFieldAsReachable(target.field, reason);
+ markInstanceFieldAsReachable(target.field, reason);
}
}
@@ -972,18 +992,18 @@
private Set<DexField> collectReachedFields(Map<DexType, Set<DexField>> map,
Function<DexField, DexField> lookup) {
- return map.values().stream().flatMap(set -> set.stream().map(lookup))
+ return map.values().stream().flatMap(set -> set.stream().map(lookup).filter(Objects::nonNull))
.collect(Collectors.toCollection(Sets::newIdentityHashSet));
}
private DexField tryLookupInstanceField(DexField field) {
DexEncodedField target = appInfo.lookupInstanceTarget(field.clazz, field);
- return target == null ? field : target.field;
+ return target == null ? null : target.field;
}
private DexField tryLookupStaticField(DexField field) {
DexEncodedField target = appInfo.lookupStaticTarget(field.clazz, field);
- return target == null ? field : target.field;
+ return target == null ? null : target.field;
}
SortedSet<DexField> collectFieldsRead() {
@@ -1487,17 +1507,17 @@
if (holder == null) {
return false;
}
- DexEncodedField target = holder.findStaticTarget(field);
+ DexEncodedField target = holder.lookupStaticField(field);
if (target != null) {
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target.field == field) {
markStaticFieldAsLive(field, KeepReason.referencedInAnnotation(annotationHolder));
}
} else {
- target = holder.findInstanceTarget(field);
+ target = holder.lookupInstanceField(field);
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target != null && target.field != field) {
- markFieldAsReachable(field, KeepReason.referencedInAnnotation(annotationHolder));
+ markInstanceFieldAsReachable(field, KeepReason.referencedInAnnotation(annotationHolder));
}
}
return false;
@@ -1509,7 +1529,7 @@
if (holder == null) {
return false;
}
- DexEncodedMethod target = holder.findDirectTarget(method);
+ DexEncodedMethod target = holder.lookupDirectMethod(method);
if (target != null) {
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target.method == method) {
@@ -1517,7 +1537,7 @@
target, KeepReason.referencedInAnnotation(annotationHolder));
}
} else {
- target = holder.findVirtualTarget(method);
+ target = holder.lookupVirtualMethod(method);
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target != null && target.method == method) {
markMethodAsTargeted(target, KeepReason.referencedInAnnotation(annotationHolder));
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 3987e87..3fa67af 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.fail;
+import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -80,6 +81,16 @@
return ToolHelper.runR8(builder.build(), optionsConsumer);
}
+ protected AndroidApp compileWithR8(JasminBuilder builder, String proguardConfig,
+ Consumer<InternalOptions> optionsConsumer)
+ throws Exception {
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(builder.build())
+ .addProguardConfiguration(ImmutableList.of(proguardConfig))
+ .build();
+ return ToolHelper.runR8(command, optionsConsumer);
+ }
+
protected String runOnArtR8(JasminBuilder builder, String main) throws Exception {
return runOnArtR8(builder, main, null);
}
@@ -91,6 +102,13 @@
return runOnArt(result, main);
}
+ protected String runOnArtR8(JasminBuilder builder, String main, String proguardConfig,
+ Consumer<InternalOptions> optionsConsumer)
+ throws Exception {
+ AndroidApp result = compileWithR8(builder, proguardConfig, optionsConsumer);
+ return runOnArt(result, main);
+ }
+
protected ProcessResult runOnArtR8Raw(JasminBuilder builder, String main,
Consumer<InternalOptions> optionsConsumer)
throws Exception {
@@ -98,6 +116,13 @@
return runOnArtRaw(result, main);
}
+ protected ProcessResult runOnArtR8Raw(JasminBuilder builder, String main, String proguardConfig,
+ Consumer<InternalOptions> optionsConsumer)
+ throws Exception {
+ AndroidApp result = compileWithR8(builder, proguardConfig, optionsConsumer);
+ return runOnArtRaw(result, main);
+ }
+
private ProcessResult runDx(JasminBuilder builder, File classes, Path dex) throws Exception {
builder.writeClassFiles(classes.toPath());
List<String> args = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index a694a12..fb1672f 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -65,6 +65,30 @@
}
@Test
+ public void lookupStaticFieldWithFieldGetFromNullReference() throws Exception {
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addDefaultConstructor();
+ superClass.addStaticFinalField("aField", "I", "42");
+
+ ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+ subClass.addDefaultConstructor();
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " aconst_null",
+ " getfield SubClass/aField I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ ensureICCE(builder);
+ }
+
+ @Test
public void lookupStaticFieldFromSupersInterfaceNotSupersSuper() throws Exception {
JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
@@ -91,7 +115,6 @@
}
@Test
- @Ignore("b/69101406")
public void lookupInstanceFieldWithShadowingStatic() throws Exception {
JasminBuilder builder = new JasminBuilder();
@@ -121,6 +144,30 @@
}
@Test
+ public void lookupStaticFieldWithShadowingInstance() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ ClassBuilder superClass = builder.addClass("SuperClass");
+ superClass.addStaticField("aField", "I", "42");
+ superClass.addDefaultConstructor();
+
+ ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+ subClass.addField("public", "aField", "I", "123");
+ subClass.addDefaultConstructor();
+
+ ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+ mainClass.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " getstatic SubClass/aField I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ ensureICCE(builder);
+ }
+
+ @Test
@Ignore("b/69101406")
public void lookupVirtualMethodWithConflictingPrivate() throws Exception {
JasminBuilder builder = new JasminBuilder();
@@ -195,6 +242,7 @@
}
@Test
+ @Ignore("b/69101406")
public void lookupPrivateSuperFromSubClass() throws Exception {
JasminBuilder builder = new JasminBuilder(ClassFileVersion.JSE_5);
@@ -305,13 +353,16 @@
}
private void ensureSameOutput(JasminBuilder app) throws Exception {
+ String javaOutput = runOnJava(app, MAIN_CLASS);
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);
+ String r8ShakenOutput = runOnArtR8(app, MAIN_CLASS, keepMainProguardConfiguration(MAIN_CLASS),
+ null);
+ Assert.assertEquals(javaOutput, dxOutput);
+ Assert.assertEquals(javaOutput, d8Output);
Assert.assertEquals(javaOutput, r8Output);
+ Assert.assertEquals(javaOutput, r8ShakenOutput);
}
private void ensureICCE(JasminBuilder app) throws Exception {
@@ -331,6 +382,9 @@
Assert.assertTrue(d8Output.stderr.contains(name));
ProcessResult r8Output = runOnArtR8Raw(app, MAIN_CLASS, null);
Assert.assertTrue(r8Output.stderr.contains(name));
+ ProcessResult r8ShakenOutput = runOnArtR8Raw(app, MAIN_CLASS,
+ keepMainProguardConfiguration(MAIN_CLASS), null);
+ Assert.assertTrue(r8ShakenOutput.stderr.contains(name));
ProcessResult javaOutput = runOnJavaNoVerifyRaw(app, MAIN_CLASS);
Assert.assertTrue(javaOutput.stderr.contains(name));
}