Merge "Revert "Move invocation type mapping to GraphLense""
diff --git a/build.gradle b/build.gradle
index 7f41caa..7761e37 100644
--- a/build.gradle
+++ b/build.gradle
@@ -501,6 +501,9 @@
task.relocate('org.apache.commons', 'com.android.tools.r8.org.apache.commons')
task.relocate('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm')
task.relocate('it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil')
+ task.relocate('kotlin', 'com.android.tools.r8.jetbrains.kotlin')
+ task.relocate('kotlinx', 'com.android.tools.r8.jetbrains.kotlinx')
+ task.relocate('org.jetbrains', 'com.android.tools.r8.org.jetbrains')
}
task repackageDeps(type: ShadowJar) {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 096de5a..2c12a90 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -99,6 +99,25 @@
return this instanceof IdentityGraphLense;
}
+ public boolean assertNotModified(Iterable<DexItem> items) {
+ for (DexItem item : items) {
+ if (item instanceof DexClass) {
+ DexType type = ((DexClass) item).type;
+ assert lookupType(type) == type;
+ } else if (item instanceof DexEncodedMethod) {
+ DexEncodedMethod method = (DexEncodedMethod) item;
+ // We allow changes to bridge methods as these get retargeted even if they are kept.
+ assert method.accessFlags.isBridge() || lookupMethod(method.method) == method.method;
+ } else if (item instanceof DexEncodedField) {
+ DexField field = ((DexEncodedField) item).field;
+ assert lookupField(field) == field;
+ } else {
+ assert false;
+ }
+ }
+ return true;
+ }
+
private static class IdentityGraphLense extends GraphLense {
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 0e4be8b..4c0be09 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
@@ -73,6 +74,13 @@
if (current.isInvokeCustom()) {
InvokeCustom invokeCustom = current.asInvokeCustom();
DexCallSite callSite = invokeCustom.getCallSite();
+ DexType[] newParameters = new DexType[callSite.methodProto.parameters.size()];
+ for (int i = 0; i < callSite.methodProto.parameters.size(); i++) {
+ newParameters[i] = graphLense.lookupType(callSite.methodProto.parameters.values[i]);
+ }
+ DexProto newMethodProto =
+ appInfo.dexItemFactory.createProto(
+ graphLense.lookupType(callSite.methodProto.returnType), newParameters);
DexMethodHandle newBootstrapMethod = rewriteDexMethodHandle(method,
callSite.bootstrapMethod);
List<DexValue> newArgs = callSite.bootstrapArgs.stream().map(
@@ -85,10 +93,12 @@
})
.collect(Collectors.toList());
- if (newBootstrapMethod != callSite.bootstrapMethod
+ if (!newMethodProto.equals(callSite.methodProto)
+ || newBootstrapMethod != callSite.bootstrapMethod
|| !newArgs.equals(callSite.bootstrapArgs)) {
- DexCallSite newCallSite = appInfo.dexItemFactory.createCallSite(
- callSite.methodName, callSite.methodProto, newBootstrapMethod, newArgs);
+ DexCallSite newCallSite =
+ appInfo.dexItemFactory.createCallSite(
+ callSite.methodName, newMethodProto, newBootstrapMethod, newArgs);
InvokeCustom newInvokeCustom = new InvokeCustom(newCallSite, invokeCustom.outValue(),
invokeCustom.inValues());
iterator.replaceCurrentInstruction(newInvokeCustom);
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 dae98ad..d42c68b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1659,18 +1659,18 @@
this.staticInvokes = rewriteMethodsConservatively(previous.staticInvokes, lense);
this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
// TODO(herhut): Migrate these to Descriptors, as well.
- assert assertNotModifiedByLense(previous.noSideEffects.keySet(), lense);
+ assert lense.assertNotModified(previous.noSideEffects.keySet());
this.noSideEffects = previous.noSideEffects;
- assert assertNotModifiedByLense(previous.assumedValues.keySet(), lense);
+ assert lense.assertNotModified(previous.assumedValues.keySet());
this.assumedValues = previous.assumedValues;
- assert assertNotModifiedByLense(previous.alwaysInline, lense);
+ assert lense.assertNotModified(previous.alwaysInline);
this.alwaysInline = previous.alwaysInline;
this.identifierNameStrings =
rewriteMixedItemsConservatively(previous.identifierNameStrings, lense);
// Switchmap classes should never be affected by renaming.
- assert assertNotModifiedByLense(
+ assert lense.assertNotModified(
previous.switchMaps.keySet().stream().map(this::definitionFor).filter(Objects::nonNull)
- .collect(Collectors.toList()), lense);
+ .collect(Collectors.toList()));
this.switchMaps = previous.switchMaps;
this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
this.protoLiteFields = previous.protoLiteFields;
@@ -1735,27 +1735,6 @@
return true;
}
- private boolean assertNotModifiedByLense(Iterable<DexItem> items, GraphLense lense) {
- for (DexItem item : items) {
- if (item instanceof DexClass) {
- DexType type = ((DexClass) item).type;
- assert lense.lookupType(type) == type;
- } else if (item instanceof DexEncodedMethod) {
- DexEncodedMethod method = (DexEncodedMethod) item;
- // We only allow changes to bridge methods, as these get retargeted even if they
- // are kept.
- assert method.accessFlags.isBridge()
- || lense.lookupMethod(method.method) == method.method;
- } else if (item instanceof DexEncodedField) {
- DexField field = ((DexEncodedField) item).field;
- assert lense.lookupField(field) == field;
- } else {
- assert false;
- }
- }
- return true;
- }
-
private SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
return joinInvokedMethods(invokes, Function.identity());
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index c737055..b11e33d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
@@ -95,8 +96,7 @@
extractPinnedItems(appInfo.pinnedItems, pinnedTypes);
// TODO(christofferqa): Remove the invariant that the graph lense should not modify any
- // methods from the sets alwaysInline and noSideEffects (see use of assertNotModifiedBy-
- // Lense).
+ // methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
extractPinnedItems(appInfo.alwaysInline, pinnedTypes);
extractPinnedItems(appInfo.noSideEffects.keySet(), pinnedTypes);
@@ -138,36 +138,41 @@
private void extractPinnedItems(Iterable<DexItem> items, Set<DexType> pinnedTypes) {
for (DexItem item : items) {
- // Note: Nothing to do for the case where item is a DexType, since we check for this case
- // using appInfo.isPinned.
- if (item instanceof DexField) {
- // Pin the type of the field.
- DexField field = (DexField) item;
- DexClass clazz = appInfo.definitionFor(field.type);
- if (clazz != null && clazz.isProgramClass()) {
- pinnedTypes.add(clazz.type);
+ if (item instanceof DexType || item instanceof DexClass) {
+ DexType type = item instanceof DexType ? (DexType) item : ((DexClass) item).type;
+ // We check for the case where the type is pinned according to appInfo.isPinned, so only
+ // add it here if it is not the case.
+ if (!appInfo.isPinned(type)) {
+ markTypeAsPinned(type, pinnedTypes);
}
- } else if (item instanceof DexMethod) {
- // Pin the return type and the parameter types of the method.
- DexMethod method = (DexMethod) item;
- DexClass clazz = appInfo.definitionFor(method.proto.returnType);
- if (clazz != null && clazz.isProgramClass()) {
- // If we were to merge [other] into its sub class, then we would implicitly change the
- // signature of this method, and therefore break the invariant.
- pinnedTypes.add(clazz.type);
- }
+ } else if (item instanceof DexField || item instanceof DexEncodedField) {
+ // Pin the holder and the type of the field.
+ DexField field =
+ item instanceof DexField ? (DexField) item : ((DexEncodedField) item).field;
+ markTypeAsPinned(field.clazz, pinnedTypes);
+ markTypeAsPinned(field.type, pinnedTypes);
+ } else if (item instanceof DexMethod || item instanceof DexEncodedMethod) {
+ // Pin the holder, the return type and the parameter types of the method. If we were to
+ // merge any of these types into their sub classes, then we would implicitly change the
+ // signature of this method.
+ DexMethod method =
+ item instanceof DexMethod ? (DexMethod) item : ((DexEncodedMethod) item).method;
+ markTypeAsPinned(method.holder, pinnedTypes);
+ markTypeAsPinned(method.proto.returnType, pinnedTypes);
for (DexType parameterType : method.proto.parameters.values) {
- clazz = appInfo.definitionFor(parameterType);
- if (clazz != null && clazz.isProgramClass()) {
- // If we were to merge [other] into its sub class, then we would implicitly change the
- // signature of this method, and therefore break the invariant.
- pinnedTypes.add(clazz.type);
- }
+ markTypeAsPinned(parameterType, pinnedTypes);
}
}
}
}
+ private void markTypeAsPinned(DexType type, Set<DexType> pinnedTypes) {
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null && clazz.isProgramClass()) {
+ pinnedTypes.add(type);
+ }
+ }
+
private boolean isMergeCandidate(DexProgramClass clazz, Set<DexType> pinnedTypes) {
if (appInfo.instantiatedTypes.contains(clazz.type)
|| appInfo.isPinned(clazz.type)
@@ -202,9 +207,13 @@
return true;
}
- private boolean mergeMayLeadToIllegalAccesses(DexClass clazz, DexClass singleSubclass) {
- if (clazz.type.isSamePackage(singleSubclass.type)) {
- return false;
+ private boolean mergeMayLeadToIllegalAccesses(DexClass source, DexClass target) {
+ if (source.type.isSamePackage(target.type)) {
+ int accessLevel =
+ source.accessFlags.isPrivate() ? 0 : (source.accessFlags.isPublic() ? 2 : 1);
+ int otherAccessLevel =
+ target.accessFlags.isPrivate() ? 0 : (target.accessFlags.isPublic() ? 2 : 1);
+ return accessLevel > otherAccessLevel;
}
// TODO(christofferqa): To merge [clazz] into a class from another package we need to ensure:
// (A) All accesses to [clazz] and its members from inside the current package of [clazz] will
@@ -268,6 +277,10 @@
timing.begin("fixup");
GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
timing.end();
+ assert result.assertNotModified(appInfo.alwaysInline);
+ assert result.assertNotModified(appInfo.noSideEffects.keySet());
+ // TODO(christofferqa): Enable this assert.
+ // assert result.assertNotModified(appInfo.pinnedItems);
return result;
}
@@ -345,6 +358,9 @@
}
continue;
}
+ if (methodResolutionMayChange(clazz, targetClass)) {
+ continue;
+ }
// Field resolution first considers the direct interfaces of [targetClass] before it proceeds
// to the super class.
if (fieldResolutionMayChange(clazz, targetClass)) {
@@ -395,6 +411,49 @@
return renamedMembersLense.build(graphLense);
}
+ private boolean methodResolutionMayChange(DexClass source, DexClass target) {
+ // When merging an interface into a class, all instructions on the form "invoke-interface
+ // [source].m" are changed into "invoke-virtual [target].m". We need to abort the merge if this
+ // transformation could hide IncompatibleClassChangeErrors.
+ if (source.isInterface() && !target.isInterface()) {
+ List<DexEncodedMethod> defaultMethods = new ArrayList<>();
+ for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
+ if (!virtualMethod.accessFlags.isAbstract()) {
+ defaultMethods.add(virtualMethod);
+ }
+ }
+
+ // For each of the default methods, the subclass [target] could inherit another default method
+ // with the same signature from another interface (i.e., there is a conflict). In such cases,
+ // instructions on the form "invoke-interface [source].foo()" will fail with an Incompatible-
+ // ClassChangeError.
+ //
+ // Example:
+ // interface I1 { default void m() {} }
+ // interface I2 { default void m() {} }
+ // class C implements I1, I2 {
+ // ... invoke-interface I1.m ... <- IncompatibleClassChangeError
+ // }
+ for (DexEncodedMethod method : defaultMethods) {
+ // Conservatively find all possible targets for this method.
+ Set<DexEncodedMethod> interfaceTargets = appInfo.lookupInterfaceTargets(method.method);
+
+ // If [method] is not even an interface-target, then we can safely merge it. Otherwise we
+ // need to check for a conflict.
+ if (interfaceTargets.remove(method)) {
+ for (DexEncodedMethod interfaceTarget : interfaceTargets) {
+ DexClass enclosingClass = appInfo.definitionFor(interfaceTarget.method.holder);
+ if (enclosingClass != null && enclosingClass.isInterface()) {
+ // Found another default method that is different from the one in [source], aborting.
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
private boolean fieldResolutionMayChange(DexClass source, DexClass target) {
if (source.type == target.superType) {
// If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class
@@ -652,17 +711,19 @@
ParameterAnnotationsList.empty(),
new SynthesizedCode(
new ForwardMethodSourceCode(holder, proto, holder, invocationTarget, Type.DIRECT),
- registry -> registry.registerInvokeDirect(invocationTarget)));
+ registry -> registry.registerInvokeDirect(invocationTarget)),
+ signature.hasClassFileVersion() ? signature.getClassFileVersion() : -1);
}
// Returns the method that shadows the given method, or null if method is not shadowed.
private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
- DexEncodedMethod actual = appInfo.resolveMethod(target.type, method.method).asSingleTarget();
- if (actual == null) {
- // May happen in case of missing classes.
+ ResolutionResult resolutionResult = appInfo.resolveMethod(target.type, method.method);
+ if (!resolutionResult.hasSingleTarget()) {
+ // May happen in case of missing classes, or if multiple implementations were found.
abortMerge = true;
return null;
}
+ DexEncodedMethod actual = resolutionResult.asSingleTarget();
if (actual != method) {
return actual;
}
diff --git a/src/test/examples/classmerging/SimpleInterface.java b/src/test/examples/classmerging/SimpleInterface.java
deleted file mode 100644
index e8ebcab..0000000
--- a/src/test/examples/classmerging/SimpleInterface.java
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) 2018, 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 classmerging;
-
-public interface SimpleInterface {
- void foo();
-}
diff --git a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
index 04b5386..ce7bf14 100644
--- a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
+++ b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
@@ -8,9 +8,35 @@
public class SimpleInterfaceAccessTest {
public static void main(String[] args) {
- // It is not possible to merge the interface SimpleInterface into SimpleInterfaceImpl, since
- // this would lead to an illegal class access here.
- SimpleInterface obj = SimpleInterfaceImplRetriever.getSimpleInterfaceImpl();
- obj.foo();
+ // Without access modifications, it is not possible to merge the interface SimpleInterface into
+ // SimpleInterfaceImpl, since this would lead to an illegal class access here.
+ SimpleInterface x = SimpleInterfaceImplRetriever.getSimpleInterfaceImpl();
+ x.foo();
+
+ // Without access modifications, it is not possible to merge the interface OtherSimpleInterface
+ // into OtherSimpleInterfaceImpl, since this could lead to an illegal class access if another
+ // package references OtherSimpleInterface.
+ OtherSimpleInterface y = new OtherSimpleInterfaceImpl();
+ y.bar();
+ }
+
+ // Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
+ public interface SimpleInterface {
+
+ void foo();
+ }
+
+ // Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
+ public interface OtherSimpleInterface {
+
+ void bar();
+ }
+
+ private static class OtherSimpleInterfaceImpl implements OtherSimpleInterface {
+
+ @Override
+ public void bar() {
+ System.out.println("In bar on OtherSimpleInterfaceImpl");
+ }
}
}
diff --git a/src/test/examples/classmerging/pkg/SimpleInterfaceImplRetriever.java b/src/test/examples/classmerging/pkg/SimpleInterfaceImplRetriever.java
index 5cfa11e..22391e9 100644
--- a/src/test/examples/classmerging/pkg/SimpleInterfaceImplRetriever.java
+++ b/src/test/examples/classmerging/pkg/SimpleInterfaceImplRetriever.java
@@ -4,7 +4,7 @@
package classmerging.pkg;
-import classmerging.SimpleInterface;
+import classmerging.SimpleInterfaceAccessTest.SimpleInterface;
public class SimpleInterfaceImplRetriever {
diff --git a/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java b/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
new file mode 100644
index 0000000..79e98d1
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, 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 classmerging;
+
+public class LambdaRewritingTest {
+
+ public static void main(String[] args) {
+ Interface obj = new InterfaceImpl();
+
+ // Leads to an invoke-custom instruction that mentions the type of `obj` since it is captured.
+ invoke(() -> obj.foo());
+ }
+
+ private static void invoke(Function f) {
+ f.accept();
+ }
+
+ public interface Function {
+
+ void accept();
+ }
+
+ // Will be merged into InterfaceImpl.
+ public interface Interface {
+
+ void foo();
+ }
+
+ public static class InterfaceImpl implements Interface {
+
+ @Override
+ public void foo() {
+ System.out.println("In InterfaceImpl.foo()");
+ }
+ }
+}
diff --git a/src/test/examplesAndroidO/classmerging/keep-rules.txt b/src/test/examplesAndroidO/classmerging/keep-rules.txt
new file mode 100644
index 0000000..9868dcf
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/keep-rules.txt
@@ -0,0 +1,12 @@
+# Copyright (c) 2018, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class classmerging.LambdaRewritingTest {
+ public static void main(...);
+}
+
+# TODO(herhut): Consider supporting merging of inner-class attributes.
+# -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 1e71a82..f141d36 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -35,7 +35,7 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
-import org.junit.Ignore;
+import java.util.function.Predicate;
import org.junit.Test;
// TODO(christofferqa): Add tests to check that statically typed invocations on method handles
@@ -44,11 +44,15 @@
public class ClassMergingTest extends TestBase {
private static final Path CF_DIR =
- Paths.get(ToolHelper.BUILD_DIR).resolve("classes/examples/classmerging");
+ Paths.get(ToolHelper.BUILD_DIR).resolve("test/examples/classes/classmerging");
+ private static final Path JAVA8_CF_DIR =
+ Paths.get(ToolHelper.BUILD_DIR).resolve("test/examplesAndroidO/classes/classmerging");
private static final Path EXAMPLE_JAR = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
.resolve("classmerging.jar");
private static final Path EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_DIR)
.resolve("classmerging").resolve("keep-rules.txt");
+ private static final Path JAVA8_EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_DIR)
+ .resolve("classmerging").resolve("keep-rules.txt");
private static final Path DONT_OPTIMIZE = Paths.get(ToolHelper.EXAMPLES_DIR)
.resolve("classmerging").resolve("keep-rules-dontoptimize.txt");
@@ -109,6 +113,28 @@
}
@Test
+ public void testLambdaRewriting() throws Exception {
+ String main = "classmerging.LambdaRewritingTest";
+ Path[] programFiles =
+ new Path[] {
+ JAVA8_CF_DIR.resolve("LambdaRewritingTest.class"),
+ JAVA8_CF_DIR.resolve("LambdaRewritingTest$Function.class"),
+ JAVA8_CF_DIR.resolve("LambdaRewritingTest$Interface.class"),
+ JAVA8_CF_DIR.resolve("LambdaRewritingTest$InterfaceImpl.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.LambdaRewritingTest",
+ "classmerging.LambdaRewritingTest$Function",
+ "classmerging.LambdaRewritingTest$InterfaceImpl");
+ runTest(
+ main,
+ programFiles,
+ name -> preservedClassNames.contains(name) || name.contains("$Lambda$"),
+ getProguardConfig(JAVA8_EXAMPLE_KEEP));
+ }
+
+ @Test
public void testSuperCallWasDetected() throws Exception {
String main = "classmerging.SuperCallRewritingTest";
Path[] programFiles =
@@ -121,7 +147,7 @@
ImmutableSet.of(
"classmerging.SubClassThatReferencesSuperMethod",
"classmerging.SuperCallRewritingTest");
- runTest(main, programFiles, preservedClassNames);
+ runTest(main, programFiles, preservedClassNames::contains);
}
// When a subclass A has been merged into its subclass B, we rewrite invoke-super calls that hit
@@ -177,7 +203,7 @@
runTestOnInput(
main,
builder.build(),
- preservedClassNames,
+ preservedClassNames::contains,
// Prevent class merging, such that the generated code would be invalid if we rewrite the
// invoke-super instruction into an invoke-direct instruction.
getProguardConfig(EXAMPLE_KEEP, "-keep class *"));
@@ -197,7 +223,7 @@
ImmutableSet.of(
"classmerging.ConflictingInterfaceSignaturesTest",
"classmerging.ConflictingInterfaceSignaturesTest$InterfaceImpl");
- runTest(main, programFiles, preservedClassNames);
+ runTest(main, programFiles, preservedClassNames::contains);
}
// If an exception class A is merged into another exception class B, then all exception tables
@@ -218,7 +244,7 @@
"classmerging.ExceptionTest",
"classmerging.ExceptionTest$ExceptionB",
"classmerging.ExceptionTest$Exception2");
- DexInspector inspector = runTest(main, programFiles, preservedClassNames);
+ DexInspector inspector = runTest(main, programFiles, preservedClassNames::contains);
ClassSubject mainClass = inspector.clazz(main);
assertThat(mainClass, isPresent());
@@ -243,8 +269,10 @@
String main = "classmerging.SimpleInterfaceAccessTest";
Path[] programFiles =
new Path[] {
- CF_DIR.resolve("SimpleInterface.class"),
CF_DIR.resolve("SimpleInterfaceAccessTest.class"),
+ CF_DIR.resolve("SimpleInterfaceAccessTest$SimpleInterface.class"),
+ CF_DIR.resolve("SimpleInterfaceAccessTest$OtherSimpleInterface.class"),
+ CF_DIR.resolve("SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl.class"),
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class")
};
@@ -252,14 +280,15 @@
// is in a different package and is not public.
ImmutableSet<String> preservedClassNames =
ImmutableSet.of(
- "classmerging.SimpleInterface",
"classmerging.SimpleInterfaceAccessTest",
+ "classmerging.SimpleInterfaceAccessTest$SimpleInterface",
+ "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterface",
+ "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl",
"classmerging.pkg.SimpleInterfaceImplRetriever",
"classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
- runTest(main, programFiles, preservedClassNames);
+ runTest(main, programFiles, preservedClassNames::contains);
}
- @Ignore("b/73958515")
@Test
public void testNoIllegalClassAccessWithAccessModifications() throws Exception {
// If access modifications are allowed then SimpleInterface should be merged into
@@ -267,14 +296,20 @@
String main = "classmerging.SimpleInterfaceAccessTest";
Path[] programFiles =
new Path[] {
- CF_DIR.resolve("SimpleInterface.class"),
CF_DIR.resolve("SimpleInterfaceAccessTest.class"),
+ CF_DIR.resolve("SimpleInterfaceAccessTest$SimpleInterface.class"),
+ CF_DIR.resolve("SimpleInterfaceAccessTest$OtherSimpleInterface.class"),
+ CF_DIR.resolve("SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl.class"),
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class")
};
ImmutableSet<String> preservedClassNames =
ImmutableSet.of(
"classmerging.SimpleInterfaceAccessTest",
+ // TODO(christofferqa): Should be able to merge SimpleInterface into SimpleInterfaceImpl
+ // when access modifications are allowed.
+ "classmerging.SimpleInterfaceAccessTest$SimpleInterface",
+ "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl",
"classmerging.pkg.SimpleInterfaceImplRetriever",
"classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
// Allow access modifications (and prevent SimpleInterfaceImplRetriever from being removed as
@@ -282,7 +317,7 @@
runTest(
main,
programFiles,
- preservedClassNames,
+ preservedClassNames::contains,
getProguardConfig(
EXAMPLE_KEEP,
"-allowaccessmodification",
@@ -310,7 +345,7 @@
runTest(
main,
programFiles,
- preservedClassNames,
+ preservedClassNames::contains,
getProguardConfig(
EXAMPLE_KEEP, "-keep class classmerging.RewritePinnedMethodTest$A { *; }"));
}
@@ -327,40 +362,42 @@
Set<String> preservedClassNames =
ImmutableSet.of(
"classmerging.TemplateMethodTest", "classmerging.TemplateMethodTest$AbstractClassImpl");
- runTest(main, programFiles, preservedClassNames);
+ runTest(main, programFiles, preservedClassNames::contains);
}
- private DexInspector runTest(String main, Path[] programFiles, Set<String> preservedClassNames)
- throws Exception {
+ private DexInspector runTest(
+ String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Exception {
return runTest(main, programFiles, preservedClassNames, getProguardConfig(EXAMPLE_KEEP));
}
private DexInspector runTest(
- String main, Path[] programFiles, Set<String> preservedClassNames, String proguardConfig)
+ String main,
+ Path[] programFiles,
+ Predicate<String> preservedClassNames,
+ String proguardConfig)
throws Exception {
return runTestOnInput(
main, readProgramFiles(programFiles), preservedClassNames, proguardConfig);
}
private DexInspector runTestOnInput(
- String main, AndroidApp input, Set<String> preservedClassNames, String proguardConfig)
+ String main, AndroidApp input, Predicate<String> preservedClassNames, String proguardConfig)
throws Exception {
AndroidApp output = compileWithR8(input, proguardConfig, this::configure);
- DexInspector inspector = new DexInspector(output);
+ DexInspector inputInspector = new DexInspector(input);
+ DexInspector outputInspector = new DexInspector(output);
// Check that all classes in [preservedClassNames] are in fact preserved.
- for (String className : preservedClassNames) {
- assertTrue(
- "Class " + className + " should be present", inspector.clazz(className).isPresent());
- }
- // Check that all other classes have been removed.
- for (FoundClassSubject classSubject : inspector.allClasses()) {
- String className = classSubject.getDexClass().toSourceString();
- assertTrue(
- "Class " + className + " should be absent", preservedClassNames.contains(className));
+ for (FoundClassSubject classSubject : inputInspector.allClasses()) {
+ String className = classSubject.getOriginalName();
+ boolean shouldBePresent = preservedClassNames.test(className);
+ assertEquals(
+ "Class " + className + " should be " + (shouldBePresent ? "present" : "absent"),
+ shouldBePresent,
+ outputInspector.clazz(className).isPresent());
}
// Check that the R8-generated code produces the same result as D8-generated code.
assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
- return inspector;
+ return outputInspector;
}
private String getProguardConfig(Path path, String... additionalRules) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
index 22e8537..eb85a7c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
@@ -13,7 +13,9 @@
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -44,6 +46,60 @@
}
}
+ private static class StreamState {
+ static final int ENTRY_LINE = -1;
+ static final int PLACEHOLDER_LINE = -2;
+
+ final Iterator<DebuggeeState> iterator;
+ final Deque<Integer> frameEntryLines = new ArrayDeque<>();
+ int currentLine = ENTRY_LINE;
+
+ StreamState(Stream<DebuggeeState> stream) {
+ iterator = stream.iterator();
+ }
+
+ DebuggeeState next() {
+ while (true) {
+ DebuggeeState state = iterator.next();
+ if (state == null) {
+ return null;
+ }
+ int nextDepth = state.getFrameDepth();
+ int nextLine = state.getLineNumber();
+ if (nextDepth == frameEntryLines.size()) {
+ currentLine = nextLine;
+ return state;
+ }
+ if (nextDepth > frameEntryLines.size()) {
+ frameEntryLines.push(currentLine);
+ while (nextDepth > frameEntryLines.size()) {
+ // If the depth grows by more than one we have entered into filtered out frames, eg,
+ // java/android internals. In this case push placeholder lines on the stack.
+ frameEntryLines.push(PLACEHOLDER_LINE);
+ }
+ currentLine = nextLine;
+ assert nextDepth == frameEntryLines.size();
+ return state;
+ }
+ currentLine = nextLine;
+ while (frameEntryLines.size() > nextDepth + 1) {
+ // If the depth decreases by more than one we have popped the filtered frames.
+ // Verify they are placeholder lines.
+ int placeholder = frameEntryLines.pop();
+ assert placeholder == PLACEHOLDER_LINE;
+ }
+ int lineOnEntry = frameEntryLines.pop();
+ assert nextDepth == frameEntryLines.size();
+ if (lineOnEntry != nextLine) {
+ return state;
+ }
+ // A frame was popped and the current line is the same as when the frame was entered.
+ // In this case we advance again to avoid comparing that a function call returns to the
+ // same line (which may not be the case if no stores are needed after the call).
+ }
+ }
+ }
+
private boolean verifyLines = true;
private boolean verifyFiles = true;
private boolean verifyMethods = true;
@@ -140,13 +196,16 @@
}
private void internal() {
- List<Iterator<DebuggeeState>> iterators =
- streams.stream().map(Stream::iterator).collect(Collectors.toList());
+ List<StreamState> streamStates =
+ streams.stream().map(StreamState::new).collect(Collectors.toList());
while (true) {
- List<DebuggeeState> states = new ArrayList<>(iterators.size());
+ List<DebuggeeState> states = new ArrayList<>(streamStates.size());
boolean done = false;
- for (Iterator<DebuggeeState> iterator : iterators) {
- DebuggeeState state = iterator.next();
+ for (StreamState streamState : streamStates) {
+ DebuggeeState state;
+ do {
+ state = streamState.next();
+ } while (state != null && !filter.test(state));
states.add(state);
if (state == null) {
done = true;
@@ -159,11 +218,9 @@
states.stream().allMatch(Objects::isNull));
return;
} else {
- if (filter.test(states.get(0))) {
- verifyStatesEqual(states);
- if (printOptions.printStates) {
- System.out.println(prettyPrintState(states.get(0), printOptions));
- }
+ verifyStatesEqual(states);
+ if (printOptions.printStates) {
+ System.out.println(prettyPrintState(states.get(0), printOptions));
}
}
} catch (AssertionError e) {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index d41ee84..5707812 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -1943,6 +1943,7 @@
.addAll(super.getExcludedClasses())
.add("libcore.*")
.add("dalvik.*")
+ .add("com.android.dex.*") // Android 6.0.1 - 7.0.0 use this for reflection.
.build();
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 02e995f..6de20c6 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -75,7 +75,7 @@
@Test
public void testBridgeMethod() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
+ // TODO(b/79671093): D8 has local variables with empty names.
testDebuggingJvmOnly("bridge", "BridgeMethod");
}
@@ -122,8 +122,7 @@
@Test
public void testInstanceVariable() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("instancevariable", "InstanceVariable");
+ testDebugging("instancevariable", "InstanceVariable");
}
@Test
@@ -133,8 +132,7 @@
@Test
public void testInvoke() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("invoke", "Invoke");
+ testDebugging("invoke", "Invoke");
}
@Test
@@ -155,8 +153,7 @@
@Test
public void testNewArray() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("newarray", "NewArray");
+ testDebugging("newarray", "NewArray");
}
@Test
@@ -166,8 +163,7 @@
@Test
public void testReturns() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("returns", "Returns");
+ testDebugging("returns", "Returns");
}
@Test
@@ -222,8 +218,7 @@
@Test
public void testInvokeEmpty() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("invokeempty", "InvokeEmpty");
+ testDebugging("invokeempty", "InvokeEmpty");
}
@Test
@@ -233,21 +228,17 @@
@Test
public void testRegress2() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("regress2", "Regress2");
+ testDebugging("regress2", "Regress2");
}
- @Ignore("TODO(mathiasr): Different behavior CfSourceCode vs JarSourceCode")
@Test
public void testRegress37726195() throws Exception {
- // TODO(b/79671093): We don't match JVM's behavior on this example.
- testDebuggingJvmOutputOnly("regress_37726195", "Regress");
+ testDebugging("regress_37726195", "Regress");
}
@Test
public void testRegress37658666() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("regress_37658666", "Regress");
+ testDebugging("regress_37658666", "Regress");
}
@Test
@@ -257,8 +248,7 @@
@Test
public void testRegress37955340() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("regress_37955340", "Regress");
+ testDebugging("regress_37955340", "Regress");
}
@Test
@@ -304,8 +294,7 @@
@Test
public void testMemberrebinding2() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("memberrebinding2", "Memberrebinding");
+ testDebugging("memberrebinding2", "Memberrebinding");
}
@Test
@@ -315,14 +304,12 @@
@Test
public void testMinification() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("minification", "Minification");
+ testDebugging("minification", "Minification");
}
@Test
public void testEnclosingmethod() throws Exception {
- // TODO(b/79671093): D8 has different line number info during stepping.
- testDebuggingJvmOnly("enclosingmethod", "Main");
+ testDebugging("enclosingmethod", "Main");
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/one/AbstractTopClass.java b/src/test/java/com/android/tools/r8/resolution/singletarget/one/AbstractTopClass.java
index d9ff675..c745dfa 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/one/AbstractTopClass.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/one/AbstractTopClass.java
@@ -5,33 +5,36 @@
public abstract class AbstractTopClass implements InterfaceWithDefault {
+ // Avoid AbstractTopClass.class.getCanonicalName() as it may change during shrinking.
+ private static final String TAG = "AbstractTopClass";
+
public void singleTargetAtTop() {
- System.out.println(AbstractTopClass.class.getCanonicalName());
+ System.out.println(TAG);
}
public void singleShadowingOverride() {
- System.out.println(AbstractTopClass.class.getCanonicalName());
+ System.out.println(TAG);
}
public abstract void abstractTargetAtTop();
public void overridenInAbstractClassOnly() {
- System.out.println(AbstractTopClass.class.getCanonicalName());
+ System.out.println(TAG);
}
public void overriddenInTwoSubTypes() {
- System.out.println(AbstractTopClass.class.getCanonicalName());
+ System.out.println(TAG);
}
public void definedInTwoSubTypes() {
- System.out.println(AbstractTopClass.class.getCanonicalName());
+ System.out.println(TAG);
}
public static void staticMethod() {
- System.out.println(AbstractTopClass.class.getCanonicalName());
+ System.out.println(TAG);
}
public void overriddenByIrrelevantInterface() {
- System.out.println(AbstractTopClass.class.getCanonicalName());
+ System.out.println(TAG);
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/one/InterfaceWithDefault.java b/src/test/java/com/android/tools/r8/resolution/singletarget/one/InterfaceWithDefault.java
index 1a0c434..f0faedd 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/one/InterfaceWithDefault.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/one/InterfaceWithDefault.java
@@ -5,15 +5,18 @@
public interface InterfaceWithDefault {
+ // Avoid InterfaceWithDefault.class.getCanonicalName() as it may change during shrinking.
+ String TAG = "InterfaceWithDefault";
+
default void defaultMethod() {
- System.out.println(InterfaceWithDefault.class.getCanonicalName());
+ System.out.println(TAG);
}
default void overriddenDefault() {
- System.out.println(InterfaceWithDefault.class.getCanonicalName());
+ System.out.println(TAG);
}
default void overriddenInOtherInterface() {
- System.out.println(InterfaceWithDefault.class.getCanonicalName());
+ System.out.println(TAG);
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassOne.java b/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassOne.java
index 22be6a4..ff585af 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassOne.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/one/SubSubClassOne.java
@@ -5,18 +5,21 @@
public class SubSubClassOne extends AbstractSubClass implements IrrelevantInterfaceWithDefault {
+ // Avoid SubSubClassOne.class.getCanonicalName() as it may change during shrinking.
+ private static final String TAG = "SubSubClassOne";
+
@Override
public void abstractTargetAtTop() {
- System.out.println(SubSubClassOne.class.getCanonicalName());
+ System.out.println(TAG);
}
@Override
public void overriddenInTwoSubTypes() {
- System.out.println(SubSubClassOne.class.getCanonicalName());
+ System.out.println(TAG);
}
@Override
public void definedInTwoSubTypes() {
- System.out.println(SubSubClassOne.class.getCanonicalName());
+ System.out.println(TAG);
}
}