Merge commit '34e007329bb88f14c7112229456dec53e2f724d9' into dev-release
diff --git a/.gitignore b/.gitignore
index ddab9db..e64324a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -153,6 +153,8 @@
tools/*/art-8.1.0.tar.gz
tools/*/art-9.0.0
tools/*/art-9.0.0.tar.gz
+tools/*/art-10.0.0
+tools/*/art-10.0.0.tar.gz
tools/*/art.tar.gz
tools/*/dalvik
tools/*/dalvik-4.0.4
diff --git a/build.gradle b/build.gradle
index 2e64458..8f323aa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -332,6 +332,7 @@
"linux/art-7.0.0",
"linux/art-8.1.0",
"linux/art-9.0.0",
+ "linux/art-10.0.0",
"linux/dalvik",
"linux/dalvik-4.0.4",
"${osString}/dx",
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 75398ad..87562ad 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -281,6 +281,24 @@
}
}
builders {
+ name: "linux-android-10.0.0"
+ mixins: "linux"
+ mixins: "normal"
+ recipe {
+ properties: "tool:r8"
+ properties: "dex_vm:10.0.0"
+ }
+ }
+ builders {
+ name: "linux-android-10.0.0_release"
+ mixins: "linux"
+ mixins: "normal"
+ recipe {
+ properties: "tool:r8"
+ properties: "dex_vm:10.0.0"
+ }
+ }
+ builders {
name: "linux-internal"
mixins: "linux"
mixins: "internal"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 7bdd5c2..faf51f3 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -51,6 +51,11 @@
short_name: "9.0.0"
}
builders {
+ name: "buildbucket/luci.r8.ci/linux-android-10.0.0"
+ category: "R8"
+ short_name: "10.0.0"
+ }
+ builders {
name: "buildbucket/luci.r8.ci/linux-internal"
category: "R8"
short_name: "internal"
@@ -131,6 +136,11 @@
short_name: "9.0.0"
}
builders {
+ name: "buildbucket/luci.r8.ci/linux-android-10.0.0_release"
+ category: "R8 release"
+ short_name: "10.0.0"
+ }
+ builders {
name: "buildbucket/luci.r8.ci/linux-internal_release"
category: "R8 release"
short_name: "internal"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
index bcd03ba..5e37d53 100644
--- a/infra/config/global/luci-notify.cfg
+++ b/infra/config/global/luci-notify.cfg
@@ -104,6 +104,16 @@
repository: "https://r8.googlesource.com/r8"
}
builders {
+ name: "linux-android-10.0.0"
+ bucket: "ci"
+ repository: "https://r8.googlesource.com/r8"
+ }
+ builders {
+ name: "linux-android-10.0.0_release"
+ bucket: "ci"
+ repository: "https://r8.googlesource.com/r8"
+ }
+ builders {
name: "linux-internal"
bucket: "ci"
repository: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index dd8fdfa..dd2df27 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -34,6 +34,7 @@
triggers: "linux-android-7.0.0"
triggers: "linux-android-8.1.0"
triggers: "linux-android-9.0.0"
+ triggers: "linux-android-10.0.0"
triggers: "linux-run-on-as-app"
triggers: "linux-run-on-as-app-recompilation"
triggers: "linux-internal"
@@ -77,6 +78,17 @@
triggers: "windows_release"
}
+trigger {
+ id: "branch-gitiles-trigger-10"
+ acl_sets: "default"
+ gitiles: {
+ repo: "https://r8.googlesource.com/r8"
+ refs: "refs/heads/2.0"
+ path_regexps: "src/main/java/com/android/tools/r8/Version.java"
+ }
+ triggers: "linux-android-10.0.0_release"
+}
+
job {
id: "archive"
@@ -301,6 +313,32 @@
}
job {
+ id: "linux-android-10.0.0"
+ acl_sets: "default"
+ triggering_policy: {
+ max_concurrent_invocations: 2
+ }
+ buildbucket {
+ server: "cr-buildbucket.appspot.com"
+ bucket: "luci.r8.ci"
+ builder: "linux-android-10.0.0"
+ }
+}
+
+job {
+ id: "linux-android-10.0.0_release"
+ acl_sets: "default"
+ triggering_policy: {
+ max_concurrent_invocations: 2
+ }
+ buildbucket {
+ server: "cr-buildbucket.appspot.com"
+ bucket: "luci.r8.ci"
+ builder: "linux-android-10.0.0_release"
+ }
+}
+
+job {
id: "linux-internal"
acl_sets: "default"
buildbucket {
diff --git a/scripts/update-host-art.sh b/scripts/update-host-art.sh
index fb45744..76572e0 100755
--- a/scripts/update-host-art.sh
+++ b/scripts/update-host-art.sh
@@ -109,7 +109,10 @@
# dalvikvm32 or dalvikvm64).
cp $ANDROID_HOST_BUILD/bin/dalvikvm $DEST/bin
cp $ANDROID_HOST_BUILD/bin/dex2oat $DEST/bin
-cp $ANDROID_HOST_BUILD/bin/patchoat $DEST/bin
+if [ -e $ANDROID_HOST_BUILD/bin/patchoat ]
+ # File patchoat does not exist on Q anymore.
+ then cp $ANDROID_HOST_BUILD/bin/patchoat $DEST/bin
+fi
# Required framework files.
mkdir -p $DEST/framework
@@ -127,8 +130,13 @@
cp -r $ANDROID_TARGET_BUILD/product/$ANDROID_PRODUCT/system/framework/* $DEST/product/$ANDROID_PRODUCT/system/framework
# Required auxillary files.
-mkdir -p $DEST/usr/icu
-cp -r $ANDROID_HOST_BUILD/usr/icu/* $DEST/usr/icu
+if [ -e $ANDROID_HOST_BUILD/usr/icu ]; then
+ mkdir -p $DEST/usr/icu
+ cp -r $ANDROID_HOST_BUILD/usr/icu/* $DEST/usr/icu
+else
+ mkdir -p $DEST/com.android.runtime/etc/icu/
+ cp -r $ANDROID_HOST_BUILD/com.android.runtime/etc/icu/* $DEST/com.android.runtime/etc/icu/
+fi
# Update links for vdex files for Android P and later.
if [ -f $DEST/product/$ANDROID_PRODUCT/system/framework/boot.vdex ]; then
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index dcb4f9a..28713e3 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -90,9 +90,6 @@
});
if (shrink) {
InternalOptions r8Options = r8Command.getInternalOptions();
- // TODO(b/143590191): Remove the hack and make it work by default.
- // Temporary hack so that keeping an interface keeps the superinterfaces.
- r8Options.testing.keepInheritedInterfaceMethods = true;
// Disable outlining for R8 when called from L8.
r8Options.outline.enabled = false;
R8.runForTesting(r8Command.getInputApp(), r8Options);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index d96a5a1..646a96a 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -163,8 +163,8 @@
}
public boolean isShrinking() {
- // Answers true if keep rules, even empty, are provided.
- return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
+ // Disable for release.
+ return false;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/OptionalBool.java b/src/main/java/com/android/tools/r8/OptionalBool.java
deleted file mode 100644
index 92f5d07..0000000
--- a/src/main/java/com/android/tools/r8/OptionalBool.java
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2019, 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;
-
-/** Three point boolean lattice. */
-public abstract class OptionalBool {
-
- private static final OptionalBool TRUE =
- new OptionalBool() {
- @Override
- public boolean isTrue() {
- return true;
- }
-
- @Override
- public String toString() {
- return "true";
- }
- };
-
- private static final OptionalBool FALSE =
- new OptionalBool() {
- @Override
- public boolean isFalse() {
- return true;
- }
-
- @Override
- public String toString() {
- return "false";
- }
- };
-
- private static final OptionalBool UNKNOWN =
- new OptionalBool() {
- @Override
- public boolean isUnknown() {
- return true;
- }
-
- @Override
- public String toString() {
- return "unknown";
- }
- };
-
- public static OptionalBool of(boolean bool) {
- return bool ? TRUE : FALSE;
- }
-
- public static OptionalBool unknown() {
- return UNKNOWN;
- }
-
- private OptionalBool() {}
-
- public boolean isTrue() {
- return false;
- }
-
- public boolean isFalse() {
- return false;
- }
-
- public boolean isUnknown() {
- return false;
- }
-
- public boolean isPossiblyTrue() {
- return !isFalse();
- }
-
- public boolean isPossiblyFalse() {
- return !isTrue();
- }
-
- public boolean getBooleanValue() {
- if (isUnknown()) {
- throw new IllegalStateException("Attempt to convert unknown value to a boolean");
- }
- return isTrue();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 3246478..0d8f3ba 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -246,7 +246,8 @@
clazz.forEachMethod(
method -> {
ResolutionResult resolutionResult = appInfo.resolveMethod(superType, method.method);
- for (DexEncodedMethod dexEncodedMethod : resolutionResult.asListOfTargets()) {
+ DexEncodedMethod dexEncodedMethod = resolutionResult.getSingleTarget();
+ if (dexEncodedMethod != null) {
addMethod(dexEncodedMethod.method);
}
});
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6db32eb..ab6796b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -868,6 +868,9 @@
}
private void computeKotlinInfoForProgramClasses(DexApplication application, AppView<?> appView) {
+ if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
+ return;
+ }
Kotlin kotlin = appView.dexItemFactory().kotlin;
Reporter reporter = options.reporter;
for (DexProgramClass programClass : application.classes()) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index fd0c968..afdc15b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -13,6 +14,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
@@ -158,26 +160,10 @@
if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
type = Type.DIRECT;
} else if (code.getOriginalHolder() == method.holder) {
- if (!this.itf || builder.appView.options().isInterfaceMethodDesugaringEnabled()) {
- // When desugaring default interface methods, it is expected they are targeted with
- // invoke-direct.
- type = Type.DIRECT;
- } else {
- DexProgramClass clazz = builder.appView.definitionForProgramType(method.holder);
- assert clazz != null;
- DexEncodedMethod encodedMethod = clazz.lookupDirectMethod(method);
- if (encodedMethod != null) {
- assert encodedMethod.isStatic() || encodedMethod.isPrivateMethod();
- type = Type.DIRECT;
- } else {
- // This is a default interface method.
- type = Type.SUPER;
- }
- }
+ type = invokeTypeForInvokeSpecialToNonInitMethodOnHolder(builder.appView, code);
} else {
type = Type.SUPER;
}
- assert type == Type.SUPER || type == Type.DIRECT;
break;
}
case Opcodes.INVOKESTATIC:
@@ -216,4 +202,39 @@
return InliningConstraintVisitor.getConstraintForInvoke(
opcode, method, graphLense, appView, inliningConstraints, invocationContext);
}
+
+ private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
+ AppView<?> appView, CfSourceCode code) {
+ boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
+ DexEncodedMethod encodedMethod = lookupMethod(appView, method);
+ if (encodedMethod == null) {
+ // The method is not defined on the class, we can use super to target. When desugaring
+ // default interface methods, it is expected they are targeted with invoke-direct.
+ return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
+ }
+ if (!encodedMethod.isVirtualMethod()) {
+ return Type.DIRECT;
+ }
+ if (encodedMethod.accessFlags.isFinal()) {
+ // This method is final which indicates no subtype will overwrite it, we can use
+ // invoke-virtual.
+ return Type.VIRTUAL;
+ }
+ if (this.itf && encodedMethod.isDefaultMethod()) {
+ return desugaringEnabled ? Type.DIRECT : Type.SUPER;
+ }
+ // We cannot emulate the semantics of invoke-special in this case and should throw a compilation
+ // error.
+ throw new CompilationError(
+ "Failed to compile unsupported use of invokespecial", code.getOrigin());
+ }
+
+ private DexEncodedMethod lookupMethod(AppView<?> appView, DexMethod method) {
+ GraphLenseLookupResult lookupResult =
+ appView.graphLense().lookupMethod(method, method, Type.DIRECT);
+ DexMethod rewrittenMethod = lookupResult.getMethod();
+ DexProgramClass clazz = appView.definitionForProgramType(rewrittenMethod.holder);
+ assert clazz != null;
+ return clazz.lookupMethod(rewrittenMethod);
+ }
}
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 3066b4d..6eb6a94 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -180,6 +180,10 @@
return existing != null ? existing : typeDefinitions;
}
+ public void invalidateTypeCacheFor(DexType type) {
+ definitions.remove(type);
+ }
+
/**
* Lookup static method following the super chain from the holder of {@code method}.
* <p>
@@ -365,25 +369,15 @@
* Section 5.4.3.3 of the JVM Spec</a>.
*/
private DexEncodedMethod resolveMethodOnClassStep2(DexClass clazz, DexMethod method) {
- // Pt. 1: Signature polymorphic method check. Those are only allowed on
- // java.lang.invoke.MethodHandle, so we only need to look for it if we are looking at
- // that type.
+ // Pt. 1: Signature polymorphic method check.
// See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
// Section 2.9 of the JVM Spec</a>.
- if (clazz.type == dexItemFactory.methodHandleType) {
- DexMethod signaturePolymorphic = dexItemFactory.createMethod(clazz.type,
- dexItemFactory.createProto(
- dexItemFactory.objectType, dexItemFactory.objectArrayType),
- method.name);
- DexEncodedMethod result = clazz.lookupMethod(signaturePolymorphic);
- // Check we found a result and that it has the required access flags for signature polymorphic
- // functions.
- if (result != null && result.accessFlags.isNative() && result.accessFlags.isVarargs()) {
- return result;
- }
+ DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(method.name, dexItemFactory);
+ if (result != null) {
+ return result;
}
// Pt 2: Find a method that matches the descriptor.
- DexEncodedMethod result = clazz.lookupMethod(method);
+ result = clazz.lookupMethod(method);
if (result != null) {
return result;
}
@@ -566,34 +560,6 @@
return null;
}
- /**
- * Implements the dispatch logic for a static invoke operation.
- * <p>
- * The only requirement is that the method is indeed static.
- */
- public DexEncodedMethod dispatchStaticInvoke(ResolutionResult resolvedMethod) {
- assert checkIfObsolete();
- DexEncodedMethod target = resolvedMethod.getSingleTarget();
- if (target != null && target.accessFlags.isStatic()) {
- return target;
- }
- return null;
- }
-
- /**
- * Implements the dispatch logic for the direct parts of a invokespecial instruction.
- * <p>
- * The only requirement is that the method is not static.
- */
- public DexEncodedMethod dispatchDirectInvoke(ResolutionResult resolvedMethod) {
- assert checkIfObsolete();
- DexEncodedMethod target = resolvedMethod.getSingleTarget();
- if (target != null && !target.accessFlags.isStatic()) {
- return target;
- }
- return null;
- }
-
public boolean hasSubtyping() {
assert checkIfObsolete();
return false;
@@ -670,7 +636,7 @@
return NoSuchMethodResult.INSTANCE;
}
// Fast path in the common case of a single method.
- if (false && maximallySpecificMethods.size() == 1) {
+ if (maximallySpecificMethods.size() == 1) {
return new SingleResolutionResult(maximallySpecificMethods.values().iterator().next());
}
DexEncodedMethod firstMaximallySpecificMethod = null;
@@ -696,7 +662,7 @@
if (nonAbstractMethods.size() == 1) {
return new SingleResolutionResult(nonAbstractMethods.get(0));
}
- return new IncompatibleClassResult(Collections.emptyList(), nonAbstractMethods);
+ return IncompatibleClassResult.create(nonAbstractMethods);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 90cc0f4..a2d6967 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.graph;
-import com.android.tools.r8.OptionalBool;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
+import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
@@ -16,6 +16,7 @@
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
@@ -240,6 +241,14 @@
return defaultValue;
}
+ public <U> U withGeneratedMessageLiteBuilderShrinker(
+ Function<GeneratedMessageLiteBuilderShrinker, U> fn, U defaultValue) {
+ if (protoShrinker != null && protoShrinker.generatedMessageLiteBuilderShrinker != null) {
+ return fn.apply(protoShrinker.generatedMessageLiteBuilderShrinker);
+ }
+ return defaultValue;
+ }
+
public GraphLense graphLense() {
return graphLense;
}
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 0acef70..72e372a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -3,13 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import com.android.tools.r8.OptionalBool;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.PredicateUtils;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicates;
@@ -581,6 +581,38 @@
return result == null ? lookupVirtualMethod(method) : result;
}
+ public DexEncodedMethod lookupSignaturePolymorphicMethod(
+ DexString methodName, DexItemFactory factory) {
+ if (type != factory.methodHandleType && type != factory.varHandleType) {
+ return null;
+ }
+ DexEncodedMethod matchingName = null;
+ DexEncodedMethod signaturePolymorphicMethod = null;
+ for (DexEncodedMethod method : virtualMethods) {
+ if (method.method.name == methodName) {
+ if (matchingName != null) {
+ // The jvm spec, section 5.4.3.3 details that there must be exactly one method with the
+ // given name only.
+ return null;
+ }
+ matchingName = method;
+ if (isSignaturePolymorphicMethod(method, factory)) {
+ signaturePolymorphicMethod = method;
+ }
+ }
+ }
+ return signaturePolymorphicMethod;
+ }
+
+ private boolean isSignaturePolymorphicMethod(DexEncodedMethod method, DexItemFactory factory) {
+ assert method.method.holder == factory.methodHandleType
+ || method.method.holder == factory.varHandleType;
+ return method.accessFlags.isVarargs()
+ && method.accessFlags.isNative()
+ && method.method.proto.parameters.size() == 1
+ && method.method.proto.parameters.values[0] != factory.objectArrayType;
+ }
+
private <T extends DexItem, S extends Descriptor<T, S>> T lookupTarget(T[] items, S descriptor) {
for (T entry : items) {
if (descriptor.match(entry)) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 415e65b..47930c2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -10,7 +10,6 @@
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
-import com.android.tools.r8.OptionalBool;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfInstruction;
@@ -62,6 +61,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index acc5942..ec2f8e3 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -4,11 +4,10 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -22,17 +21,10 @@
return null;
}
- // TODO(b/140214802): Remove this method as its usage is questionable.
- public abstract DexEncodedMethod asResultOfResolve();
-
public abstract DexEncodedMethod getSingleTarget();
public abstract boolean hasSingleTarget();
- public abstract List<DexEncodedMethod> asListOfTargets();
-
- public abstract void forEachTarget(Consumer<DexEncodedMethod> consumer);
-
public abstract boolean isValidVirtualTarget(InternalOptions options);
public abstract boolean isValidVirtualTargetForDynamicDispatch();
@@ -46,10 +38,9 @@
public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
assert isValidVirtualTarget(appInfo.app().options);
// First add the target for receiver type method.type.
- Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
- forEachTarget(result::add);
+ DexEncodedMethod encodedMethod = getSingleTarget();
+ Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
// Add all matching targets from the subclass hierarchy.
- DexEncodedMethod encodedMethod = asResultOfResolve();
DexMethod method = encodedMethod.method;
// TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
// receiver type if available.
@@ -57,12 +48,10 @@
DexClass clazz = appInfo.definitionFor(type);
if (!clazz.isInterface()) {
ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
- methods.forEachTarget(
- target -> {
- if (target.isVirtualMethod()) {
- result.add(target);
- }
- });
+ DexEncodedMethod target = methods.getSingleTarget();
+ if (target != null && target.isVirtualMethod()) {
+ result.add(target);
+ }
}
}
return result;
@@ -102,7 +91,7 @@
}
}
- DexEncodedMethod encodedMethod = asResultOfResolve();
+ DexEncodedMethod encodedMethod = getSingleTarget();
DexMethod method = encodedMethod.method;
Consumer<DexEncodedMethod> addIfNotAbstract =
m -> {
@@ -128,10 +117,14 @@
DexClass clazz = appInfo.definitionFor(type);
if (clazz.isInterface()) {
ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
- targetMethods.forEachTarget(addIfNotAbstractAndBridge);
+ if (targetMethods.hasSingleTarget()) {
+ addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
+ }
} else {
ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
- targetMethods.forEachTarget(addIfNotAbstract);
+ if (targetMethods.hasSingleTarget()) {
+ addIfNotAbstract.accept(targetMethods.getSingleTarget());
+ }
}
}
return result;
@@ -162,11 +155,6 @@
}
@Override
- public DexEncodedMethod asResultOfResolve() {
- return resolutionTarget;
- }
-
- @Override
public DexEncodedMethod getSingleTarget() {
return resolutionTarget;
}
@@ -175,83 +163,11 @@
public boolean hasSingleTarget() {
return true;
}
-
- @Override
- public List<DexEncodedMethod> asListOfTargets() {
- return Collections.singletonList(resolutionTarget);
- }
-
- @Override
- public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
- consumer.accept(resolutionTarget);
- }
- }
-
- public static class MultiResolutionResult extends ResolutionResult {
-
- private final ImmutableList<DexEncodedMethod> methods;
-
- public MultiResolutionResult(ImmutableList<DexEncodedMethod> results) {
- assert results.size() > 1;
- this.methods = results;
- }
-
- @Override
- public boolean isValidVirtualTarget(InternalOptions options) {
- for (DexEncodedMethod method : methods) {
- if (!SingleResolutionResult.isValidVirtualTarget(options, method)) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public boolean isValidVirtualTargetForDynamicDispatch() {
- for (DexEncodedMethod method : methods) {
- if (!method.isVirtualMethod()) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public DexEncodedMethod asResultOfResolve() {
- // Resolution may return any of the targets that were found.
- return methods.get(0);
- }
-
- @Override
- public DexEncodedMethod getSingleTarget() {
- // There is no single target that is guaranteed to be called.
- return null;
- }
-
- @Override
- public boolean hasSingleTarget() {
- return false;
- }
-
- @Override
- public List<DexEncodedMethod> asListOfTargets() {
- return methods;
- }
-
- @Override
- public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
- methods.forEach(consumer);
- }
}
public abstract static class EmptyResult extends ResolutionResult {
@Override
- public DexEncodedMethod asResultOfResolve() {
- return null;
- }
-
- @Override
public DexEncodedMethod getSingleTarget() {
return null;
}
@@ -262,16 +178,6 @@
}
@Override
- public List<DexEncodedMethod> asListOfTargets() {
- return Collections.emptyList();
- }
-
- @Override
- public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
- // Intentionally left empty.
- }
-
- @Override
public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
return null;
}
@@ -313,9 +219,7 @@
return this;
}
- public void forEachFailureDependency(
- Consumer<DexProgramClass> classesCausingFailure,
- Consumer<DexEncodedMethod> methodsCausingFailure) {
+ public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
// Default failure has no dependencies.
}
@@ -339,28 +243,24 @@
}
public static class IncompatibleClassResult extends FailedResolutionResult {
- static final IncompatibleClassResult INSTANCE = new IncompatibleClassResult();
+ static final IncompatibleClassResult INSTANCE =
+ new IncompatibleClassResult(Collections.emptyList());
- private final Collection<DexProgramClass> classesCausingError;
private final Collection<DexEncodedMethod> methodsCausingError;
- private IncompatibleClassResult() {
- this(Collections.emptyList(), Collections.emptyList());
- }
-
- IncompatibleClassResult(
- Collection<DexProgramClass> classesCausingError,
- Collection<DexEncodedMethod> methodsCausingError) {
- this.classesCausingError = classesCausingError;
+ private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
this.methodsCausingError = methodsCausingError;
}
+ static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
+ return methodsCausingError.isEmpty()
+ ? INSTANCE
+ : new IncompatibleClassResult(methodsCausingError);
+ }
+
@Override
- public void forEachFailureDependency(
- Consumer<DexProgramClass> classesCausingFailure,
- Consumer<DexEncodedMethod> methodsCausingFailure) {
- this.classesCausingError.forEach(classesCausingFailure);
- this.methodsCausingError.forEach(methodsCausingFailure);
+ public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
+ this.methodsCausingError.forEach(methodCausingFailureConsumer);
}
}
@@ -370,8 +270,5 @@
private NoSuchMethodResult() {
// Intentionally left empty.
}
-
- // TODO(b/144085169): Consider if the resolution resulting in a NoSuchMethodError should
- // be preserved by ensuring its class is marked. Otherwise, the error may become ClassNotFound.
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 827529a..e5ace97 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.analysis;
-import com.android.tools.r8.OptionalBool;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
@@ -35,6 +34,7 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.util.ArrayDeque;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 35f6661..2c86c7b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -25,12 +25,12 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.DominatorTree.Assumption;
-import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,11 +56,9 @@
AppView<AppInfoWithLiveness> appView,
IRCode code,
OptimizationFeedback feedback,
- DexProgramClass clazz,
DexEncodedMethod method) {
- assert clazz.type == method.method.holder;
this.appView = appView;
- this.clazz = clazz;
+ this.clazz = appView.definitionFor(method.method.holder).asProgramClass();
this.code = code;
this.feedback = feedback;
this.method = method;
@@ -69,26 +67,11 @@
public static void run(
AppView<?> appView, IRCode code, OptimizationFeedback feedback, DexEncodedMethod method) {
- if (!appView.enableWholeProgramOptimizations()) {
- return;
+ if (appView.enableWholeProgramOptimizations() && method.isClassInitializer()) {
+ assert appView.appInfo().hasLiveness();
+ new FieldValueAnalysis(appView.withLiveness(), code, feedback, method)
+ .computeFieldOptimizationInfo();
}
- assert appView.appInfo().hasLiveness();
- if (!method.isInitializer()) {
- return;
- }
- DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
- if (method.isInstanceInitializer()) {
- DexEncodedMethod otherInstanceInitializer =
- clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method);
- if (otherInstanceInitializer != null) {
- // Conservatively bail out.
- // TODO(b/125282093): Handle multiple instance initializers on the same class.
- return;
- }
- }
-
- new FieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
- .computeFieldOptimizationInfo();
}
private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
@@ -103,50 +86,54 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DominatorTree dominatorTree = null;
- DexType context = method.method.holder;
+ if (method.isClassInitializer()) {
+ DexType context = method.method.holder;
- // Find all the static-put instructions that assign a field in the enclosing class which is
- // guaranteed to be assigned only in the current initializer.
- boolean isStraightLineCode = true;
- Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
- for (Instruction instruction : code.instructions()) {
- if (instruction.isFieldPut()) {
- FieldInstruction fieldPut = instruction.asFieldInstruction();
- DexField field = fieldPut.getField();
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField != null
- && encodedField.field.holder == context
- && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
- putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
+ // Find all the static-put instructions that assign a field in the enclosing class which is
+ // guaranteed to be assigned only in the current initializer.
+ boolean isStraightLineCode = true;
+ Map<DexEncodedField, LinkedList<StaticPut>> staticPutsPerField = new IdentityHashMap<>();
+ for (Instruction instruction : code.instructions()) {
+ if (instruction.isStaticPut()) {
+ StaticPut staticPut = instruction.asStaticPut();
+ DexField field = staticPut.getField();
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField != null
+ && encodedField.field.holder == context
+ && appInfo.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(encodedField)) {
+ staticPutsPerField
+ .computeIfAbsent(encodedField, ignore -> new LinkedList<>())
+ .add(staticPut);
+ }
+ }
+ if (instruction.isJumpInstruction()) {
+ if (!instruction.isGoto() && !instruction.isReturn()) {
+ isStraightLineCode = false;
+ }
}
}
- if (instruction.isJumpInstruction()) {
- if (!instruction.isGoto() && !instruction.isReturn()) {
- isStraightLineCode = false;
- }
- }
- }
- List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
- for (Entry<DexEncodedField, LinkedList<FieldInstruction>> entry : putsPerField.entrySet()) {
- DexEncodedField encodedField = entry.getKey();
- LinkedList<FieldInstruction> fieldPuts = entry.getValue();
- if (fieldPuts.size() > 1) {
- continue;
- }
- FieldInstruction fieldPut = fieldPuts.getFirst();
- if (!isStraightLineCode) {
- if (dominatorTree == null) {
- dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
- }
- if (!dominatorTree.dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+ List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
+ for (Entry<DexEncodedField, LinkedList<StaticPut>> entry : staticPutsPerField.entrySet()) {
+ DexEncodedField encodedField = entry.getKey();
+ LinkedList<StaticPut> staticPuts = entry.getValue();
+ if (staticPuts.size() > 1) {
continue;
}
+ StaticPut staticPut = staticPuts.getFirst();
+ if (!isStraightLineCode) {
+ if (dominatorTree == null) {
+ dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
+ }
+ if (!dominatorTree.dominatesAllOf(staticPut.getBlock(), normalExitBlocks)) {
+ continue;
+ }
+ }
+ if (fieldMaybeReadBeforeInstruction(encodedField, staticPut)) {
+ continue;
+ }
+ updateFieldOptimizationInfo(encodedField, staticPut.value());
}
- if (fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
- continue;
- }
- updateFieldOptimizationInfo(encodedField, fieldPut.value());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
new file mode 100644
index 0000000..0e8da38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, 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.ir.analysis.proto;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+
+// TODO(b/112437944): Should remove the new Builder() instructions from each dynamicMethod() that
+// references a dead proto builder.
+public class GeneratedMessageLiteBuilderShrinker {
+
+ private final ProtoReferences references;
+
+ GeneratedMessageLiteBuilderShrinker(ProtoReferences references) {
+ this.references = references;
+ }
+
+ /** Returns true if an action was deferred. */
+ public boolean deferDeadProtoBuilders(
+ DexProgramClass clazz, DexEncodedMethod context, BooleanSupplier register) {
+ if (references.isDynamicMethod(context) && references.isGeneratedMessageLiteBuilder(clazz)) {
+ return register.getAsBoolean();
+ }
+ return false;
+ }
+
+ public static void addInliningHeuristicsForBuilderInlining(
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Set<DexMethod> alwaysInline,
+ Set<DexMethod> neverInline,
+ Set<DexMethod> bypassClinitforInlining) {
+ new RootSetExtension(appView, alwaysInline, neverInline, bypassClinitforInlining).extend();
+ }
+
+ private static class RootSetExtension {
+
+ private final AppView<? extends AppInfoWithSubtyping> appView;
+ private final ProtoReferences references;
+
+ private final Set<DexMethod> alwaysInline;
+ private final Set<DexMethod> neverInline;
+ private final Set<DexMethod> bypassClinitforInlining;
+
+ RootSetExtension(
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Set<DexMethod> alwaysInline,
+ Set<DexMethod> neverInline,
+ Set<DexMethod> bypassClinitforInlining) {
+ this.appView = appView;
+ this.references = appView.protoShrinker().references;
+ this.alwaysInline = alwaysInline;
+ this.neverInline = neverInline;
+ this.bypassClinitforInlining = bypassClinitforInlining;
+ }
+
+ void extend() {
+ // GeneratedMessageLite heuristics.
+ alwaysInlineCreateBuilderFromGeneratedMessageLite();
+ neverInlineIsInitializedFromGeneratedMessageLite();
+
+ // * extends GeneratedMessageLite heuristics.
+ bypassClinitforInliningNewBuilderMethods();
+ alwaysInlineDynamicMethodFromGeneratedMessageLiteImplementations();
+
+ // GeneratedMessageLite$Builder heuristics.
+ alwaysInlineBuildPartialFromGeneratedMessageLiteBuilder();
+ }
+
+ private void bypassClinitforInliningNewBuilderMethods() {
+ for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
+ DexProgramClass clazz = appView.definitionFor(type).asProgramClass();
+ if (clazz != null) {
+ DexEncodedMethod newBuilderMethod =
+ clazz.lookupDirectMethod(
+ method -> method.method.name == references.newBuilderMethodName);
+ if (newBuilderMethod != null) {
+ bypassClinitforInlining.add(newBuilderMethod.method);
+ }
+ }
+ }
+ }
+
+ private void alwaysInlineBuildPartialFromGeneratedMessageLiteBuilder() {
+ alwaysInline.add(references.generatedMessageLiteBuilderMethods.buildPartialMethod);
+ }
+
+ private void alwaysInlineCreateBuilderFromGeneratedMessageLite() {
+ alwaysInline.add(references.generatedMessageLiteMethods.createBuilderMethod);
+ }
+
+ private void alwaysInlineDynamicMethodFromGeneratedMessageLiteImplementations() {
+ // TODO(b/132600418): We should be able to determine that dynamicMethod() becomes 'SIMPLE'
+ // when the MethodToInvoke argument is MethodToInvoke.NEW_BUILDER.
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
+ alwaysInline.add(
+ dexItemFactory.createMethod(
+ type,
+ dexItemFactory.createProto(
+ dexItemFactory.objectType,
+ references.methodToInvokeType,
+ dexItemFactory.objectType,
+ dexItemFactory.objectType),
+ "dynamicMethod"));
+ }
+ }
+
+ /**
+ * Without this rule, GeneratedMessageLite$Builder.build() becomes too big for class inlining.
+ * TODO(b/112437944): Maybe introduce a -neverinlineinto rule instead?
+ */
+ private void neverInlineIsInitializedFromGeneratedMessageLite() {
+ neverInline.add(references.generatedMessageLiteMethods.isInitializedMethod);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
new file mode 100644
index 0000000..4d04a73
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2019, 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.ir.analysis.proto;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.proto.ProtoReferences.MethodToInvokeMembers;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
+
+/**
+ * Equivalent to the {@link InliningReasonStrategy} in {@link #parent} except for invocations to
+ * dynamicMethod().
+ */
+public class ProtoInliningReasonStrategy implements InliningReasonStrategy {
+
+ private static final int METHOD_TO_INVOKE_ARGUMENT_POSITION_IN_DYNAMIC_METHOD = 1;
+
+ private final InliningReasonStrategy parent;
+ private final ProtoReferences references;
+
+ public ProtoInliningReasonStrategy(AppView<?> appView, InliningReasonStrategy parent) {
+ this.parent = parent;
+ this.references = appView.protoShrinker().references;
+ }
+
+ @Override
+ public Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target) {
+ return references.isDynamicMethod(target)
+ ? computeInliningReasonForDynamicMethod(invoke)
+ : parent.computeInliningReason(invoke, target);
+ }
+
+ private Reason computeInliningReasonForDynamicMethod(InvokeMethod invoke) {
+ Value methodToInvokeValue =
+ invoke
+ .inValues()
+ .get(METHOD_TO_INVOKE_ARGUMENT_POSITION_IN_DYNAMIC_METHOD)
+ .getAliasedValue();
+ if (methodToInvokeValue.isPhi()) {
+ return Reason.NEVER;
+ }
+
+ Instruction methodToInvokeDefinition = methodToInvokeValue.definition;
+ if (!methodToInvokeDefinition.isStaticGet()) {
+ return Reason.NEVER;
+ }
+
+ DexField field = methodToInvokeDefinition.asStaticGet().getField();
+ MethodToInvokeMembers methodToInvokeMembers = references.methodToInvokeMembers;
+ if (methodToInvokeMembers.isMethodToInvokeWithSimpleBody(field)) {
+ return Reason.ALWAYS;
+ }
+
+ assert field.holder != references.methodToInvokeType
+ || methodToInvokeMembers.isMethodToInvokeWithNonSimpleBody(field);
+
+ return Reason.NEVER;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index 0919db9..cedb6ed 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -5,8 +5,10 @@
package com.android.tools.r8.ir.analysis.proto;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -17,12 +19,18 @@
public final DexType extensionRegistryLiteType;
public final DexType generatedExtensionType;
public final DexType generatedMessageLiteType;
+ public final DexType generatedMessageLiteBuilderType;
public final DexType rawMessageInfoType;
public final DexType messageLiteType;
public final DexType methodToInvokeType;
+ public final GeneratedMessageLiteMethods generatedMessageLiteMethods;
+ public final GeneratedMessageLiteBuilderMethods generatedMessageLiteBuilderMethods;
+ public final MethodToInvokeMembers methodToInvokeMembers;
+
public final DexString dynamicMethodName;
public final DexString findLiteExtensionByNumberName;
+ public final DexString newBuilderMethodName;
public final DexProto dynamicMethodProto;
public final DexProto findLiteExtensionByNumberProto;
@@ -42,6 +50,9 @@
factory.createString("Lcom/google/protobuf/GeneratedMessageLite$GeneratedExtension;"));
generatedMessageLiteType =
factory.createType(factory.createString("Lcom/google/protobuf/GeneratedMessageLite;"));
+ generatedMessageLiteBuilderType =
+ factory.createType(
+ factory.createString("Lcom/google/protobuf/GeneratedMessageLite$Builder;"));
rawMessageInfoType =
factory.createType(factory.createString("Lcom/google/protobuf/RawMessageInfo;"));
messageLiteType = factory.createType(factory.createString("Lcom/google/protobuf/MessageLite;"));
@@ -52,6 +63,7 @@
// Names.
dynamicMethodName = factory.createString("dynamicMethod");
findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber");
+ newBuilderMethodName = factory.createString("newBuilder");
// Protos.
dynamicMethodProto =
@@ -73,6 +85,10 @@
factory.createProto(
factory.voidType, messageLiteType, factory.stringType, factory.objectArrayType),
factory.constructorMethodName);
+
+ generatedMessageLiteMethods = new GeneratedMessageLiteMethods(factory);
+ generatedMessageLiteBuilderMethods = new GeneratedMessageLiteBuilderMethods(factory);
+ methodToInvokeMembers = new MethodToInvokeMembers(factory);
}
public boolean isDynamicMethod(DexMethod method) {
@@ -88,7 +104,87 @@
&& method.name.startsWith(findLiteExtensionByNumberName);
}
+ public boolean isGeneratedMessageLiteBuilder(DexProgramClass clazz) {
+ return clazz.superType == generatedMessageLiteBuilderType;
+ }
+
public boolean isMessageInfoConstructionMethod(DexMethod method) {
return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
}
+
+ class GeneratedMessageLiteMethods {
+
+ public final DexMethod createBuilderMethod;
+ public final DexMethod isInitializedMethod;
+
+ private GeneratedMessageLiteMethods(DexItemFactory dexItemFactory) {
+ createBuilderMethod =
+ dexItemFactory.createMethod(
+ generatedMessageLiteType,
+ dexItemFactory.createProto(generatedMessageLiteBuilderType),
+ "createBuilder");
+ isInitializedMethod =
+ dexItemFactory.createMethod(
+ generatedMessageLiteType,
+ dexItemFactory.createProto(dexItemFactory.booleanType),
+ "isInitialized");
+ }
+ }
+
+ class GeneratedMessageLiteBuilderMethods {
+
+ public final DexMethod buildPartialMethod;
+
+ private GeneratedMessageLiteBuilderMethods(DexItemFactory dexItemFactory) {
+ buildPartialMethod =
+ dexItemFactory.createMethod(
+ generatedMessageLiteBuilderType,
+ dexItemFactory.createProto(generatedMessageLiteType),
+ "buildPartial");
+ }
+ }
+
+ public class MethodToInvokeMembers {
+
+ public final DexField buildMessageInfoField;
+ public final DexField getDefaultInstanceField;
+ public final DexField getMemoizedIsInitializedField;
+ public final DexField getParserField;
+ public final DexField newBuilderField;
+ public final DexField newMutableInstanceField;
+ public final DexField setMemoizedIsInitializedField;
+
+ private MethodToInvokeMembers(DexItemFactory dexItemFactory) {
+ buildMessageInfoField =
+ dexItemFactory.createField(methodToInvokeType, methodToInvokeType, "BUILD_MESSAGE_INFO");
+ getDefaultInstanceField =
+ dexItemFactory.createField(
+ methodToInvokeType, methodToInvokeType, "GET_DEFAULT_INSTANCE");
+ getMemoizedIsInitializedField =
+ dexItemFactory.createField(
+ methodToInvokeType, methodToInvokeType, "GET_MEMOIZED_IS_INITIALIZED");
+ getParserField =
+ dexItemFactory.createField(methodToInvokeType, methodToInvokeType, "GET_PARSER");
+ newBuilderField =
+ dexItemFactory.createField(methodToInvokeType, methodToInvokeType, "NEW_BUILDER");
+ newMutableInstanceField =
+ dexItemFactory.createField(
+ methodToInvokeType, methodToInvokeType, "NEW_MUTABLE_INSTANCE");
+ setMemoizedIsInitializedField =
+ dexItemFactory.createField(
+ methodToInvokeType, methodToInvokeType, "SET_MEMOIZED_IS_INITIALIZED");
+ }
+
+ public boolean isMethodToInvokeWithSimpleBody(DexField field) {
+ return field == getDefaultInstanceField
+ || field == getMemoizedIsInitializedField
+ || field == newBuilderField
+ || field == newMutableInstanceField
+ || field == setMemoizedIsInitializedField;
+ }
+
+ public boolean isMethodToInvokeWithNonSimpleBody(DexField field) {
+ return field == buildMessageInfoField || field == getParserField;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index bd7d362..d7ba0cc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -14,6 +14,7 @@
public final ProtoFieldTypeFactory factory;
public final GeneratedExtensionRegistryShrinker generatedExtensionRegistryShrinker;
public final GeneratedMessageLiteShrinker generatedMessageLiteShrinker;
+ public final GeneratedMessageLiteBuilderShrinker generatedMessageLiteBuilderShrinker;
public final ProtoReferences references;
public ProtoShrinker(AppView<AppInfoWithLiveness> appView) {
@@ -29,6 +30,10 @@
appView.options().protoShrinking().enableGeneratedMessageLiteShrinking
? new GeneratedMessageLiteShrinker(appView, decoder, references)
: null;
+ this.generatedMessageLiteBuilderShrinker =
+ appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking
+ ? new GeneratedMessageLiteBuilderShrinker(references)
+ : null;
this.references = references;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 27b3ede..fce7b09 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.analysis.proto.schema;
-import com.android.tools.r8.OptionalBool;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -25,6 +24,7 @@
import com.android.tools.r8.shaking.EnqueuerWorklist;
import com.android.tools.r8.shaking.KeepReason;
import com.android.tools.r8.utils.BitUtils;
+import com.android.tools.r8.utils.OptionalBool;
import com.google.common.collect.Sets;
import java.util.IdentityHashMap;
import java.util.List;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index 35c483b..3449c30 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.analysis.sideeffect;
-import com.android.tools.r8.OptionalBool;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexType;
@@ -16,6 +15,7 @@
import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.OptionalBool;
public class ClassInitializerSideEffectAnalysis {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 51196ea..2b05e0c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.google.common.annotations.VisibleForTesting;
@@ -76,6 +78,19 @@
return new Position(-1, null, method, callerPosition, false);
}
+ public static Position getPositionForInlining(
+ AppView<?> appView, InvokeMethod invoke, DexEncodedMethod context) {
+ Position position = invoke.getPosition();
+ if (position.method == null) {
+ assert position.isNone();
+ position = Position.noneWithMethod(context.method, null);
+ }
+ assert position.callerPosition == null
+ || position.getOutermostCaller().method
+ == appView.graphLense().getOriginalMethodSignature(context.method);
+ return position;
+ }
+
public boolean isNone() {
return line == -1;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 6af3446..189bb92 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -132,7 +132,10 @@
if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) {
// For virtual and interface calls add all potential targets that could be called.
ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
- resolutionResult.forEachTarget(target -> processInvokeWithDynamicDispatch(type, target));
+ DexEncodedMethod target = resolutionResult.getSingleTarget();
+ if (target != null) {
+ processInvokeWithDynamicDispatch(type, target);
+ }
} else {
DexEncodedMethod singleTarget =
appView.appInfo().lookupSingleTarget(type, method, context.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 8907df8..55cabed 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -23,7 +23,6 @@
import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -166,12 +165,15 @@
return code;
}
- private static boolean verifyInvokeInterface(CfCode code, DexDefinitionSupplier definitions) {
+ private static boolean verifyInvokeInterface(CfCode code, AppView<?> appView) {
+ if (appView.options().testing.allowInvokeErrors) {
+ return true;
+ }
for (CfInstruction instruction : code.instructions) {
if (instruction instanceof CfInvoke) {
CfInvoke invoke = (CfInvoke) instruction;
if (invoke.getMethod().holder.isClassType()) {
- DexClass holder = definitions.definitionFor(invoke.getMethod().holder);
+ DexClass holder = appView.definitionFor(invoke.getMethod().holder);
assert holder == null || holder.isInterface() == invoke.isInterface();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 28e95f8..a7c90fe 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -39,8 +39,6 @@
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@@ -251,6 +249,10 @@
&& method.accessFlags.isSynchronized();
}
+ public Origin getOrigin() {
+ return origin;
+ }
+
public DexType getOriginalHolder() {
return code.getOriginalHolder();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 9935044..7a64973 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -209,6 +209,7 @@
// InterfaceMethodRewriter is needed for emulated interfaces.
// LambdaRewriter is needed because if it is missing there are invoke custom on
// default/static interface methods, and this is not supported by the compiler.
+ // DesugaredLibraryAPIConverter is here to duplicate APIs.
// The rest is nulled out. In addition the rewriting logic fails without lambda rewriting.
this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
this.interfaceMethodRewriter =
@@ -216,6 +217,7 @@
? null
: new InterfaceMethodRewriter(appView, this);
this.lambdaRewriter = new LambdaRewriter(appView, this);
+ this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView);
this.twrCloseResourceRewriter = null;
this.lambdaMerger = null;
this.covariantReturnTypeAnnotationTransformer = null;
@@ -234,7 +236,6 @@
this.typeChecker = null;
this.d8NestBasedAccessDesugaring = null;
this.stringSwitchRemover = null;
- this.desugaredLibraryAPIConverter = null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
return;
@@ -662,6 +663,8 @@
assert graphLenseForIR == appView.graphLense();
}
+ // Assure that no more optimization feedback left after primary processing.
+ assert feedback.noUpdatesLeft();
appView.setAllCodeProcessed();
if (libraryMethodOverrideAnalysis != null) {
@@ -784,6 +787,7 @@
}
}
+ // Assure that no more optimization feedback left after post processing.
assert feedback.noUpdatesLeft();
// Check if what we've added to the application builder as synthesized classes are same as
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index e321f56..11c9239 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -81,7 +81,7 @@
}
// When inheriting from a library class, the library class may implement interfaces to
- // desugar. We therefore need to look the interfaces of the library classes.
+ // desugar. We therefore need to look at the interfaces of the library classes.
boolean desugaredLibraryLookup =
superClass != null
&& superClass.isLibraryClass()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index aebe043..4483eca 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -130,16 +130,19 @@
return;
}
DexMethod method = code.method.method;
- if (appView.rewritePrefix.hasRewrittenType(method.holder) || method.holder.isArrayType()) {
+ if (method.holder.isArrayType()
+ || !appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto)
+ || appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getEmulateLibraryInterface()
+ .containsKey(method.holder)) {
return;
}
DexClass dexClass = appView.definitionFor(method.holder);
if (dexClass == null) {
return;
}
- if (!appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto)) {
- return;
- }
if (overridesLibraryMethod(dexClass, method)) {
generateCallBack(dexClass, code.method);
}
@@ -164,17 +167,13 @@
if (dexClass.superType != factory.objectType) {
workList.add(dexClass.superType);
}
- if (!dexClass.isLibraryClass()) {
+ if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
continue;
}
DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method);
if (dexEncodedMethod != null) {
- if (appView
- .options()
- .desugaredLibraryConfiguration
- .getEmulateLibraryInterface()
- .containsKey(dexClass.type)
- || appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
+ // In this case, the object will be wrapped.
+ if (appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
return false;
}
foundOverrideToRewrite = true;
@@ -184,14 +183,10 @@
}
private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
- if (trackedCallBackAPIs != null) {
- trackedCallBackAPIs.add(originalMethod.method);
- }
- DexMethod methodToInstall =
- methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
if (dexClass.isInterface()
&& originalMethod.isDefaultMethod()
- && !appView.options().canUseDefaultAndStaticInterfaceMethods()) {
+ && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
+ || appView.options().isDesugaredLibraryCompilation())) {
// Interface method desugaring has been performed before and all the call-backs will be
// generated in all implementors of the interface. R8 cannot introduce new
// default methods at this point, but R8 does not need to do anything (the interface
@@ -199,17 +194,14 @@
// support the call-back correctly).
return;
}
- CfCode cfCode =
- new APIConverterWrapperCfCodeProvider(
- appView, originalMethod.method, null, this, dexClass.isInterface())
- .generateCfCode();
- DexEncodedMethod newDexEncodedMethod =
- wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
- newDexEncodedMethod.setCode(cfCode, appView);
- addCallBackSignature(dexClass, newDexEncodedMethod);
+ if (trackedCallBackAPIs != null) {
+ trackedCallBackAPIs.add(originalMethod.method);
+ }
+ addCallBackSignature(dexClass, originalMethod);
}
private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
+ assert dexClass.type == method.method.holder;
callBackMethods.putIfAbsent(dexClass, new HashSet<>());
callBackMethods.get(dexClass).add(method);
}
@@ -240,12 +232,31 @@
generateTrackDesugaredAPIWarnings(trackedAPIs, "");
generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
}
- wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
for (DexClass dexClass : callBackMethods.keySet()) {
- Set<DexEncodedMethod> dexEncodedMethods = callBackMethods.get(dexClass);
+ Set<DexEncodedMethod> dexEncodedMethods =
+ generateCallbackMethods(callBackMethods.get(dexClass), dexClass);
dexClass.appendVirtualMethods(dexEncodedMethods);
irConverter.processMethodsConcurrently(dexEncodedMethods, executorService);
}
+ wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
+ }
+
+ private Set<DexEncodedMethod> generateCallbackMethods(
+ Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
+ Set<DexEncodedMethod> newDexEncodedMethods = new HashSet<>();
+ for (DexEncodedMethod originalMethod : originalMethods) {
+ DexMethod methodToInstall =
+ methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
+ CfCode cfCode =
+ new APIConverterWrapperCfCodeProvider(
+ appView, originalMethod.method, null, this, dexClass.isInterface())
+ .generateCfCode();
+ DexEncodedMethod newDexEncodedMethod =
+ wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+ newDexEncodedMethod.setCode(cfCode, appView);
+ newDexEncodedMethods.add(newDexEncodedMethod);
+ }
+ return newDexEncodedMethods;
}
private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index e40252f..5d7c31f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -137,7 +136,7 @@
if (dexClass == null) {
return false;
}
- return dexClass.isLibraryClass();
+ return dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
}
DexType getTypeWrapper(DexType type) {
@@ -153,9 +152,10 @@
}
private DexType createWrapperType(DexType type, String suffix) {
+ String prefix = appView.options().isDesugaredLibraryCompilation() ? "lib$" : "";
return factory.createType(
DescriptorUtils.javaTypeToDescriptor(
- WRAPPER_PREFIX + type.toString().replace('.', '$') + suffix));
+ WRAPPER_PREFIX + prefix + type.toString().replace('.', '$') + suffix));
}
private DexType getWrapper(
@@ -183,7 +183,8 @@
assert pair.getSecond() == null;
DexClass dexClass = appView.definitionFor(type);
// The dexClass should be a library class, so it cannot be null.
- assert dexClass != null && dexClass.isLibraryClass();
+ assert dexClass != null
+ && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
if (dexClass.accessFlags.isFinal()) {
throw appView
.options()
@@ -209,7 +210,7 @@
return synthesizeWrapper(
vivifiedTypeFor(type),
dexClass,
- synthesizeVirtualMethodsForTypeWrapper(dexClass.asLibraryClass(), wrapperField),
+ synthesizeVirtualMethodsForTypeWrapper(dexClass, wrapperField),
wrapperField);
}
@@ -221,7 +222,7 @@
return synthesizeWrapper(
type,
dexClass,
- synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass.asLibraryClass(), wrapperField),
+ synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass, wrapperField),
wrapperField);
}
@@ -274,7 +275,7 @@
}
private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
- DexLibraryClass dexClass, DexEncodedField wrapperField) {
+ DexClass dexClass, DexEncodedField wrapperField) {
List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only types in their signature, but each method the wrapper forwards
@@ -318,7 +319,7 @@
}
private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
- DexLibraryClass dexClass, DexEncodedField wrapperField) {
+ DexClass dexClass, DexEncodedField wrapperField) {
List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only vivified types in their signature, but each method the wrapper
@@ -334,7 +335,8 @@
Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
for (DexEncodedMethod dexEncodedMethod : dexMethods) {
DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
- assert holderClass != null;
+ assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
+ boolean isInterface = holderClass == null || holderClass.isInterface();
DexMethod methodToInstall =
DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
dexEncodedMethod.method, wrapperField.field.holder, appView);
@@ -346,11 +348,7 @@
} else {
cfCode =
new APIConverterWrapperCfCodeProvider(
- appView,
- dexEncodedMethod.method,
- wrapperField.field,
- converter,
- holderClass.isInterface())
+ appView, dexEncodedMethod.method, wrapperField.field, converter, isInterface)
.generateCfCode();
}
DexEncodedMethod newDexEncodedMethod =
@@ -399,7 +397,7 @@
code);
}
- private List<DexEncodedMethod> allImplementedMethods(DexLibraryClass libraryClass) {
+ private List<DexEncodedMethod> allImplementedMethods(DexClass libraryClass) {
LinkedList<DexClass> workList = new LinkedList<>();
List<DexEncodedMethod> implementedMethods = new ArrayList<>();
workList.add(libraryClass);
@@ -423,8 +421,11 @@
}
for (DexType itf : dexClass.interfaces.values) {
DexClass itfClass = appView.definitionFor(itf);
- assert itfClass != null; // Cannot be null since we started from a LibraryClass.
- workList.add(itfClass);
+ // Cannot be null in program since we started from a LibraryClass.
+ assert itfClass != null || appView.options().isDesugaredLibraryCompilation();
+ if (itfClass != null) {
+ workList.add(itfClass);
+ }
}
if (dexClass.superType != factory.objectType) {
DexClass superClass = appView.definitionFor(dexClass.superType);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index be007bb..e1c583d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -3125,9 +3125,12 @@
if (!enumClazz.accessFlags.isFinal()) {
continue;
}
- if (appView.appInfo()
- .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString)
- .asResultOfResolve().method != dexItemFactory.enumMethods.toString) {
+ if (appView
+ .appInfo()
+ .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString)
+ .getSingleTarget()
+ .method
+ != dexItemFactory.enumMethods.toString) {
continue;
}
iterator.replaceCurrentInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index fea8d6c..6b7c389 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstancePut;
@@ -26,12 +27,13 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Monitor;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
+import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,8 +57,8 @@
private final DexEncodedMethod method;
private final IRCode code;
private final MethodProcessor methodProcessor;
- private final CallSiteInformation callSiteInformation;
private final Predicate<DexEncodedMethod> isProcessedConcurrently;
+ private final InliningReasonStrategy reasonStrategy;
private final int inliningInstructionLimit;
private int instructionAllowance;
@@ -73,10 +75,17 @@
this.method = method;
this.code = code;
this.methodProcessor = methodProcessor;
- this.callSiteInformation = methodProcessor.getCallSiteInformation();
this.isProcessedConcurrently = methodProcessor::isProcessedConcurrently;
this.inliningInstructionLimit = inliningInstructionLimit;
this.instructionAllowance = inliningInstructionAllowance;
+
+ DefaultInliningReasonStrategy defaultInliningReasonStrategy =
+ new DefaultInliningReasonStrategy(
+ appView, methodProcessor.getCallSiteInformation(), inliner);
+ this.reasonStrategy =
+ appView.withGeneratedMessageLiteShrinker(
+ ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy),
+ defaultInliningReasonStrategy);
}
@Override
@@ -128,33 +137,6 @@
return false;
}
- private Reason computeInliningReason(DexEncodedMethod target) {
- if (target.getOptimizationInfo().forceInline()
- || (appView.appInfo().hasLiveness()
- && appView.withLiveness().appInfo().forceInline.contains(target.method))) {
- assert !appView.appInfo().neverInline.contains(target.method);
- return Reason.FORCE;
- }
- if (appView.appInfo().hasLiveness()
- && appView.withLiveness().appInfo().alwaysInline.contains(target.method)) {
- return Reason.ALWAYS;
- }
- if (appView.options().disableInliningOfLibraryMethodOverrides
- && target.isLibraryMethodOverride().isTrue()) {
- // This method will always have an implicit call site from the library, so we won't be able to
- // remove it after inlining even if we have single or dual call site information from the
- // program.
- return Reason.SIMPLE;
- }
- if (callSiteInformation.hasSingleCallSite(target.method)) {
- return Reason.SINGLE_CALLER;
- }
- if (isDoubleInliningTarget(target)) {
- return Reason.DUAL_CALLER;
- }
- return Reason.SIMPLE;
- }
-
private boolean canInlineStaticInvoke(
InvokeStatic invoke,
DexEncodedMethod method,
@@ -203,16 +185,14 @@
return true;
}
+ if (appView.rootSet().bypassClinitForInlining.contains(target.method)) {
+ return true;
+ }
+
whyAreYouNotInliningReporter.reportMustTriggerClassInitialization();
return false;
}
- private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
- // 10 is found from measuring.
- return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
- && candidate.getCode().estimatedSizeForInliningAtMost(10);
- }
-
@Override
public boolean passesInliningConstraints(
InvokeMethod invoke,
@@ -364,7 +344,11 @@
return null;
}
- Reason reason = computeInliningReason(singleTarget);
+ Reason reason = reasonStrategy.computeInliningReason(invoke, singleTarget);
+ if (reason == Reason.NEVER) {
+ return null;
+ }
+
if (!singleTarget.isInliningCandidate(
method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) {
return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index ec28d2d..c791772 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -24,12 +24,12 @@
private final AppView<AppInfoWithLiveness> appView;
private final DexEncodedMethod method;
- private final Map<InvokeMethod, Inliner.InliningInfo> invokesToInline;
+ private final Map<? extends InvokeMethod, Inliner.InliningInfo> invokesToInline;
ForcedInliningOracle(
AppView<AppInfoWithLiveness> appView,
DexEncodedMethod method,
- Map<InvokeMethod, Inliner.InliningInfo> invokesToInline) {
+ Map<? extends InvokeMethod, Inliner.InliningInfo> invokesToInline) {
this.appView = appView;
this.method = method;
this.invokesToInline = invokesToInline;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 70013de..c3fe726 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -37,8 +37,6 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.conversion.CodeOptimization;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -46,11 +44,11 @@
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.utils.InternalOptions;
@@ -89,7 +87,10 @@
LensCodeRewriter lensCodeRewriter) {
Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
this.appView = appView;
- this.blacklist = ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
+ this.blacklist =
+ appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
+ ? ImmutableSet.of()
+ : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
this.lambdaMerger = lambdaMerger;
this.lensCodeRewriter = lensCodeRewriter;
this.mainDexClasses = mainDexClasses;
@@ -202,10 +203,8 @@
return target.isSamePackage(context);
}
- synchronized boolean isDoubleInliningTarget(
- CallSiteInformation callSiteInformation, DexEncodedMethod candidate) {
- return callSiteInformation.hasDoubleCallSite(candidate.method)
- || doubleInlineSelectedTargets.contains(candidate);
+ public synchronized boolean isDoubleInlineSelectedTarget(DexEncodedMethod method) {
+ return doubleInlineSelectedTargets.contains(method);
}
synchronized boolean satisfiesRequirementsForDoubleInlining(
@@ -546,7 +545,8 @@
ALWAYS, // Inlinee is marked for inlining due to alwaysinline directive.
SINGLE_CALLER, // Inlinee has precisely one caller.
DUAL_CALLER, // Inlinee has precisely two callers.
- SIMPLE; // Inlinee has simple code suitable for inlining.
+ SIMPLE, // Inlinee has simple code suitable for inlining.
+ NEVER; // Inlinee must not be inlined.
public boolean mustBeInlined() {
// TODO(118734615): Include SINGLE_CALLER and DUAL_CALLER here as well?
@@ -573,18 +573,17 @@
}
InlineeWithReason buildInliningIR(
- DexEncodedMethod context,
- ValueNumberGenerator generator,
AppView<? extends AppInfoWithSubtyping> appView,
- Position callerPosition,
+ InvokeMethod invoke,
+ DexEncodedMethod context,
+ InliningIRProvider inliningIRProvider,
LambdaMerger lambdaMerger,
LensCodeRewriter lensCodeRewriter) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
- Origin origin = appView.appInfo().originFor(target.method.holder);
// Build the IR for a yet not processed method, and perform minimal IR processing.
- IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
+ IRCode code = inliningIRProvider.getInliningIR(invoke, target);
// Insert a null check if this is needed to preserve the implicit null check for the receiver.
// This is only needed if we do not also insert a monitor-enter instruction, since that will
@@ -813,10 +812,11 @@
public void performForcedInlining(
DexEncodedMethod method,
IRCode code,
- Map<InvokeMethod, InliningInfo> invokesToInline) {
-
+ Map<? extends InvokeMethod, InliningInfo> invokesToInline,
+ InliningIRProvider inliningIRProvider) {
ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline);
- performInliningImpl(oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance());
+ performInliningImpl(
+ oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance(), inliningIRProvider);
}
public void performInlining(
@@ -832,7 +832,9 @@
methodProcessor,
options.inliningInstructionLimit,
options.inliningInstructionAllowance - numberOfInstructions(code));
- performInliningImpl(oracle, oracle, method, code, feedback);
+ InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+ assert inliningIRProvider.verifyIRCacheIsEmpty();
+ performInliningImpl(oracle, oracle, method, code, feedback, inliningIRProvider);
}
public DefaultInliningOracle createDefaultOracle(
@@ -856,7 +858,8 @@
InliningOracle oracle,
DexEncodedMethod context,
IRCode code,
- OptimizationFeedback feedback) {
+ OptimizationFeedback feedback,
+ InliningIRProvider inliningIRProvider) {
AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -910,12 +913,7 @@
InlineeWithReason inlinee =
action.buildInliningIR(
- context,
- code.valueNumberGenerator,
- appView,
- getPositionForInlining(invoke, context),
- lambdaMerger,
- lensCodeRewriter);
+ appView, invoke, context, inliningIRProvider, lambdaMerger, lensCodeRewriter);
if (strategy.willExceedBudget(
code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
@@ -997,18 +995,6 @@
assert code.isConsistentSSA();
}
- private Position getPositionForInlining(InvokeMethod invoke, DexEncodedMethod context) {
- Position position = invoke.getPosition();
- if (position.method == null) {
- assert position.isNone();
- position = Position.noneWithMethod(context.method, null);
- }
- assert position.callerPosition == null
- || position.getOutermostCaller().method
- == appView.graphLense().getOriginalMethodSignature(context.method);
- return position;
- }
-
private boolean useReflectiveOperationExceptionOrUnknownClassInCatch(IRCode code) {
for (BasicBlock block : code.blocks) {
for (CatchHandler<BasicBlock> catchHandler : block.getCatchHandlers()) {
@@ -1023,18 +1009,18 @@
return false;
}
- private static DexType getDowncastTypeIfNeeded(
+ private DexType getDowncastTypeIfNeeded(
InliningStrategy strategy, InvokeMethod invoke, DexEncodedMethod target) {
if (invoke.isInvokeMethodWithReceiver()) {
// If the invoke has a receiver but the actual type of the receiver is different
// from the computed target holder, inlining requires a downcast of the receiver.
- DexType assumedReceiverType = strategy.getReceiverTypeIfKnown(invoke);
- if (assumedReceiverType == null) {
+ DexType receiverType = strategy.getReceiverTypeIfKnown(invoke);
+ if (receiverType == null) {
// In case we don't know exact type of the receiver we use declared
// method holder as a fallback.
- assumedReceiverType = invoke.getInvokedMethod().holder;
+ receiverType = invoke.getInvokedMethod().holder;
}
- if (assumedReceiverType != target.method.holder) {
+ if (!appView.appInfo().isSubtype(receiverType, target.method.holder)) {
return target.method.holder;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index f1256d0..4977d4c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -356,7 +356,7 @@
return ConstraintWithTarget.NEVER;
}
- DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve();
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
if (resolutionTarget == null) {
// This will fail at runtime.
return ConstraintWithTarget.NEVER;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index c4d7051..2945c6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.InliningOracle;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -226,7 +227,8 @@
}
// Inline the class instance.
- anyInlinedMethods |= processor.processInlining(code, defaultOracle);
+ InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+ anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
// Restore normality.
Set<Value> affectedValues = Sets.newIdentityHashSet();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 9bad45e..096699b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -39,21 +39,25 @@
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -75,8 +79,8 @@
private DexClass eligibleClassDefinition;
private boolean isDesugaredLambda;
- private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
- = new IdentityHashMap<>();
+ private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
+ new IdentityHashMap<>();
private final Map<InvokeMethod, InliningInfo> extraMethodCalls
= new IdentityHashMap<>();
private final List<Pair<InvokeMethod, Integer>> unusedArguments
@@ -84,6 +88,11 @@
private int estimatedCombinedSizeForInlining = 0;
+ // Set of values that may be an alias of the "root" instance (including the root instance itself).
+ // TODO(b/144825216): Distinguish the "may-aliases" from the "must-aliases" such that the cost
+ // analysis is not optimistic.
+ private final Set<Value> receivers;
+
InlineCandidateProcessor(
AppView<AppInfoWithLiveness> appView,
LambdaRewriter lambdaRewriter,
@@ -99,12 +108,17 @@
this.method = method;
this.root = root;
this.isProcessedConcurrently = isProcessedConcurrently;
+ this.receivers = SetUtils.newIdentityHashSet(root.outValue());
}
int getEstimatedCombinedSizeForInlining() {
return estimatedCombinedSizeForInlining;
}
+ Set<Value> getReceivers() {
+ return receivers;
+ }
+
// Checks if the root instruction defines eligible value, i.e. the value
// exists and we have a definition of its class.
EligibilityStatus isInstanceEligible() {
@@ -258,7 +272,7 @@
*/
InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) {
// No Phi users.
- if (eligibleInstance.numberOfPhiUsers() > 0) {
+ if (eligibleInstance.hasPhiUsers()) {
return eligibleInstance.firstPhiUser(); // Not eligible.
}
@@ -267,10 +281,12 @@
Set<Instruction> indirectUsers = Sets.newIdentityHashSet();
for (Instruction user : currentUsers) {
if (user.isAssume()) {
- if (user.outValue().numberOfPhiUsers() > 0) {
- return user.outValue().firstPhiUser(); // Not eligible.
+ Value alias = user.outValue();
+ if (alias.hasPhiUsers()) {
+ return alias.firstPhiUser(); // Not eligible.
}
- indirectUsers.addAll(user.outValue().uniqueUsers());
+ receivers.add(alias);
+ indirectUsers.addAll(alias.uniqueUsers());
continue;
}
// Field read/write.
@@ -303,9 +319,9 @@
&& root.outValue() == invoke.getReceiver();
if (isCorrespondingConstructorCall) {
InliningInfo inliningInfo =
- isEligibleConstructorCall(user.asInvokeDirect(), singleTarget, defaultOracle);
+ isEligibleConstructorCall(invoke, singleTarget, defaultOracle);
if (inliningInfo != null) {
- methodCallsOnInstance.put(user.asInvokeDirect(), inliningInfo);
+ methodCallsOnInstance.put(invoke, inliningInfo);
continue;
}
}
@@ -365,12 +381,13 @@
// * remove root instruction
//
// Returns `true` if at least one method was inlined.
- boolean processInlining(IRCode code, Supplier<InliningOracle> defaultOracle) {
+ boolean processInlining(
+ IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider) {
// Verify that `eligibleInstance` is not aliased.
assert eligibleInstance == eligibleInstance.getAliasedValue();
replaceUsagesAsUnusedArgument(code);
- boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code);
+ boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
if (anyInlinedMethods) {
// Reset the collections.
methodCallsOnInstance.clear();
@@ -396,7 +413,7 @@
: "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
}
- anyInlinedMethods |= forceInlineDirectMethodInvocations(code);
+ anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
removeAssumeInstructionsLinkedToEligibleInstance();
removeMiscUsages(code);
removeFieldReads(code);
@@ -421,20 +438,25 @@
unusedArguments.clear();
}
- private boolean forceInlineExtraMethodInvocations(IRCode code) {
+ private boolean forceInlineExtraMethodInvocations(
+ IRCode code, InliningIRProvider inliningIRProvider) {
if (extraMethodCalls.isEmpty()) {
return false;
}
// Inline extra methods.
- inliner.performForcedInlining(method, code, extraMethodCalls);
+ inliner.performForcedInlining(method, code, extraMethodCalls, inliningIRProvider);
return true;
}
- private boolean forceInlineDirectMethodInvocations(IRCode code) {
+ private boolean forceInlineDirectMethodInvocations(
+ IRCode code, InliningIRProvider inliningIRProvider) {
if (methodCallsOnInstance.isEmpty()) {
return false;
}
- inliner.performForcedInlining(method, code, methodCallsOnInstance);
+ assert methodCallsOnInstance.keySet().stream()
+ .map(InvokeMethodWithReceiver::getReceiver)
+ .allMatch(receivers::contains);
+ inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
return true;
}
@@ -446,6 +468,7 @@
Assume<?> assumeInstruction = user.asAssume();
Value src = assumeInstruction.src();
Value dest = assumeInstruction.outValue();
+ assert receivers.contains(dest);
assert !dest.hasPhiUsers();
dest.replaceUsers(src);
removeInstruction(user);
@@ -515,17 +538,21 @@
// Replace field reads with appropriate values, insert phis when needed.
private void removeFieldReads(IRCode code) {
- Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
+ TreeSet<InstanceGet> uniqueInstanceGetUsersWithDeterministicOrder =
+ new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber()));
for (Instruction user : eligibleInstance.uniqueUsers()) {
if (user.isInstanceGet()) {
- // Replace a field read with appropriate value.
- replaceFieldRead(code, user.asInstanceGet(), fieldHelpers);
+ if (user.outValue().hasAnyUsers()) {
+ uniqueInstanceGetUsersWithDeterministicOrder.add(user.asInstanceGet());
+ } else {
+ removeInstruction(user);
+ }
continue;
}
if (user.isInstancePut()) {
- // Skip in this iteration since these instructions are needed to
- // properly calculate what value should field reads be replaced with.
+ // Skip in this iteration since these instructions are needed to properly calculate what
+ // value should field reads be replaced with.
continue;
}
@@ -535,6 +562,12 @@
+ "` after inlining: "
+ user);
}
+
+ Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
+ for (InstanceGet user : uniqueInstanceGetUsersWithDeterministicOrder) {
+ // Replace a field read with appropriate value.
+ replaceFieldRead(code, user, fieldHelpers);
+ }
}
private void replaceFieldRead(
@@ -647,7 +680,7 @@
// - if it is a regular chaining pattern where the only users of the out value are receivers to
// other invocations. In that case, we should add all indirect users of the out value to ensure
// they can also be inlined.
- private static boolean isEligibleInvokeWithAllUsersAsReceivers(
+ private boolean isEligibleInvokeWithAllUsersAsReceivers(
ClassInlinerEligibility eligibility,
InvokeMethodWithReceiver invoke,
Set<Instruction> indirectUsers) {
@@ -667,6 +700,10 @@
return false;
}
+ // Since the invoke-instruction may return the receiver, the out-value may be an alias of the
+ // receiver.
+ receivers.add(outValue);
+
Set<Instruction> currentUsers = outValue.uniqueUsers();
while (!currentUsers.isEmpty()) {
Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
@@ -676,6 +713,7 @@
if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
return false;
}
+ receivers.add(outValueAlias);
indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 9161f51..13844a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -670,6 +670,9 @@
// declare a method called checkParameterIsNotNull(parameter, message) or
// throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
private static boolean isKotlinNullCheck(Instruction instr, Value value, AppView<?> appView) {
+ if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
+ return false;
+ }
if (!instr.isInvokeStatic()) {
return false;
}
@@ -814,11 +817,12 @@
appView
.appInfo()
.resolveMethodOnClass(clazz, appView.dexItemFactory().objectMethods.finalize);
- for (DexEncodedMethod target : resolutionResult.asListOfTargets()) {
- if (target.method != dexItemFactory.enumMethods.finalize
- && target.method != dexItemFactory.objectMethods.finalize) {
+
+ DexEncodedMethod target = resolutionResult.getSingleTarget();
+ if (target != null
+ && target.method != dexItemFactory.enumMethods.finalize
+ && target.method != dexItemFactory.objectMethods.finalize) {
return true;
- }
}
return false;
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
new file mode 100644
index 0000000..f28afbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2019, 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.ir.optimize.inliner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class DefaultInliningReasonStrategy implements InliningReasonStrategy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final CallSiteInformation callSiteInformation;
+ private final Inliner inliner;
+
+ public DefaultInliningReasonStrategy(
+ AppView<AppInfoWithLiveness> appView,
+ CallSiteInformation callSiteInformation,
+ Inliner inliner) {
+ this.appView = appView;
+ this.callSiteInformation = callSiteInformation;
+ this.inliner = inliner;
+ }
+
+ @Override
+ public Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target) {
+ if (target.getOptimizationInfo().forceInline()
+ || (appView.appInfo().hasLiveness()
+ && appView.withLiveness().appInfo().forceInline.contains(target.method))) {
+ assert !appView.appInfo().neverInline.contains(target.method);
+ return Reason.FORCE;
+ }
+ if (appView.appInfo().hasLiveness()
+ && appView.withLiveness().appInfo().alwaysInline.contains(target.method)) {
+ return Reason.ALWAYS;
+ }
+ if (appView.options().disableInliningOfLibraryMethodOverrides
+ && target.isLibraryMethodOverride().isTrue()) {
+ // This method will always have an implicit call site from the library, so we won't be able to
+ // remove it after inlining even if we have single or dual call site information from the
+ // program.
+ return Reason.SIMPLE;
+ }
+ if (callSiteInformation.hasSingleCallSite(target.method)) {
+ return Reason.SINGLE_CALLER;
+ }
+ if (isDoubleInliningTarget(target)) {
+ return Reason.DUAL_CALLER;
+ }
+ return Reason.SIMPLE;
+ }
+
+ private boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
+ // 10 is found from measuring.
+ if (callSiteInformation.hasDoubleCallSite(candidate.method)
+ || inliner.isDoubleInlineSelectedTarget(candidate)) {
+ return candidate.getCode().estimatedSizeForInliningAtMost(10);
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
new file mode 100644
index 0000000..580e1b2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2019, 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.ir.optimize.inliner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.origin.Origin;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class InliningIRProvider {
+
+ private final AppView<?> appView;
+ private final DexEncodedMethod context;
+ private final ValueNumberGenerator valueNumberGenerator;
+
+ private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>();
+
+ public InliningIRProvider(AppView<?> appView, DexEncodedMethod context, IRCode code) {
+ this.appView = appView;
+ this.context = context;
+ this.valueNumberGenerator = code.valueNumberGenerator;
+ }
+
+ public IRCode getInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
+ IRCode cached = cache.remove(invoke);
+ if (cached != null) {
+ return cached;
+ }
+ Position position = Position.getPositionForInlining(appView, invoke, context);
+ Origin origin = appView.appInfo().originFor(method.method.holder);
+ return method.buildInliningIR(context, appView, valueNumberGenerator, position, origin);
+ }
+
+ public void cacheInliningIR(InvokeMethod invoke, IRCode code) {
+ IRCode existing = cache.put(invoke, code);
+ assert existing == null;
+ }
+
+ public boolean verifyIRCacheIsEmpty() {
+ assert cache.isEmpty();
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
new file mode 100644
index 0000000..5450988
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2019, 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.ir.optimize.inliner;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+
+public interface InliningReasonStrategy {
+
+ Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 9a4565c..b9ef1c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.ir.optimize.staticizer;
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
@@ -562,14 +560,12 @@
Value outValue = invoke.outValue();
DexType returnType = method.proto.returnType;
Value newOutValue =
- returnType.isVoidType()
+ returnType.isVoidType() || outValue == null
? null
: code.createValue(
TypeLatticeElement.fromDexType(
- returnType,
- outValue == null ? maybeNull() : outValue.getTypeLattice().nullability(),
- appView),
- outValue == null ? null : outValue.getLocalInfo());
+ returnType, outValue.getTypeLattice().nullability(), appView),
+ outValue.getLocalInfo());
it.replaceCurrentInstruction(new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
}
continue;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 92ba6a0..a83c407 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,11 +4,22 @@
package com.android.tools.r8.kotlin;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
+ private KmClass kmClass;
+
static KotlinClass fromKotlinClassMetadata(
KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
assert kotlinClassMetadata instanceof KotlinClassMetadata.Class;
@@ -24,7 +35,35 @@
void processMetadata() {
assert !isProcessed;
isProcessed = true;
- // TODO(b/70169921): once migration is complete, use #toKmClass and store a mutable model.
+ kmClass = metadata.toKmClass();
+ }
+
+ @Override
+ void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ kmClass.getSupertypes().removeIf(
+ kmType -> {
+ Box<Boolean> isLive = new Box<>(false);
+ kmType.accept(new KmTypeVisitor() {
+ @Override
+ public void visitClass(String name) {
+ String descriptor = DescriptorUtils.javaTypeToDescriptorIfValidJavaType(name);
+ if (descriptor != null) {
+ DexType type = appView.dexItemFactory().createType(name);
+ DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+ isLive.set(appView.appInfo().isLiveProgramType(renamedType));
+ }
+ }
+ });
+ return !isLive.get();
+ }
+ );
+ }
+
+ @Override
+ KotlinClassHeader createHeader() {
+ KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
+ kmClass.accept(writer);
+ return writer.write().getHeader();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 0bd545c..6429a50 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,6 +4,11 @@
package com.android.tools.r8.kotlin;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
@@ -27,6 +32,16 @@
}
@Override
+ void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ throw new Unreachable(toString());
+ }
+
+ @Override
+ KotlinClassHeader createHeader() {
+ throw new Unreachable(toString());
+ }
+
+ @Override
public Kind getKind() {
return Kind.Facade;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 6004201..dc3161c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -79,11 +79,11 @@
if (kMetadata instanceof KotlinClassMetadata.Class) {
return KotlinClass.fromKotlinClassMetadata(kMetadata, clazz);
} else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
- return KotlinFile.fromKotlinClassMetadata(kMetadata);
+ return KotlinFile.fromKotlinClassMetadata(kMetadata, clazz);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
return KotlinClassFacade.fromKotlinClassMetadata(kMetadata);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
- return KotlinClassPart.fromKotlinClassMetdata(kMetadata);
+ return KotlinClassPart.fromKotlinClassMetadata(kMetadata);
} else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz);
} else {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 1ae3db8..96c1022 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,11 +4,18 @@
package com.android.tools.r8.kotlin;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.KmPackage;
+import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
- static KotlinClassPart fromKotlinClassMetdata(KotlinClassMetadata kotlinClassMetadata) {
+ private KmPackage kmPackage;
+
+ static KotlinClassPart fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassPart;
KotlinClassMetadata.MultiFileClassPart multiFileClassPart =
(KotlinClassMetadata.MultiFileClassPart) kotlinClassMetadata;
@@ -23,7 +30,20 @@
void processMetadata() {
assert !isProcessed;
isProcessed = true;
- // TODO(b/70169921): once migration is complete, use #toKmPackage and store a mutable model.
+ kmPackage = metadata.toKmPackage();
+ }
+
+ @Override
+ void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ // TODO(b/70169921): no idea yet!
+ assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+ : toString();
+ }
+
+ @Override
+ KotlinClassHeader createHeader() {
+ // TODO(b/70169921): may need to update if `rewrite` is implemented.
+ return metadata.getHeader();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index c22dcdb..e2bb332 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,26 +4,48 @@
package com.android.tools.r8.kotlin;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.KmPackage;
+import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
- static KotlinFile fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+ private KmPackage kmPackage;
+
+ static KotlinFile fromKotlinClassMetadata(
+ KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
assert kotlinClassMetadata instanceof KotlinClassMetadata.FileFacade;
KotlinClassMetadata.FileFacade fileFacade =
(KotlinClassMetadata.FileFacade) kotlinClassMetadata;
- return new KotlinFile(fileFacade);
+ return new KotlinFile(fileFacade, clazz);
}
- private KotlinFile(KotlinClassMetadata.FileFacade metadata) {
- super(metadata);
+ private KotlinFile(KotlinClassMetadata.FileFacade metadata, DexClass clazz) {
+ super(metadata, clazz);
}
@Override
void processMetadata() {
assert !isProcessed;
isProcessed = true;
- // TODO(b/70169921): once migration is complete, use #toKmPackage and store a mutable model.
+ kmPackage = metadata.toKmPackage();
+ }
+
+ @Override
+ void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ // TODO(b/70169921): no idea yet!
+ assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+ : toString();
+ }
+
+ @Override
+ KotlinClassHeader createHeader() {
+ // TODO(b/70169921): may need to update if `rewrite` is implemented.
+ return metadata.getHeader();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 6603357..ac69eff 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,7 +4,11 @@
package com.android.tools.r8.kotlin;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
// Provides access to kotlin information.
@@ -20,11 +24,17 @@
KotlinInfo(MetadataKind metadata, DexClass clazz) {
this.metadata = metadata;
this.clazz = clazz;
+ processMetadata();
}
// Subtypes will define how to process the given metadata.
abstract void processMetadata();
+ // Subtypes will define how to rewrite metadata after shrinking and minification.
+ abstract void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens);
+
+ abstract KotlinClassHeader createHeader();
+
public enum Kind {
Class, File, Synthetic, Part, Facade
}
@@ -70,4 +80,9 @@
public KotlinClassFacade asClassFacade() {
return null;
}
+
+ @Override
+ public String toString() {
+ return clazz.toSourceString() + ": " + metadata.toString();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
new file mode 100644
index 0000000..054c5e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2019, 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.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+
+public class KotlinMetadataRewriter {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final NamingLens lens;
+ private final DexItemFactory factory;
+ private final Kotlin kotlin;
+
+ public KotlinMetadataRewriter(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ this.appView = appView;
+ this.lens = lens;
+ this.factory = appView.dexItemFactory();
+ this.kotlin = factory.kotlin;
+ }
+
+ public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexType type) {
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz == null) {
+ return;
+ }
+ removeKotlinMetadataFromRenamedClass(appView, clazz);
+ }
+
+ public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexClass clazz) {
+ // Remove @Metadata in DexAnnotation form if a class is renamed.
+ clazz.annotations = clazz.annotations.keepIf(anno -> isNotKotlinMetadata(appView, anno));
+ // Clear associated {@link KotlinInfo} to avoid accidentally deserialize it back to
+ // DexAnnotation we've just removed above.
+ if (clazz.isProgramClass()) {
+ clazz.asProgramClass().setKotlinInfo(null);
+ }
+ }
+
+ private static boolean isNotKotlinMetadata(AppView<?> appView, DexAnnotation annotation) {
+ return annotation.annotation.type
+ != appView.dexItemFactory().kotlin.metadata.kotlinMetadataType;
+ }
+
+ public void run(ExecutorService executorService) throws ExecutionException {
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz -> {
+ KotlinInfo<?> kotlinInfo = clazz.getKotlinInfo();
+ if (kotlinInfo != null) {
+ assert kotlinInfo.isClass()
+ || kotlinInfo.isSyntheticClass()
+ || kotlinInfo.isFile(); // e.g., B.kt becomes class `BKt`
+ // If @Metadata is still associated, this class should not be renamed
+ // (by {@link ClassNameMinifier} of course).
+ assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+ : clazz.toSourceString() + " != "
+ + lens.lookupType(clazz.type, appView.dexItemFactory());
+
+ DexAnnotation oldMeta =
+ clazz.annotations.getFirstMatching(kotlin.metadata.kotlinMetadataType);
+ // If @Metadata is already gone, e.g., by {@link AnnotationRemover} if type Metadata is
+ // determined as dead (e.g., due to no keep rule), nothing to do.
+ if (oldMeta == null) {
+ return;
+ }
+
+ kotlinInfo.rewrite(appView, lens);
+
+ DexAnnotation newMeta = createKotlinMetadataAnnotation(kotlinInfo.createHeader());
+ clazz.annotations = clazz.annotations.rewrite(anno -> anno == oldMeta ? newMeta : anno);
+ }
+ },
+ executorService
+ );
+ }
+
+ private DexAnnotation createKotlinMetadataAnnotation(KotlinClassHeader header) {
+ List<DexAnnotationElement> elements = new ArrayList<>();
+ elements.add(
+ new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
+ elements.add(
+ new DexAnnotationElement(
+ kotlin.metadata.metadataVersion, createIntArray(header.getMetadataVersion())));
+ elements.add(
+ new DexAnnotationElement(
+ kotlin.metadata.bytecodeVersion, createIntArray(header.getBytecodeVersion())));
+ elements.add(
+ new DexAnnotationElement(kotlin.metadata.data1, createStringArray(header.getData1())));
+ elements.add(
+ new DexAnnotationElement(kotlin.metadata.data2, createStringArray(header.getData2())));
+ elements.add(
+ new DexAnnotationElement(
+ kotlin.metadata.extraString,
+ new DexValueString(factory.createString(header.getExtraString()))));
+ elements.add(
+ new DexAnnotationElement(
+ kotlin.metadata.packageName,
+ new DexValueString(factory.createString(header.getPackageName()))));
+ elements.add(
+ new DexAnnotationElement(
+ kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
+ DexEncodedAnnotation encodedAnnotation =
+ new DexEncodedAnnotation(
+ kotlin.metadata.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY));
+ return new DexAnnotation(DexAnnotation.VISIBILITY_RUNTIME, encodedAnnotation);
+ }
+
+ private DexValueArray createIntArray(int[] data) {
+ DexValue[] values = new DexValue[data.length];
+ for (int i = 0; i < data.length; i++) {
+ values[i] = DexValueInt.create(data[i]);
+ }
+ return new DexValueArray(values);
+ }
+
+ private DexValueArray createStringArray(String[] data) {
+ DexValue[] values = new DexValue[data.length];
+ for (int i = 0; i < data.length; i++) {
+ values[i] = new DexValueString(factory.createString(data[i]));
+ }
+ return new DexValueArray(values);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index fa65464..59f7dce 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -4,7 +4,11 @@
package com.android.tools.r8.kotlin;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> {
@@ -22,16 +26,17 @@
KotlinClassMetadata.SyntheticClass syntheticClass =
(KotlinClassMetadata.SyntheticClass) kotlinClassMetadata;
if (isKotlinStyleLambda(syntheticClass, kotlin, clazz)) {
- return new KotlinSyntheticClass(Flavour.KotlinStyleLambda, syntheticClass);
+ return new KotlinSyntheticClass(Flavour.KotlinStyleLambda, syntheticClass, clazz);
} else if (isJavaStyleLambda(syntheticClass, kotlin, clazz)) {
- return new KotlinSyntheticClass(Flavour.JavaStyleLambda, syntheticClass);
+ return new KotlinSyntheticClass(Flavour.JavaStyleLambda, syntheticClass, clazz);
} else {
- return new KotlinSyntheticClass(Flavour.Unclassified, syntheticClass);
+ return new KotlinSyntheticClass(Flavour.Unclassified, syntheticClass, clazz);
}
}
- private KotlinSyntheticClass(Flavour flavour, KotlinClassMetadata.SyntheticClass metadata) {
- super(metadata);
+ private KotlinSyntheticClass(
+ Flavour flavour, KotlinClassMetadata.SyntheticClass metadata, DexClass clazz) {
+ super(metadata, clazz);
this.flavour = flavour;
}
@@ -40,10 +45,23 @@
assert !isProcessed;
isProcessed = true;
if (metadata.isLambda()) {
- // TODO(b/70169921): once migration is complete, use #toKmLambda and store a mutable model.
+ // TODO(b/70169921): Use #toKmLambda to store a mutable model if needed.
}
}
+ @Override
+ void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+ // TODO(b/70169921): no idea yet!
+ assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+ : toString();
+ }
+
+ @Override
+ KotlinClassHeader createHeader() {
+ // TODO(b/70169921): may need to update if `rewrite` is implemented.
+ return metadata.getHeader();
+ }
+
public boolean isLambda() {
return isKotlinStyleLambda() || isJavaStyleLambda();
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index ada50fa..d0240e9 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -10,7 +10,6 @@
import static com.android.tools.r8.utils.DescriptorUtils.getPackageBinaryNameFromJavaType;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,6 +17,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardPackageNameList;
@@ -110,16 +110,17 @@
timing.begin("rename-classes");
for (DexClass clazz : classes) {
if (!renaming.containsKey(clazz.type)) {
- clazz.annotations = clazz.annotations.keepIf(this::isNotKotlinMetadata);
DexString renamed = computeName(clazz.type);
renaming.put(clazz.type, renamed);
+ KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
// If the class is a member class and it has used $ separator, its renamed name should have
// the same separator (as long as inner-class attribute is honored).
assert !keepInnerClassStructure
|| !clazz.isMemberClass()
|| !clazz.type.getInternalName().contains(String.valueOf(INNER_CLASS_SEPARATOR))
|| renamed.toString().contains(String.valueOf(INNER_CLASS_SEPARATOR))
- || classNamingStrategy.isRenamedByApplyMapping(clazz.type);
+ || classNamingStrategy.isRenamedByApplyMapping(clazz.type)
+ : clazz.toSourceString() + " -> " + renamed;
}
}
timing.end();
@@ -327,6 +328,7 @@
// and then use that renamed name as a base prefix for the current inner class.
renamed = computeName(outer);
renaming.put(outer, renamed);
+ KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, outer);
}
String binaryName = getClassBinaryNameFromDescriptor(renamed.toString());
state = new Namespace(binaryName, innerClassSeparator);
@@ -442,9 +444,4 @@
}
return packagePrefix.substring(0, i);
}
-
- private boolean isNotKotlinMetadata(DexAnnotation annotation) {
- return annotation.annotation.type
- != appView.dexItemFactory().kotlin.metadata.kotlinMetadataType;
- }
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index ccac2d6..c6b7ee0 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -185,6 +185,7 @@
if (isDirectMethodCall) {
return virtualNameCount + directNameCount++;
} else {
+ // TODO(b/144877828): is it guaranteed?
assert directNameCount == 0;
return virtualNameCount++;
}
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 7423075..7d93488 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -98,10 +98,6 @@
if (renamed != null) {
return renamed;
}
- // TODO(b/144339115): Don't allocate in the item factory during resolution!
- if (method.holder == appView.dexItemFactory().methodHandleType) {
- return method.name;
- }
// If the method does not have a direct renaming, return the resolutions mapping.
ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
if (resolutionResult.hasSingleTarget()) {
@@ -111,7 +107,7 @@
// to the failure.
if (resolutionResult.isFailedResolution()) {
List<DexEncodedMethod> targets = new ArrayList<>();
- resolutionResult.asFailedResolution().forEachFailureDependency(clazz -> {}, targets::add);
+ resolutionResult.asFailedResolution().forEachFailureDependency(targets::add);
if (!targets.isEmpty()) {
DexString firstRename = renaming.get(targets.get(0).method);
assert targets.stream().allMatch(target -> renaming.get(target.method) == firstRename);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 8dfe02c..eb3a614 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
import com.android.tools.r8.naming.ClassNameMinifier.PackageNamingStrategy;
@@ -87,6 +88,11 @@
timing.begin("MinifyIdentifiers");
new IdentifierMinifier(appView, lens).run(executorService);
timing.end();
+
+ timing.begin("MinifyKotlinMetadata");
+ new KotlinMetadataRewriter(appView, lens).run(executorService);
+ timing.end();
+
return lens;
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 8db8e01..92df84f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -165,6 +166,10 @@
new IdentifierMinifier(appView, lens).run(executorService);
timing.end();
+ timing.begin("MinifyKotlinMetadata");
+ new KotlinMetadataRewriter(appView, lens).run(executorService);
+ timing.end();
+
return lens;
}
@@ -180,9 +185,12 @@
Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
if (classNaming != null) {
- // TODO(b/133091438) assert that !dexClass.isLibaryClass();
+ // TODO(b/133091438) assert that !dexClass.isLibraryClass();
DexString mappedName = factory.createString(classNaming.renamedName);
checkAndAddMappedNames(type, mappedName, classNaming.position);
+ if (dexClass != null) {
+ KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, dexClass);
+ }
classNaming.forAllMemberNaming(
memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
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 760904a..703deea 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -107,15 +107,15 @@
}
private DexEncodedMethod classLookup(DexMethod method) {
- return appView.appInfo().resolveMethodOnClass(method.holder, method).asResultOfResolve();
+ return appView.appInfo().resolveMethodOnClass(method.holder, method).getSingleTarget();
}
private DexEncodedMethod interfaceLookup(DexMethod method) {
- return appView.appInfo().resolveMethodOnInterface(method.holder, method).asResultOfResolve();
+ return appView.appInfo().resolveMethodOnInterface(method.holder, method).getSingleTarget();
}
private DexEncodedMethod anyLookup(DexMethod method) {
- return appView.appInfo().resolveMethod(method.holder, method).asResultOfResolve();
+ return appView.appInfo().resolveMethod(method.holder, method).getSingleTarget();
}
private void computeMethodRebinding(
diff --git a/src/main/java/com/android/tools/r8/references/ClassReference.java b/src/main/java/com/android/tools/r8/references/ClassReference.java
index 50ce220..1159ec3 100644
--- a/src/main/java/com/android/tools/r8/references/ClassReference.java
+++ b/src/main/java/com/android/tools/r8/references/ClassReference.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.references;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.DescriptorUtils;
/** Reference to a class type or interface type. */
@Keep
@@ -19,6 +20,10 @@
return new ClassReference(descriptor);
}
+ public String getBinaryName() {
+ return DescriptorUtils.getBinaryNameFromDescriptor(descriptor);
+ }
+
@Override
public boolean isClass() {
return true;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 319cbaf..f5b0de8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -758,24 +758,20 @@
return false;
}
- public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
- assert checkIfObsolete();
- assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
- if (!isPinned(field.field)) {
- FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
- return fieldAccessInfo != null
- && fieldAccessInfo.isWritten()
- && !fieldAccessInfo.isWrittenOutside(method);
- }
- return false;
- }
-
public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
- DexEncodedMethod staticInitializer =
- definitionFor(field.field.holder).asProgramClass().getClassInitializer();
- return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
+ if (!isPinned(field.field)) {
+ DexEncodedMethod staticInitializer =
+ definitionFor(field.field.holder).asProgramClass().getClassInitializer();
+ if (staticInitializer != null) {
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+ return fieldAccessInfo != null
+ && fieldAccessInfo.isWritten()
+ && !fieldAccessInfo.isWrittenOutside(staticInitializer);
+ }
+ }
+ return false;
}
public boolean mayPropagateValueFor(DexReference reference) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
new file mode 100644
index 0000000..773db00
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2019, 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.shaking;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import java.util.function.Consumer;
+
+public abstract class DelayedRootSetActionItem {
+
+ public boolean isInterfaceMethodSyntheticBridgeAction() {
+ return false;
+ }
+
+ public InterfaceMethodSyntheticBridgeAction asInterfaceMethodSyntheticBridgeAction() {
+ return null;
+ }
+
+ public static class InterfaceMethodSyntheticBridgeAction extends DelayedRootSetActionItem {
+ private final DexEncodedMethod methodToKeep;
+ private final DexEncodedMethod singleTarget;
+ private final Consumer<RootSetBuilder> action;
+
+ InterfaceMethodSyntheticBridgeAction(
+ DexEncodedMethod methodToKeep,
+ DexEncodedMethod singleTarget,
+ Consumer<RootSetBuilder> action) {
+ this.methodToKeep = methodToKeep;
+ this.singleTarget = singleTarget;
+ this.action = action;
+ }
+
+ public DexEncodedMethod getMethodToKeep() {
+ return methodToKeep;
+ }
+
+ public DexEncodedMethod getSingleTarget() {
+ return singleTarget;
+ }
+
+ public Consumer<RootSetBuilder> getAction() {
+ return action;
+ }
+
+ @Override
+ public boolean isInterfaceMethodSyntheticBridgeAction() {
+ return true;
+ }
+
+ @Override
+ public InterfaceMethodSyntheticBridgeAction asInterfaceMethodSyntheticBridgeAction() {
+ return this;
+ }
+ }
+}
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 24108b7..f7cf47c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -57,11 +56,13 @@
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.EnqueuerWorklist.Action;
+import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
+import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SetUtils;
@@ -76,6 +77,7 @@
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.lang.reflect.InvocationHandler;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
@@ -743,10 +745,35 @@
boolean traceInvokeDirect(
DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+ boolean skipTracing =
+ registerDeferredActionForDeadProtoBuilder(
+ invokedMethod.holder,
+ currentMethod,
+ () ->
+ workList.enqueueTraceInvokeDirectAction(
+ invokedMethod, currentHolder, currentMethod));
+ if (skipTracing) {
+ return false;
+ }
+
return traceInvokeDirect(
invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
}
+ /** Returns true if a deferred action was registered. */
+ private boolean registerDeferredActionForDeadProtoBuilder(
+ DexType type, DexEncodedMethod currentMethod, Action action) {
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz != null) {
+ return appView.withGeneratedMessageLiteBuilderShrinker(
+ shrinker ->
+ shrinker.deferDeadProtoBuilders(
+ clazz, currentMethod, () -> liveTypes.registerDeferredAction(clazz, action)),
+ false);
+ }
+ return false;
+ }
+
boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
return traceInvokeDirect(
invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
@@ -877,6 +904,13 @@
}
boolean traceNewInstance(DexType type, DexEncodedMethod currentMethod) {
+ boolean skipTracing =
+ registerDeferredActionForDeadProtoBuilder(
+ type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, currentMethod));
+ if (skipTracing) {
+ return false;
+ }
+
return traceNewInstance(type, currentMethod, KeepReason.instantiatedIn(currentMethod));
}
@@ -1626,22 +1660,18 @@
private void markResolutionAsLive(DexClass libraryClass, ResolutionResult resolution) {
if (resolution.isValidVirtualTarget(options)) {
- resolution.forEachTarget(
- target -> {
- if (!target.isAbstract()) {
- DexClass targetHolder = appView.definitionFor(target.method.holder);
- if (targetHolder != null && targetHolder.isProgramClass()) {
- DexProgramClass programClass = targetHolder.asProgramClass();
- if (shouldMarkLibraryMethodOverrideAsReachable(programClass, target)) {
- target.setLibraryMethodOverride();
- markVirtualMethodAsLive(
- programClass,
- target,
- KeepReason.isLibraryMethod(programClass, libraryClass.type));
- }
- }
- }
- });
+ DexEncodedMethod target = resolution.getSingleTarget();
+ if (!target.isAbstract()) {
+ DexClass targetHolder = appView.definitionFor(target.method.holder);
+ if (targetHolder != null && targetHolder.isProgramClass()) {
+ DexProgramClass programClass = targetHolder.asProgramClass();
+ if (shouldMarkLibraryMethodOverrideAsReachable(programClass, target)) {
+ target.setLibraryMethodOverride();
+ markVirtualMethodAsLive(
+ programClass, target, KeepReason.isLibraryMethod(programClass, libraryClass.type));
+ }
+ }
+ }
}
}
@@ -2077,9 +2107,6 @@
private void markFailedResolutionTargets(
FailedResolutionResult failedResolution, KeepReason reason) {
failedResolution.forEachFailureDependency(
- clazz -> {
- throw new Unimplemented();
- },
method -> {
DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
if (clazz != null) {
@@ -2119,8 +2146,8 @@
// See <a
// href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial">
// the JVM spec for invoke-special.
- DexEncodedMethod resolutionTarget = appInfo.resolveMethod(method.holder, method)
- .asResultOfResolve();
+ DexEncodedMethod resolutionTarget =
+ appInfo.resolveMethod(method.holder, method).getSingleTarget();
if (resolutionTarget == null) {
brokenSuperInvokes.add(method);
reportMissingMethod(method);
@@ -2295,7 +2322,7 @@
numOfLiveItems += (long) liveMethods.items.size();
numOfLiveItems += (long) liveFields.items.size();
while (!workList.isEmpty()) {
- Action action = workList.poll();
+ EnqueuerAction action = workList.poll();
action.run(this);
}
@@ -2314,7 +2341,7 @@
activeIfRules.computeIfAbsent(wrap, ignore -> new LinkedHashSet<>()).add(ifRule);
}
}
- RootSetBuilder consequentSetBuilder = new RootSetBuilder(appView, null);
+ RootSetBuilder consequentSetBuilder = new RootSetBuilder(appView);
IfRuleEvaluator ifRuleEvaluator =
new IfRuleEvaluator(
appView,
@@ -2326,22 +2353,7 @@
mode,
consequentSetBuilder,
targetedMethods.getItems());
- ConsequentRootSet consequentRootSet = ifRuleEvaluator.run();
- // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
- rootSet.addConsequentRootSet(consequentRootSet);
- enqueueRootItems(consequentRootSet.noShrinking);
- // TODO(b/132828740): Seems incorrect that the precondition is not always met here.
- consequentRootSet.dependentNoShrinking.forEach(
- (precondition, dependentItems) -> enqueueRootItems(dependentItems));
- // Check for compatibility rules indicating that the holder must be implicitly kept.
- if (forceProguardCompatibility) {
- consequentRootSet.dependentKeepClassCompatRule.forEach(
- (precondition, compatRules) -> {
- assert precondition.isDexType();
- DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
- compatEnqueueHolderIfDependentNonStaticMember(preconditionHolder, compatRules);
- });
- }
+ addConsequentRootSet(ifRuleEvaluator.run(), false);
if (!workList.isEmpty()) {
continue;
}
@@ -2364,6 +2376,13 @@
continue;
}
+ addConsequentRootSet(computeDelayedInterfaceMethodSyntheticBridges(), true);
+ rootSet.delayedRootSetActionItems.clear();
+
+ if (!workList.isEmpty()) {
+ continue;
+ }
+
// Reached the fixpoint.
break;
}
@@ -2392,6 +2411,55 @@
unpinLambdaMethods();
}
+ private void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
+ // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
+ rootSet.addConsequentRootSet(consequentRootSet, addNoShrinking);
+ enqueueRootItems(consequentRootSet.noShrinking);
+ // TODO(b/132828740): Seems incorrect that the precondition is not always met here.
+ consequentRootSet.dependentNoShrinking.forEach(
+ (precondition, dependentItems) -> enqueueRootItems(dependentItems));
+ // Check for compatibility rules indicating that the holder must be implicitly kept.
+ if (forceProguardCompatibility) {
+ consequentRootSet.dependentKeepClassCompatRule.forEach(
+ (precondition, compatRules) -> {
+ assert precondition.isDexType();
+ DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
+ compatEnqueueHolderIfDependentNonStaticMember(preconditionHolder, compatRules);
+ });
+ }
+ }
+
+ private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() {
+ RootSetBuilder builder = new RootSetBuilder(appView);
+ for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) {
+ if (delayedRootSetActionItem.isInterfaceMethodSyntheticBridgeAction()) {
+ handleInterfaceMethodSyntheticBridgeAction(
+ delayedRootSetActionItem.asInterfaceMethodSyntheticBridgeAction(), builder);
+ }
+ }
+ return builder.buildConsequentRootSet();
+ }
+
+ private void handleInterfaceMethodSyntheticBridgeAction(
+ InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) {
+ if (rootSet.noShrinking.containsKey(action.getSingleTarget().method)) {
+ return;
+ }
+ DexEncodedMethod methodToKeep = action.getMethodToKeep();
+ DexClass clazz = getProgramClassOrNull(methodToKeep.method.holder);
+ if (methodToKeep != action.getSingleTarget()) {
+ // Insert a bridge method.
+ if (appView.definitionFor(methodToKeep.method) == null) {
+ clazz.appendVirtualMethod(methodToKeep);
+ appView.appInfo().invalidateTypeCacheFor(methodToKeep.method.holder);
+ // The addition of a bridge method can lead to a change of resolution, thus the cached
+ // resolution targets are invalid.
+ virtualTargetsMarkedAsReachable.remove(methodToKeep.method);
+ }
+ }
+ action.getAction().accept(builder);
+ }
+
private void unpinLambdaMethods() {
for (DexMethod method : lambdaMethodsTargetedByInvokeDynamic) {
pinnedItems.remove(method);
@@ -2911,16 +2979,29 @@
private static class SetWithReportedReason<T> {
private final Set<T> items = Sets.newIdentityHashSet();
+ private final Map<T, List<Action>> deferredActions = new IdentityHashMap<>();
boolean add(T item, KeepReasonWitness witness) {
assert witness != null;
- return items.add(item);
+ if (items.add(item)) {
+ deferredActions.getOrDefault(item, Collections.emptyList()).forEach(Action::execute);
+ return true;
+ }
+ return false;
}
boolean contains(T item) {
return items.contains(item);
}
+ boolean registerDeferredAction(T item, Action action) {
+ if (!items.contains(item)) {
+ deferredActions.computeIfAbsent(item, ignore -> new ArrayList<>()).add(action);
+ return true;
+ }
+ return false;
+ }
+
Set<T> getItems() {
return Collections.unmodifiableSet(items);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 5b3be60..ff6ece0 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -17,11 +17,11 @@
public class EnqueuerWorklist {
- public abstract static class Action {
+ public abstract static class EnqueuerAction {
public abstract void run(Enqueuer enqueuer);
}
- static class MarkReachableDirectAction extends Action {
+ static class MarkReachableDirectAction extends EnqueuerAction {
final DexMethod target;
final KeepReason reason;
@@ -36,7 +36,7 @@
}
}
- static class MarkReachableVirtualAction extends Action {
+ static class MarkReachableVirtualAction extends EnqueuerAction {
final DexMethod target;
final KeepReason reason;
@@ -51,7 +51,7 @@
}
}
- static class MarkReachableInterfaceAction extends Action {
+ static class MarkReachableInterfaceAction extends EnqueuerAction {
final DexMethod target;
final KeepReason reason;
@@ -66,7 +66,7 @@
}
}
- static class MarkReachableSuperAction extends Action {
+ static class MarkReachableSuperAction extends EnqueuerAction {
final DexMethod target;
final DexEncodedMethod context;
@@ -81,7 +81,7 @@
}
}
- static class MarkReachableFieldAction extends Action {
+ static class MarkReachableFieldAction extends EnqueuerAction {
final DexEncodedField target;
final KeepReason reason;
@@ -96,7 +96,7 @@
}
}
- static class MarkInstantiatedAction extends Action {
+ static class MarkInstantiatedAction extends EnqueuerAction {
final DexProgramClass target;
final DexEncodedMethod context;
final KeepReason reason;
@@ -114,7 +114,7 @@
}
}
- static class MarkMethodLiveAction extends Action {
+ static class MarkMethodLiveAction extends EnqueuerAction {
final DexEncodedMethod target;
final KeepReason reason;
@@ -129,7 +129,7 @@
}
}
- static class MarkMethodKeptAction extends Action {
+ static class MarkMethodKeptAction extends EnqueuerAction {
final DexProgramClass holder;
final DexEncodedMethod target;
final KeepReason reason;
@@ -147,7 +147,7 @@
}
}
- static class MarkFieldKeptAction extends Action {
+ static class MarkFieldKeptAction extends EnqueuerAction {
final DexProgramClass holder;
final DexEncodedField target;
final KeepReasonWitness witness;
@@ -165,7 +165,7 @@
}
}
- static class TraceConstClassAction extends Action {
+ static class TraceConstClassAction extends EnqueuerAction {
final DexType type;
final DexEncodedMethod currentMethod;
@@ -180,7 +180,40 @@
}
}
- static class TraceStaticFieldReadAction extends Action {
+ static class TraceInvokeDirectAction extends EnqueuerAction {
+ final DexMethod invokedMethod;
+ final DexProgramClass currentHolder;
+ final DexEncodedMethod currentMethod;
+
+ TraceInvokeDirectAction(
+ DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+ this.invokedMethod = invokedMethod;
+ this.currentHolder = currentHolder;
+ this.currentMethod = currentMethod;
+ }
+
+ @Override
+ public void run(Enqueuer enqueuer) {
+ enqueuer.traceInvokeDirect(invokedMethod, currentHolder, currentMethod);
+ }
+ }
+
+ static class TraceNewInstanceAction extends EnqueuerAction {
+ final DexType type;
+ final DexEncodedMethod currentMethod;
+
+ TraceNewInstanceAction(DexType type, DexEncodedMethod currentMethod) {
+ this.type = type;
+ this.currentMethod = currentMethod;
+ }
+
+ @Override
+ public void run(Enqueuer enqueuer) {
+ enqueuer.traceNewInstance(type, currentMethod);
+ }
+ }
+
+ static class TraceStaticFieldReadAction extends EnqueuerAction {
final DexField field;
final DexEncodedMethod currentMethod;
@@ -196,7 +229,7 @@
}
private final AppView<?> appView;
- private final Queue<Action> queue = new ArrayDeque<>();
+ private final Queue<EnqueuerAction> queue = new ArrayDeque<>();
private EnqueuerWorklist(AppView<?> appView) {
this.appView = appView;
@@ -210,7 +243,7 @@
return queue.isEmpty();
}
- public Action poll() {
+ public EnqueuerAction poll() {
return queue.poll();
}
@@ -267,6 +300,17 @@
queue.add(new TraceConstClassAction(type, currentMethod));
}
+ public void enqueueTraceInvokeDirectAction(
+ DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+ assert currentMethod.method.holder == currentHolder.type;
+ queue.add(new TraceInvokeDirectAction(invokedMethod, currentHolder, currentMethod));
+ }
+
+ public void enqueueTraceNewInstanceAction(DexType type, DexEncodedMethod currentMethod) {
+ assert currentMethod.isProgramMethod(appView);
+ queue.add(new TraceNewInstanceAction(type, currentMethod));
+ }
+
public void enqueueTraceStaticFieldRead(DexField field, DexEncodedMethod currentMethod) {
assert currentMethod.isProgramMethod(appView);
queue.add(new TraceStaticFieldReadAction(field, currentMethod));
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 77d0b58..ed49a01 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -49,6 +49,14 @@
used = true;
}
+ public boolean isProguardKeepRule() {
+ return false;
+ }
+
+ public ProguardKeepRule asProguardKeepRule() {
+ return null;
+ }
+
Iterable<DexProgramClass> relevantCandidatesForRule(
AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> defaultValue) {
if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 5e382f9..4633882 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -127,4 +127,14 @@
modifiers.accept(builder.getModifiersBuilder());
return builder.build();
}
+
+ @Override
+ public boolean isProguardKeepRule() {
+ return true;
+ }
+
+ @Override
+ public ProguardKeepRule asProguardKeepRule() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 55b880b..fee13db 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
@@ -25,7 +24,11 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.Consumer3;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -35,6 +38,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.io.PrintStream;
@@ -51,12 +55,13 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Queue;
import java.util.Set;
import java.util.Stack;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -73,6 +78,7 @@
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
@@ -87,6 +93,8 @@
private final Map<DexReference, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
+ private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
+ new ConcurrentLinkedQueue<>();
private final InternalOptions options;
private final DexStringCache dexStringCache = new DexStringCache();
@@ -102,11 +110,15 @@
this.options = appView.options();
}
- RootSetBuilder(
+ public RootSetBuilder(
AppView<? extends AppInfoWithSubtyping> appView, Collection<ProguardIfRule> ifRules) {
this(appView, appView.appInfo().app(), ifRules);
}
+ public RootSetBuilder(AppView<? extends AppInfoWithSubtyping> appView) {
+ this(appView, appView.appInfo().app(), null);
+ }
+
// Process a class with the keep rule.
private void process(
DexClass clazz,
@@ -280,6 +292,10 @@
BottomUpClassHierarchyTraversal.forAllClasses(appView)
.visit(appView.appInfo().classes(), this::propagateAssumeRules);
}
+ if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
+ GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
+ appView, alwaysInline, neverInline, bypassClinitforInlining);
+ }
assert Sets.intersection(neverInline, alwaysInline).isEmpty()
&& Sets.intersection(neverInline, forceInline).isEmpty()
: "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
@@ -292,6 +308,7 @@
alwaysInline,
forceInline,
neverInline,
+ bypassClinitforInlining,
whyAreYouNotInlining,
keepParametersWithConstantValue,
keepUnusedArguments,
@@ -304,7 +321,8 @@
dependentNoShrinking,
dependentKeepClassCompatRule,
identifierNameStrings,
- ifRules);
+ ifRules,
+ Lists.newArrayList(delayedRootSetActionItems));
}
private void propagateAssumeRules(DexClass clazz) {
@@ -337,7 +355,7 @@
// is due to the lack of the definition or it indeed means no matching rules. Similar to how
// we apply those assume rules, here we use a resolved target.
DexEncodedMethod target =
- appView.appInfo().resolveMethod(subType, referenceInSubType).asResultOfResolve();
+ appView.appInfo().resolveMethod(subType, referenceInSubType).getSingleTarget();
// But, the resolution should not be landed on the current type we are visiting.
if (target == null || target.method.holder == type) {
continue;
@@ -372,7 +390,8 @@
noOptimization,
noObfuscation,
dependentNoShrinking,
- dependentKeepClassCompatRule);
+ dependentKeepClassCompatRule,
+ Lists.newArrayList(delayedRootSetActionItems));
}
private static DexDefinition testAndGetPrecondition(
@@ -409,7 +428,7 @@
while (!worklist.isEmpty()) {
DexClass currentClass = worklist.pop();
if (!includeLibraryClasses && currentClass.isNotProgramClass()) {
- return;
+ break;
}
// In compat mode traverse all direct methods in the hierarchy.
if (currentClass == clazz || options.forceProguardCompatibility) {
@@ -434,15 +453,128 @@
worklist.add(dexClass);
}
}
- if (options.testing.keepInheritedInterfaceMethods) {
- for (DexType iface : currentClass.interfaces.values) {
- DexClass interfaceClass = application.definitionFor(iface);
- if (interfaceClass != null) {
- worklist.add(interfaceClass);
+ }
+ // TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
+ // fullmode.
+ if (clazz.isProgramClass()) {
+ new SynthesizeMissingInterfaceMethodsForMemberRules(
+ clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule)
+ .run();
+ }
+ }
+
+ /**
+ * Utility class for visiting all super interfaces to ensure we keep method definitions specified
+ * by proguard rules. If possible, we generate a forwarding bridge to the resolved target. If not,
+ * we specifically synthesize a keep rule for the interface method.
+ */
+ private class SynthesizeMissingInterfaceMethodsForMemberRules {
+ private final DexProgramClass originalClazz;
+ private final Collection<ProguardMemberRule> memberKeepRules;
+ private final ProguardConfigurationRule context;
+ private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+ private final ProguardIfRule ifRule;
+ private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
+ private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
+
+ private SynthesizeMissingInterfaceMethodsForMemberRules(
+ DexProgramClass originalClazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule context,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ ProguardIfRule ifRule) {
+ this.originalClazz = originalClazz;
+ this.memberKeepRules = memberKeepRules;
+ this.context = context;
+ this.preconditionSupplier = preconditionSupplier;
+ this.ifRule = ifRule;
+ }
+
+ void run() {
+ visitAllSuperInterfaces(originalClazz.type);
+ }
+
+ private void visitAllSuperInterfaces(DexType type) {
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz == null || clazz.isNotProgramClass() || !seenTypes.add(type)) {
+ return;
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ visitAllSuperInterfaces(iface);
+ }
+ if (!clazz.isInterface()) {
+ visitAllSuperInterfaces(clazz.superType);
+ return;
+ }
+ if (originalClazz == clazz) {
+ return;
+ }
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ // Check if we already added this.
+ Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.method);
+ if (!seenMethods.add(wrapped)) {
+ continue;
+ }
+ for (ProguardMemberRule rule : memberKeepRules) {
+ if (rule.matches(method, appView, dexStringCache)) {
+ tryAndKeepMethodOnClass(method, rule);
}
}
}
}
+
+ private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
+ boolean shouldKeepMethod =
+ context.isProguardKeepRule()
+ && !context.asProguardKeepRule().getModifiers().allowsShrinking;
+ if (!shouldKeepMethod) {
+ return;
+ }
+ ResolutionResult resolutionResult =
+ appView.appInfo().resolveMethod(originalClazz, method.method);
+ if (!resolutionResult.isValidVirtualTarget(appView.options())
+ || !resolutionResult.hasSingleTarget()) {
+ return;
+ }
+ DexEncodedMethod methodToKeep = resolutionResult.getSingleTarget();
+ if (methodToKeep.method.holder == originalClazz.type) {
+ return;
+ }
+ DexClass holder = appView.definitionFor(methodToKeep.method.holder);
+ if (holder.isNotProgramClass()) {
+ return;
+ }
+ if (!holder.isInterface()) {
+ // TODO(b/143643942): For fullmode, this check should probably be removed.
+ return;
+ }
+ if (canInsertForwardingMethod(originalClazz, methodToKeep)) {
+ methodToKeep = methodToKeep.toForwardingMethod(originalClazz, appView);
+ }
+ final DexEncodedMethod finalKeepMethod = methodToKeep;
+ delayedRootSetActionItems.add(
+ new InterfaceMethodSyntheticBridgeAction(
+ methodToKeep,
+ resolutionResult.getSingleTarget(),
+ (rootSetBuilder) -> {
+ if (Log.ENABLED) {
+ Log.verbose(
+ getClass(),
+ "Marking method `%s` due to `%s { %s }`.",
+ finalKeepMethod,
+ context,
+ rule);
+ }
+ DexDefinition precondition =
+ testAndGetPrecondition(finalKeepMethod, preconditionSupplier);
+ rootSetBuilder.addItemToSets(finalKeepMethod, context, rule, precondition, ifRule);
+ }));
+ }
+ }
+
+ private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
+ return appView.options().isGeneratingDex()
+ || ArrayUtils.contains(holder.interfaces.values, target.method.holder);
}
private void markMatchingOverriddenMethods(
@@ -1053,6 +1185,7 @@
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
public final Set<DexMethod> neverInline;
+ public final Set<DexMethod> bypassClinitForInlining;
public final Set<DexMethod> whyAreYouNotInlining;
public final Set<DexMethod> keepConstantArguments;
public final Set<DexMethod> keepUnusedArguments;
@@ -1067,6 +1200,7 @@
private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
public final Set<DexReference> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
+ public final List<DelayedRootSetActionItem> delayedRootSetActionItems;
private RootSet(
Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
@@ -1077,6 +1211,7 @@
Set<DexMethod> alwaysInline,
Set<DexMethod> forceInline,
Set<DexMethod> neverInline,
+ Set<DexMethod> bypassClinitForInlining,
Set<DexMethod> whyAreYouNotInlining,
Set<DexMethod> keepConstantArguments,
Set<DexMethod> keepUnusedArguments,
@@ -1089,7 +1224,8 @@
Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
Set<DexReference> identifierNameStrings,
- Set<ProguardIfRule> ifRules) {
+ Set<ProguardIfRule> ifRules,
+ List<DelayedRootSetActionItem> delayedRootSetActionItems) {
this.noShrinking = noShrinking;
this.noOptimization = noOptimization;
this.noObfuscation = noObfuscation;
@@ -1098,6 +1234,7 @@
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.forceInline = Collections.unmodifiableSet(forceInline);
this.neverInline = neverInline;
+ this.bypassClinitForInlining = bypassClinitForInlining;
this.whyAreYouNotInlining = whyAreYouNotInlining;
this.keepConstantArguments = keepConstantArguments;
this.keepUnusedArguments = keepUnusedArguments;
@@ -1111,6 +1248,7 @@
this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
+ this.delayedRootSetActionItems = delayedRootSetActionItems;
}
public void checkAllRulesAreUsed(InternalOptions options) {
@@ -1132,25 +1270,21 @@
}
}
- private static <T extends DexReference, S> Map<T, Map<T, S>> rewriteDependentReferenceKeys(
- Map<T, Map<T, S>> original, Function<T, T> rewrite) {
- Map<T, Map<T, S>> result = new IdentityHashMap<>();
- for (T item : original.keySet()) {
- result.put(rewrite.apply(item), rewriteReferenceKeys(original.get(item), rewrite));
- }
- return result;
- }
-
- void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
+ void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
neverInline.addAll(consequentRootSet.neverInline);
neverClassInline.addAll(consequentRootSet.neverClassInline);
noOptimization.addAll(consequentRootSet.noOptimization);
noObfuscation.addAll(consequentRootSet.noObfuscation);
+ if (addNoShrinking) {
+ consequentRootSet.noShrinking.forEach(
+ (type, rules) -> noShrinking.computeIfAbsent(type, k -> new HashSet<>()).addAll(rules));
+ }
addDependentItems(consequentRootSet.dependentNoShrinking);
consequentRootSet.dependentKeepClassCompatRule.forEach(
(type, rules) ->
dependentKeepClassCompatRule.computeIfAbsent(
type, k -> new HashSet<>()).addAll(rules));
+ delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems);
}
// Add dependent items that depend on -if rules.
@@ -1422,8 +1556,9 @@
}
}
- // A partial RootSet that becomes live due to the enabled -if rule.
- static class ConsequentRootSet {
+ // A partial RootSet that becomes live due to the enabled -if rule or the addition of interface
+ // keep rules.
+ public static class ConsequentRootSet {
final Set<DexMethod> neverInline;
final Set<DexType> neverClassInline;
final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
@@ -1431,6 +1566,7 @@
final Set<DexReference> noObfuscation;
final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking;
final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
+ final List<DelayedRootSetActionItem> delayedRootSetActionItems;
private ConsequentRootSet(
Set<DexMethod> neverInline,
@@ -1439,7 +1575,8 @@
Set<DexReference> noOptimization,
Set<DexReference> noObfuscation,
Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
- Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule) {
+ Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+ List<DelayedRootSetActionItem> delayedRootSetActionItems) {
this.neverInline = Collections.unmodifiableSet(neverInline);
this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
this.noShrinking = Collections.unmodifiableMap(noShrinking);
@@ -1447,6 +1584,7 @@
this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
this.dependentNoShrinking = Collections.unmodifiableMap(dependentNoShrinking);
this.dependentKeepClassCompatRule = Collections.unmodifiableMap(dependentKeepClassCompatRule);
+ this.delayedRootSetActionItems = Collections.unmodifiableList(delayedRootSetActionItems);
}
}
}
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 a9ec234..f07499b 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -327,6 +327,11 @@
for (DexMethod signature : appInfo.virtualMethodsTargetedByInvokeDirect) {
markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_DIRECT);
}
+
+ // The set of targets that must remain for proper resolution error cases should not be merged.
+ for (DexMethod method : appInfo.targetedMethodsThatMustRemainNonAbstract) {
+ markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
+ }
}
private <T extends DexReference> void extractPinnedItems(Iterable<T> items, AbortReason reason) {
@@ -989,15 +994,15 @@
// Non-abstract methods are handled below (they cannot simply be moved to the subclass as
// a virtual method, because they might be the target of an invoke-super instruction).
if (virtualMethod.accessFlags.isAbstract()) {
+ // Abort if target is non-abstract and does not override the abstract method.
+ if (!target.isAbstract()) {
+ assert appView.options().testing.allowNonAbstractClassesWithAbstractMethods;
+ abortMerge = true;
+ return false;
+ }
// Update the holder of [virtualMethod] using renameMethod().
DexEncodedMethod resultingVirtualMethod =
renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
- if (appView.options().canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
- && !target.isAbstract()) {
- resultingVirtualMethod.accessFlags.unsetAbstract();
- resultingVirtualMethod =
- resultingVirtualMethod.toEmptyThrowingMethod(appView.options());
- }
deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index c059819..40324ec 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -106,4 +106,12 @@
clazz.cast(Array.newInstance(clazz.getComponentType(), results.size())));
}
+ public static <T> boolean contains(T[] elements, T elementToLookFor) {
+ for (Object element : elements) {
+ if (element.equals(elementToLookFor)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanLatticeElement.java b/src/main/java/com/android/tools/r8/utils/BooleanLatticeElement.java
new file mode 100644
index 0000000..9e955c6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BooleanLatticeElement.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, 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.utils;
+
+public abstract class BooleanLatticeElement {
+
+ public static final BooleanLatticeElement BOTTOM =
+ new BooleanLatticeElement() {
+
+ @Override
+ public OptionalBool asOptionalBool() {
+ throw new IllegalStateException("BooleanLatticeElement.BOTTOM is not an OptionalBool");
+ }
+
+ @Override
+ public boolean isBottom() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "bottom";
+ }
+ };
+
+ BooleanLatticeElement() {}
+
+ public abstract OptionalBool asOptionalBool();
+
+ public boolean isBottom() {
+ return false;
+ }
+
+ public boolean isTrue() {
+ return false;
+ }
+
+ public boolean isFalse() {
+ return false;
+ }
+
+ public boolean isUnknown() {
+ return false;
+ }
+
+ public boolean isPossiblyTrue() {
+ return isTrue() || isUnknown();
+ }
+
+ public boolean isPossiblyFalse() {
+ return isFalse() || isUnknown();
+ }
+
+ public BooleanLatticeElement join(BooleanLatticeElement other) {
+ if (this == other || other.isBottom() || isUnknown()) {
+ return this;
+ }
+ if (isBottom() || other.isUnknown()) {
+ return other;
+ }
+ assert isTrue() || isFalse();
+ assert other.isTrue() || other.isFalse();
+ return OptionalBool.UNKNOWN;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ // Force all subtypes to implement toString().
+ @Override
+ public abstract String toString();
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index adbaad4..09fa0fe 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -283,6 +283,11 @@
return className.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR);
}
+ public static String getBinaryNameFromDescriptor(String classDescriptor) {
+ assert isClassDescriptor(classDescriptor);
+ return classDescriptor.substring(1, classDescriptor.length() - 1);
+ }
+
/**
* Convert a class binary name to a descriptor.
*
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 595ac28..3a0b969 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -444,6 +444,8 @@
public boolean debug = false;
private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
+ private final KotlinOptimizationOptions kotlinOptimizationOptions =
+ new KotlinOptimizationOptions();
public final TestingOptions testing = new TestingOptions();
public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
@@ -463,6 +465,10 @@
return protoShrinking;
}
+ public KotlinOptimizationOptions kotlinOptimizationOptions() {
+ return kotlinOptimizationOptions;
+ }
+
public static boolean shouldEnableKeepRuleSynthesisForRecompilation() {
return System.getProperty("com.android.tools.r8.keepRuleSynthesisForRecompilation") != null;
}
@@ -925,6 +931,11 @@
public int threshold = 20;
}
+ public static class KotlinOptimizationOptions {
+ public boolean disableKotlinSpecificOptimizations =
+ System.getProperty("com.android.tools.r8.disableKotlinSpecificOptimizations") != null;
+ }
+
public static class ProtoShrinkingOptions {
public boolean enableGeneratedExtensionRegistryShrinking =
@@ -933,11 +944,16 @@
public boolean enableGeneratedMessageLiteShrinking =
System.getProperty("com.android.tools.r8.generatedMessageLiteShrinking") != null;
+ public boolean enableGeneratedMessageLiteBuilderShrinking =
+ System.getProperty("com.android.tools.r8.generatedMessageLiteBuilderShrinking") != null;
+
public boolean traverseOneOfAndRepeatedProtoFields =
System.getProperty("com.android.tools.r8.traverseOneOfAndRepeatedProtoFields") == null;
public boolean isProtoShrinkingEnabled() {
- return enableGeneratedExtensionRegistryShrinking || enableGeneratedMessageLiteShrinking;
+ return enableGeneratedExtensionRegistryShrinking
+ || enableGeneratedMessageLiteShrinking
+ || enableGeneratedMessageLiteBuilderShrinking;
}
}
@@ -970,6 +986,7 @@
public boolean allowTypeErrors =
!Version.isDev() || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
+ public boolean allowInvokeErrors = false;
public boolean disableL8AnnotationRemoval = false;
public boolean allowUnusedProguardConfigurationRules = true;
public boolean reportUnusedProguardConfigurationRules = false;
@@ -998,7 +1015,9 @@
public PrintStream whyAreYouNotInliningConsumer = System.out;
public boolean trackDesugaredAPIConversions =
System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
- public boolean keepInheritedInterfaceMethods = false;
+
+ // TODO(b/144781417): This is disabled by default as some test apps appear to have such classes.
+ public boolean allowNonAbstractClassesWithAbstractMethods = true;
// Flag to turn on/off JDK11+ nest-access control even when not required (Cf backend)
public boolean enableForceNestBasedAccessDesugaringForTest = false;
diff --git a/src/main/java/com/android/tools/r8/utils/OptionalBool.java b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
new file mode 100644
index 0000000..54029d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2019, 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.utils;
+
+public abstract class OptionalBool extends BooleanLatticeElement {
+
+ public static final OptionalBool TRUE =
+ new OptionalBool() {
+
+ @Override
+ public boolean isTrue() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "true";
+ }
+ };
+
+ public static final OptionalBool FALSE =
+ new OptionalBool() {
+
+ @Override
+ public boolean isFalse() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "false";
+ }
+ };
+
+ public static final OptionalBool UNKNOWN =
+ new OptionalBool() {
+
+ @Override
+ public boolean isUnknown() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "unknown";
+ }
+ };
+
+ OptionalBool() {}
+
+ public static OptionalBool of(boolean bool) {
+ return bool ? TRUE : FALSE;
+ }
+
+ public static OptionalBool unknown() {
+ return UNKNOWN;
+ }
+
+ @Override
+ public OptionalBool asOptionalBool() {
+ return this;
+ }
+}
diff --git a/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
index 1ad06e0..a0f6c8d 100644
--- a/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
+++ b/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
@@ -5,7 +5,6 @@
package java.util;
import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
public class DoubleSummaryStatisticsConversions {
@@ -40,6 +39,8 @@
JD_DOUBLE_MAX_FIELD.setAccessible(true);
}
+ private DoubleSummaryStatisticsConversions() {}
+
private static Field getField(Class<?> clazz, String name) {
try {
return clazz.getDeclaredField(name);
diff --git a/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
index 8351704..8e9b616 100644
--- a/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
+++ b/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
@@ -40,6 +40,8 @@
JD_INT_MAX_FIELD.setAccessible(true);
}
+ private IntSummaryStatisticsConversions() {}
+
private static Field getField(Class<?> clazz, String name) {
try {
return clazz.getDeclaredField(name);
diff --git a/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
index 7096a2a..ae21eb2 100644
--- a/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
+++ b/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
@@ -40,6 +40,8 @@
JD_LONG_MAX_FIELD.setAccessible(true);
}
+ private LongSummaryStatisticsConversions() {}
+
private static Field getField(Class<?> clazz, String name) {
try {
return clazz.getDeclaredField(name);
diff --git a/src/test/desugaredLibrary/conversions/OptionalConversions.java b/src/test/desugaredLibrary/conversions/OptionalConversions.java
index 390b123..908f9bf 100644
--- a/src/test/desugaredLibrary/conversions/OptionalConversions.java
+++ b/src/test/desugaredLibrary/conversions/OptionalConversions.java
@@ -6,7 +6,9 @@
public class OptionalConversions {
- public static j$.util.Optional convert(java.util.Optional optional) {
+ private OptionalConversions() {}
+
+ public static <T> j$.util.Optional<T> convert(java.util.Optional<T> optional) {
if (optional == null) {
return null;
}
@@ -16,7 +18,7 @@
return j$.util.Optional.empty();
}
- public static java.util.Optional convert(j$.util.Optional optional) {
+ public static <T> java.util.Optional<T> convert(j$.util.Optional<T> optional) {
if (optional == null) {
return null;
}
diff --git a/src/test/desugaredLibrary/conversions/TimeConversions.java b/src/test/desugaredLibrary/conversions/TimeConversions.java
index b5adf3c..1320546 100644
--- a/src/test/desugaredLibrary/conversions/TimeConversions.java
+++ b/src/test/desugaredLibrary/conversions/TimeConversions.java
@@ -6,6 +6,8 @@
public class TimeConversions {
+ private TimeConversions() {}
+
public static j$.time.ZonedDateTime convert(java.time.ZonedDateTime dateTime) {
if (dateTime == null) {
return null;
diff --git a/src/test/examples/shaking18/Options.java b/src/test/examples/shaking18/Options.java
index 7012340..edb9d77 100644
--- a/src/test/examples/shaking18/Options.java
+++ b/src/test/examples/shaking18/Options.java
@@ -4,7 +4,9 @@
package shaking18;
public class Options {
- public boolean alwaysFalse = false;
+ // TODO(b/138913138): member value propagation can behave same with and without initialization.
+ // public boolean alwaysFalse = false;
+ public boolean alwaysFalse;
public boolean dummy = false;
public Options() {}
diff --git a/src/test/examplesProto/proto2/BuilderTestClass.java b/src/test/examplesProto/proto2/BuilderTestClass.java
deleted file mode 100644
index 9712c22..0000000
--- a/src/test/examplesProto/proto2/BuilderTestClass.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2019, 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 proto2;
-
-import com.android.tools.r8.proto2.TestProto.NestedMessage;
-import com.android.tools.r8.proto2.TestProto.OuterMessage;
-import com.android.tools.r8.proto2.TestProto.Primitives;
-
-public class BuilderTestClass {
-
- public static void main(String[] args) {
- builderWithPrimitiveSetters();
- builderWithReusedSetters();
- builderWithProtoBuilderSetter();
- builderWithProtoSetter();
- builderWithOneofSetter();
- }
-
- public static void builderWithPrimitiveSetters() {
- System.out.println("builderWithPrimitiveSetters");
- Primitives primitives = Primitives.newBuilder().setFooInt32(17).build();
- Primitives other = Primitives.newBuilder().setBarInt64(16).build();
- System.out.println(primitives.getFooInt32());
- System.out.println(other.getBarInt64());
- }
-
- public static void builderWithReusedSetters() {
- System.out.println("builderWithReusedSetters");
- Primitives.Builder builder = Primitives.newBuilder().setFooInt32(1);
- Primitives primitives = builder.build();
- // Reusing the builder after a build should force copyOnWrite to be kept.
- Primitives other = builder.setQuxString("qux").build();
- System.out.println(primitives.getFooInt32());
- System.out.println(other.getQuxString());
- }
-
- public static void builderWithProtoBuilderSetter() {
- System.out.println("builderWithProtoBuilderSetter");
- OuterMessage outerMessage =
- OuterMessage.newBuilder()
- .setNestedField(NestedMessage.newBuilder().setFooInt64(42))
- .build();
- System.out.println(outerMessage.getNestedField().getFooInt64());
- }
-
- public static void builderWithProtoSetter() {
- System.out.println("builderWithProtoSetter");
- OuterMessage outerMessage =
- OuterMessage.newBuilder()
- .setNestedField(NestedMessage.newBuilder().setFooInt64(42).build())
- .build();
- System.out.println(outerMessage.getNestedField().getFooInt64());
- }
-
- public static void builderWithOneofSetter() {
- System.out.println("builderWithOneofSetter");
- Primitives primitives = Primitives.newBuilder().setOneofString("foo").build();
- System.out.println(primitives.getOneofString());
- }
-}
diff --git a/src/test/examplesProto/proto2/BuilderWithOneofSetterTestClass.java b/src/test/examplesProto/proto2/BuilderWithOneofSetterTestClass.java
new file mode 100644
index 0000000..cdda6aa
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithOneofSetterTestClass.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2019, 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class BuilderWithOneofSetterTestClass {
+
+ public static void main(String[] args) {
+ System.out.println("builderWithOneofSetter");
+ Primitives primitives = Primitives.newBuilder().setOneofString("foo").build();
+ Printer.print(primitives);
+ }
+}
diff --git a/src/test/examplesProto/proto2/BuilderWithPrimitiveSettersTestClass.java b/src/test/examplesProto/proto2/BuilderWithPrimitiveSettersTestClass.java
new file mode 100644
index 0000000..27a0abd
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithPrimitiveSettersTestClass.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2019, 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class BuilderWithPrimitiveSettersTestClass {
+
+ public static void main(String[] args) {
+ System.out.println("builderWithPrimitiveSetters");
+ Primitives primitives = Primitives.newBuilder().setFooInt32(17).build();
+ Primitives other = Primitives.newBuilder().setBarInt64(16).build();
+ Printer.print(primitives);
+ Printer.print(other);
+ }
+}
diff --git a/src/test/examplesProto/proto2/BuilderWithProtoBuilderSetterTestClass.java b/src/test/examplesProto/proto2/BuilderWithProtoBuilderSetterTestClass.java
new file mode 100644
index 0000000..6dde164
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithProtoBuilderSetterTestClass.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.NestedMessage;
+import com.android.tools.r8.proto2.TestProto.OuterMessage;
+
+public class BuilderWithProtoBuilderSetterTestClass {
+
+ public static void main(String[] args) {
+ System.out.println("builderWithProtoBuilderSetter");
+ OuterMessage outerMessage =
+ OuterMessage.newBuilder()
+ .setNestedField(NestedMessage.newBuilder().setFooInt64(42))
+ .build();
+ System.out.println(outerMessage.getNestedField().getFooInt64());
+ }
+}
diff --git a/src/test/examplesProto/proto2/BuilderWithProtoSetterTestClass.java b/src/test/examplesProto/proto2/BuilderWithProtoSetterTestClass.java
new file mode 100644
index 0000000..1200684
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithProtoSetterTestClass.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.NestedMessage;
+import com.android.tools.r8.proto2.TestProto.OuterMessage;
+
+public class BuilderWithProtoSetterTestClass {
+
+ public static void main(String[] args) {
+ System.out.println("builderWithProtoSetter");
+ OuterMessage outerMessage =
+ OuterMessage.newBuilder()
+ .setNestedField(NestedMessage.newBuilder().setFooInt64(42).build())
+ .build();
+ System.out.println(outerMessage.getNestedField().getFooInt64());
+ }
+}
diff --git a/src/test/examplesProto/proto2/BuilderWithReusedSettersTestClass.java b/src/test/examplesProto/proto2/BuilderWithReusedSettersTestClass.java
new file mode 100644
index 0000000..badcdbc
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithReusedSettersTestClass.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class BuilderWithReusedSettersTestClass {
+
+ public static void main(String[] args) {
+ System.out.println("builderWithReusedSetters");
+ Primitives.Builder builder = Primitives.newBuilder().setFooInt32(1);
+ Primitives primitives = builder.build();
+ // Reusing the builder after a build should force copyOnWrite to be kept.
+ Primitives other = builder.setQuxString("qux").build();
+ Printer.print(primitives);
+ Printer.print(other);
+ }
+}
diff --git a/src/test/examplesProto/proto2/Printer.java b/src/test/examplesProto/proto2/Printer.java
new file mode 100644
index 0000000..fd87ef0
--- /dev/null
+++ b/src/test/examplesProto/proto2/Printer.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class Printer {
+
+ static void print(Primitives primitives) {
+ System.out.println(primitives.hasFooInt32());
+ System.out.println(primitives.getFooInt32());
+ System.out.println(primitives.hasOneofString());
+ System.out.println(primitives.getOneofString());
+ System.out.println(primitives.hasOneofUint32());
+ System.out.println(primitives.getOneofUint32());
+ System.out.println(primitives.hasBarInt64());
+ System.out.println(primitives.getBarInt64());
+ System.out.println(primitives.hasQuxString());
+ System.out.println(primitives.getQuxString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index 1754bff..1defe48 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.List;
@@ -27,21 +28,21 @@
@Parameters(name = "{0}, library: {1}")
public static List<Object[]> data() {
- return buildParameters(getTestParameters().withAllRuntimes().build(), CfVm.values());
+ return buildParameters(
+ getTestParameters().withAllRuntimes().build(), TestRuntime.getCheckedInCfRuntimes());
}
private final TestParameters parameters;
- private final CfVm library;
+ private final CfRuntime library;
- public CompileWithJdkClassFileProviderTest(TestParameters parameters, CfVm library) {
+ public CompileWithJdkClassFileProviderTest(TestParameters parameters, CfRuntime library) {
this.parameters = parameters;
this.library = library;
}
@Test
public void compileSimpleCodeWithJdkLibrary() throws Exception {
- ClassFileResourceProvider provider =
- JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+ ClassFileResourceProvider provider = JdkClassFileProvider.fromJdkHome(library.getJavaHome());
testForR8(parameters.getBackend())
.addLibraryProvider(provider)
@@ -58,7 +59,7 @@
@Test
public void compileSimpleCodeWithSystemJdk() throws Exception {
// Don't run duplicate tests (library is not used by the test).
- assumeTrue(library == CfVm.JDK8);
+ assumeTrue(library.getVm() == CfVm.JDK8);
ClassFileResourceProvider provider = JdkClassFileProvider.fromSystemJdk();
@@ -76,8 +77,7 @@
@Test
public void compileCodeWithJava9APIUsage() throws Exception {
- ClassFileResourceProvider provider =
- JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+ ClassFileResourceProvider provider = JdkClassFileProvider.fromJdkHome(library.getJavaHome());
TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder =
testForR8(parameters.getBackend())
@@ -85,7 +85,7 @@
.addProgramClassFileData(dumpClassWhichUseJava9Flow())
.addKeepMainRule("MySubscriber");
- if (library == CfVm.JDK8) {
+ if (library.getVm() == CfVm.JDK8) {
try {
// java.util.concurrent.Flow$Subscriber is not present in JDK8 rt.jar.
testBuilder.compileWithExpectedDiagnostics(
diff --git a/src/test/java/com/android/tools/r8/DumpInputsTest.java b/src/test/java/com/android/tools/r8/DumpInputsTest.java
index b8b3a04..e7f2ef8 100644
--- a/src/test/java/com/android/tools/r8/DumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/DumpInputsTest.java
@@ -41,8 +41,7 @@
public void testDumpToFile() throws Exception {
Path dump = temp.newFolder().toPath().resolve("dump.zip");
try {
- testForExternalR8(parameters.getBackend())
- .useExternalJDK(parameters.getRuntime().asCf().getVm())
+ testForExternalR8(parameters.getBackend(), parameters.getRuntime())
.addJvmFlag("-Dcom.android.tools.r8.dumpinputtofile=" + dump)
.addProgramClasses(TestClass.class)
.compile();
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index ae4a36c..0ebe047 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -5,12 +5,10 @@
package com.android.tools.r8;
import static com.android.tools.r8.ToolHelper.CLASSPATH_SEPARATOR;
-import static com.android.tools.r8.ToolHelper.getJavaExecutable;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.utils.AndroidApp;
@@ -53,18 +51,22 @@
private List<Path> proguardConfigFiles = new ArrayList<>();
// External JDK to use to run R8
- private CfVm externalJDK = null;
+ private final TestRuntime runtime;
private boolean addR8ExternalDeps = false;
private List<String> jvmFlags = new ArrayList<>();
- private ExternalR8TestBuilder(TestState state, Builder builder, Backend backend) {
+ private ExternalR8TestBuilder(
+ TestState state, Builder builder, Backend backend, TestRuntime runtime) {
super(state, builder, backend);
+ assert runtime != null;
+ this.runtime = runtime;
}
- public static ExternalR8TestBuilder create(TestState state, Backend backend) {
- return new ExternalR8TestBuilder(state, R8Command.builder(), backend);
+ public static ExternalR8TestBuilder create(
+ TestState state, Backend backend, TestRuntime runtime) {
+ return new ExternalR8TestBuilder(state, R8Command.builder(), backend, runtime);
}
@Override
@@ -72,18 +74,6 @@
return this;
}
- public ExternalR8TestBuilder useExternalJDK(CfVm externalJDK) {
- this.externalJDK = externalJDK;
- return self();
- }
-
- private String getJDKToRun() {
- if (externalJDK == null) {
- return getJavaExecutable();
- }
- return getJavaExecutable(externalJDK);
- }
-
public ExternalR8TestBuilder addJvmFlag(String flag) {
jvmFlags.add(flag);
return self();
@@ -106,7 +96,10 @@
: r8jar.toAbsolutePath().toString();
List<String> command = new ArrayList<>();
- Collections.addAll(command, getJDKToRun());
+ if (runtime.isDex()) {
+ throw new Unimplemented();
+ }
+ Collections.addAll(command, runtime.asCf().getJavaExecutable().toString());
command.addAll(jvmFlags);
diff --git a/src/test/java/com/android/tools/r8/JavaCompilerTool.java b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
index d527670..c7f7652 100644
--- a/src/test/java/com/android/tools/r8/JavaCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
@@ -7,7 +7,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ZipUtils;
@@ -23,24 +23,24 @@
public class JavaCompilerTool {
- private final CfVm jdk;
+ private final CfRuntime jdk;
private final TestState state;
private final List<Path> sources = new ArrayList<>();
private final List<Path> classpath = new ArrayList<>();
private final List<String> options = new ArrayList<>();
private Path output = null;
- private JavaCompilerTool(CfVm jdk, TestState state) {
+ private JavaCompilerTool(CfRuntime jdk, TestState state) {
this.jdk = jdk;
this.state = state;
}
- public static JavaCompilerTool create(CfVm jdk, TemporaryFolder temp) {
+ public static JavaCompilerTool create(CfRuntime jdk, TemporaryFolder temp) {
assert temp != null;
return create(jdk, new TestState(temp));
}
- public static JavaCompilerTool create(CfVm jdk, TestState state) {
+ public static JavaCompilerTool create(CfRuntime jdk, TestState state) {
assert state != null;
return new JavaCompilerTool(jdk, state);
}
@@ -106,7 +106,7 @@
private ProcessResult compileInternal(Path output) throws IOException {
Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
List<String> cmdline = new ArrayList<>();
- cmdline.add(ToolHelper.getJavaExecutable(jdk) + "c");
+ cmdline.add(jdk.getJavaExecutable() + "c");
cmdline.addAll(options);
if (!classpath.isEmpty()) {
cmdline.add("-cp");
diff --git a/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
index d6f38af..fbcc9d6 100644
--- a/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
@@ -4,18 +4,40 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.Opcodes;
+@RunWith(Parameterized.class)
public class JdkClassFileProviderTest extends TestBase implements Opcodes {
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ final TestParameters parameters;
+
+ public JdkClassFileProviderTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private CfRuntime getRuntime() {
+ return parameters.getRuntime().asCf();
+ }
+
@Test
- public void testInvalid8RuntimeClassPath() throws Exception {
+ public void testInvalidRuntimeClassPath() throws Exception {
Path path = temp.newFolder().toPath();
try {
JdkClassFileProvider.fromJdkHome(path);
@@ -27,22 +49,23 @@
}
@Test
- public void testJdk8JavHome() throws Exception {
+ public void testJdkJavaHome() throws Exception {
ClassFileResourceProvider provider =
- JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+ JdkClassFileProvider.fromJdkHome(getRuntime().getJavaHome());
assertJavaLangObject(provider);
assert provider instanceof AutoCloseable;
+ if (getRuntime().isNewerThanOrEqual(CfVm.JDK9)) {
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ }
((AutoCloseable) provider).close();
}
@Test
public void testJdk8RuntimeClassPath() throws Exception {
+ assumeTrue(getRuntime().getVm() == CfVm.JDK8);
ClassFileResourceProvider provider =
JdkClassFileProvider.fromJavaRuntimeJar(
- ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8)
- .resolve("jre")
- .resolve("lib")
- .resolve("rt.jar"));
+ getRuntime().getJavaHome().resolve("jre").resolve("lib").resolve("rt.jar"));
assertJavaLangObject(provider);
assert provider instanceof AutoCloseable;
((AutoCloseable) provider).close();
@@ -50,8 +73,9 @@
@Test
public void testJdk8SystemModules() throws Exception {
+ assumeTrue(getRuntime().getVm() == CfVm.JDK8);
try {
- JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+ JdkClassFileProvider.fromSystemModulesJdk(getRuntime().getJavaHome());
fail("Not supposed to succeed");
} catch (NoSuchFileException e) {
assertThat(e.toString(), containsString("lib/jrt-fs.jar"));
@@ -59,39 +83,10 @@
}
@Test
- public void testJdk9JavaHome() throws Exception {
+ public void testJdk9PlusSystemModules() throws Exception {
+ assumeTrue(getRuntime().isNewerThanOrEqual(CfVm.JDK9));
ClassFileResourceProvider provider =
- JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
- assertJavaLangObject(provider);
- assertJavaUtilConcurrentFlowSubscriber(provider);
- assert provider instanceof AutoCloseable;
- ((AutoCloseable) provider).close();
- }
-
- @Test
- public void testJdk9SystemModules() throws Exception {
- ClassFileResourceProvider provider =
- JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
- assertJavaLangObject(provider);
- assertJavaUtilConcurrentFlowSubscriber(provider);
- assert provider instanceof AutoCloseable;
- ((AutoCloseable) provider).close();
- }
-
- @Test
- public void testJdk11JavaHome() throws Exception {
- ClassFileResourceProvider provider =
- JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
- assertJavaLangObject(provider);
- assertJavaUtilConcurrentFlowSubscriber(provider);
- assert provider instanceof AutoCloseable;
- ((AutoCloseable) provider).close();
- }
-
- @Test
- public void testJdk11SystemModules() throws Exception {
- ClassFileResourceProvider provider =
- JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
+ JdkClassFileProvider.fromSystemModulesJdk(getRuntime().getJavaHome());
assertJavaLangObject(provider);
assertJavaUtilConcurrentFlowSubscriber(provider);
assert provider instanceof AutoCloseable;
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 4f31cc8..e44287f 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -48,7 +48,7 @@
throws IOException {
assert runtime.isCf();
ProcessResult result =
- ToolHelper.runJava(runtime.asCf().getVm(), classpath, ObjectArrays.concat(mainClass, args));
+ ToolHelper.runJava(runtime.asCf(), classpath, ObjectArrays.concat(mainClass, args));
return new JvmTestRunResult(builder.build(), runtime, result);
}
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index e66a3de..268709d 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -28,21 +28,27 @@
private final CfRuntime jdk;
private final TestState state;
+ private final String kotlincJar;
private final List<Path> sources = new ArrayList<>();
private final List<Path> classpath = new ArrayList<>();
private Path output = null;
- private KotlinCompilerTool(CfRuntime jdk, TestState state) {
+ private KotlinCompilerTool(CfRuntime jdk, TestState state, Path kotlincJar) {
this.jdk = jdk;
this.state = state;
+ this.kotlincJar = kotlincJar == null ? KT_COMPILER : kotlincJar.toString();
}
public static KotlinCompilerTool create(CfRuntime jdk, TemporaryFolder temp) {
- return create(jdk, new TestState(temp));
+ return create(jdk, new TestState(temp), null);
}
- public static KotlinCompilerTool create(CfRuntime jdk, TestState state) {
- return new KotlinCompilerTool(jdk, state);
+ public static KotlinCompilerTool create (CfRuntime jdk, TemporaryFolder temp, Path kotlincJar) {
+ return create(jdk, new TestState(temp), kotlincJar);
+ }
+
+ public static KotlinCompilerTool create(CfRuntime jdk, TestState state, Path kotlincJar) {
+ return new KotlinCompilerTool(jdk, state, kotlincJar);
}
public KotlinCompilerTool addSourceFiles(Path files) {
@@ -94,13 +100,13 @@
private ProcessResult compileInternal(Path output) throws IOException {
Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
List<String> cmdline = new ArrayList<>();
- cmdline.add(ToolHelper.getJavaExecutable(jdk.getVm()));
+ cmdline.add(jdk.getJavaExecutable().toString());
cmdline.add("-jar");
cmdline.add(KT_PRELOADER);
cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
cmdline.add("-cp");
- cmdline.add(KT_COMPILER);
- cmdline.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
+ cmdline.add(kotlincJar);
+ cmdline.add(ToolHelper.K2JVMCompiler);
for (Path source : sources) {
cmdline.add(source.toString());
}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 7b7f270..74cff92 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -102,7 +102,8 @@
DexVm.Version.V6_0_1,
DexVm.Version.V7_0_0,
DexVm.Version.V8_1_0,
- DexVm.Version.V9_0_0);
+ DexVm.Version.V9_0_0,
+ DexVm.Version.V10_0_0);
// Input jar for jctf tests.
private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
@@ -478,6 +479,14 @@
static {
ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
builder
+ .put(DexVm.Version.V10_0_0, ImmutableList.of(
+ // TODO(b/144975341): Triage, Verif error.
+ "518-null-array-get",
+ // TODO(b/144975341): Triage, Linking error.
+ "457-regs",
+ "543-env-long-ref",
+ "454-get-vreg"
+ ))
.put(DexVm.Version.V9_0_0, ImmutableList.of(
// TODO(120400625): Triage.
"454-get-vreg",
@@ -1993,6 +2002,11 @@
vms.add(TestRuntime.getDefaultJavaRuntime());
} else {
for (DexVm vm : TestParametersBuilder.getAvailableDexVms()) {
+ // TODO(144966342): Disabled for triaging failures
+ if (vm.getVersion() == DexVm.Version.V10_0_0) {
+ System.out.println("Running on 10.0.0 is disabled, see b/144966342");
+ continue;
+ }
vms.add(new DexRuntime(vm));
}
}
diff --git a/src/test/java/com/android/tools/r8/SanityCheck.java b/src/test/java/com/android/tools/r8/SanityCheck.java
index ef7d3c3..527fadd 100644
--- a/src/test/java/com/android/tools/r8/SanityCheck.java
+++ b/src/test/java/com/android/tools/r8/SanityCheck.java
@@ -6,25 +6,32 @@
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Enumeration;
-import java.util.Set;
+import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.junit.Test;
public class SanityCheck extends TestBase {
- private void checkJarContent(Path jar, boolean allowDirectories, Set<String> additionalEntries)
+ private static final String SRV_PREFIX = "META-INF/services/";
+ private static final String METADATA_EXTENSION =
+ "com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions";
+ private static final String EXT_IN_SRV = SRV_PREFIX + METADATA_EXTENSION;
+
+ private void checkJarContent(
+ Path jar, boolean allowDirectories, Predicate<String> entryTester)
throws Exception {
ZipFile zipFile;
try {
@@ -47,7 +54,7 @@
// Allow.
} else if (name.equals("LICENSE")) {
licenseSeen = true;
- } else if (additionalEntries.contains(name)) {
+ } else if (entryTester.test(name)) {
// Allow.
} else if (name.endsWith("/")) {
assertTrue("Unexpected directory entry in" + jar, allowDirectories);
@@ -58,32 +65,45 @@
assertTrue("No LICENSE entry found in " + jar, licenseSeen);
}
- private void checkLibJarContent(Path jar) throws Exception {
+ private void checkLibJarContent(Path jar, Path map) throws Exception {
if (!Files.exists(jar)) {
return;
}
- checkJarContent(jar, false, ImmutableSet.of());
+ assertTrue(Files.exists(map));
+ ClassNameMapper mapping = ClassNameMapper.mapperFromFile(map);
+ checkJarContent(jar, false, name -> metadataExtensionTester(name, mapping));
}
private void checkJarContent(Path jar) throws Exception {
if (!Files.exists(jar)) {
return;
}
- checkJarContent(
- jar,
- true,
- ImmutableSet.of(
- "META-INF/services/"
- + "com.android.tools.r8."
- + "jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions"));
+ checkJarContent(jar, true, name -> metadataExtensionTester(name, null));
+ }
+
+ private boolean metadataExtensionTester(String name, ClassNameMapper mapping) {
+ if (name.equals(EXT_IN_SRV)) {
+ assertNull(mapping);
+ return true;
+ }
+ if (mapping != null && name.startsWith(SRV_PREFIX)) {
+ String obfuscatedName = name.substring(SRV_PREFIX.length());
+ String originalName =
+ mapping.getObfuscatedToOriginalMapping().original
+ .getOrDefault(obfuscatedName, obfuscatedName);
+ if (originalName.equals(METADATA_EXTENSION)) {
+ return true;
+ }
+ }
+ return false;
}
@Test
public void testLibJarsContent() throws Exception {
- checkLibJarContent(ToolHelper.R8LIB_JAR);
- checkLibJarContent(ToolHelper.R8LIB_EXCLUDE_DEPS_JAR);
- checkLibJarContent(ToolHelper.COMPATDXLIB_JAR);
- checkLibJarContent(ToolHelper.COMPATPROGUARDLIB_JAR);
+ checkLibJarContent(ToolHelper.R8LIB_JAR, ToolHelper.R8LIB_MAP);
+ checkLibJarContent(ToolHelper.R8LIB_EXCLUDE_DEPS_JAR, ToolHelper.R8LIB_EXCLUDE_DEPS_MAP);
+ checkLibJarContent(ToolHelper.COMPATDXLIB_JAR, ToolHelper.COMPATDXLIB_MAP);
+ checkLibJarContent(ToolHelper.COMPATPROGUARDLIB_JAR, ToolHelper.COMPATPROGUARDLIB_MAP);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1d3ce80..b90b40c 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -98,8 +97,9 @@
return R8CompatTestBuilder.create(new TestState(temp), backend, forceProguardCompatibility);
}
- public static ExternalR8TestBuilder testForExternalR8(TemporaryFolder temp, Backend backend) {
- return ExternalR8TestBuilder.create(new TestState(temp), backend);
+ public static ExternalR8TestBuilder testForExternalR8(
+ TemporaryFolder temp, Backend backend, TestRuntime runtime) {
+ return ExternalR8TestBuilder.create(new TestState(temp), backend, runtime);
}
public static D8TestBuilder testForD8(TemporaryFolder temp) {
@@ -134,8 +134,8 @@
return testForR8Compat(temp, backend, forceProguardCompatibility);
}
- public ExternalR8TestBuilder testForExternalR8(Backend backend) {
- return testForExternalR8(temp, backend);
+ public ExternalR8TestBuilder testForExternalR8(Backend backend, TestRuntime runtime) {
+ return testForExternalR8(temp, backend, runtime);
}
public D8TestBuilder testForD8() {
@@ -175,10 +175,10 @@
}
public JavaCompilerTool javac(CfRuntime jdk) {
- return JavaCompilerTool.create(jdk.getVm(), temp);
+ return JavaCompilerTool.create(jdk, temp);
}
- public static JavaCompilerTool javac(CfVm jdk, TemporaryFolder temp) {
+ public static JavaCompilerTool javac(CfRuntime jdk, TemporaryFolder temp) {
return JavaCompilerTool.create(jdk, temp);
}
@@ -186,6 +186,10 @@
return KotlinCompilerTool.create(jdk, temp);
}
+ public KotlinCompilerTool kotlinc(CfRuntime jdk, Path kotlincJar) {
+ return KotlinCompilerTool.create(jdk, temp, kotlincJar);
+ }
+
public static KotlinCompilerTool kotlinc(CfRuntime jdk, TemporaryFolder temp) {
return KotlinCompilerTool.create(jdk, temp);
}
@@ -1198,6 +1202,7 @@
}
}
+ @Deprecated
public static Path runtimeJar(TestParameters parameters) {
if (parameters.isDexRuntime()) {
return ToolHelper.getAndroidJar(parameters.getRuntime().asDex().getMinApiLevel());
@@ -1207,7 +1212,6 @@
}
}
- @Deprecated
public static Path runtimeJar(Backend backend) {
if (backend == Backend.DEX) {
return ToolHelper.getDefaultAndroidJar();
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 8c643ff..13059f6 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -237,6 +237,11 @@
return self();
}
+ public CR assertOnlyErrors() {
+ getDiagnosticMessages().assertOnlyErrors();
+ return self();
+ }
+
public CR assertInfoMessageThatMatches(Matcher<String> matcher) {
getDiagnosticMessages().assertInfoMessageThatMatches(matcher);
return self();
@@ -257,6 +262,16 @@
return self();
}
+ public CR assertErrorMessageThatMatches(Matcher<String> matcher) {
+ getDiagnosticMessages().assertErrorMessageThatMatches(matcher);
+ return self();
+ }
+
+ public CR assertNoErrorMessageThatMatches(Matcher<String> matcher) {
+ getDiagnosticMessages().assertNoErrorMessageThatMatches(matcher);
+ return self();
+ }
+
public CR disassemble(PrintStream ps) throws IOException, ExecutionException {
ToolHelper.disassemble(app, ps);
return self();
@@ -294,8 +309,7 @@
.addAll(additionalClassPath)
.add(out)
.build();
- ProcessResult result =
- ToolHelper.runJava(runtime.asCf().getVm(), vmArguments, classPath, arguments);
+ ProcessResult result = ToolHelper.runJava(runtime.asCf(), vmArguments, classPath, arguments);
return createRunResult(runtime, result);
}
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index 70e545e..d72ba77 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -24,6 +24,7 @@
ART_V7_0_0,
ART_V8_1_0,
ART_V9_0_0,
+ ART_V10_0_0,
ART_DEFAULT,
JAVA;
@@ -46,6 +47,8 @@
return ART_V8_1_0;
case V9_0_0:
return ART_V9_0_0;
+ case V10_0_0:
+ return ART_V10_0_0;
case DEFAULT:
return ART_DEFAULT;
default:
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 5759c3a..702815a 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -36,4 +36,8 @@
public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
+
+ public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher);
+
+ public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher);
}
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 8282c77..478a60d 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -171,4 +171,14 @@
public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher) {
return assertNoMessageThatMatches(getWarnings(), "warning", matcher);
}
+
+ @Override
+ public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher) {
+ return assertMessageThatMatches(getErrors(), "error", matcher);
+ }
+
+ @Override
+ public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher) {
+ return assertNoMessageThatMatches(getErrors(), "error", matcher);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index f2504f2..35d37cc 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -3,14 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.TestRuntime.DexRuntime;
import com.android.tools.r8.TestRuntime.NoneRuntime;
import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -219,54 +217,45 @@
// Public method to check that the CF runtime coincides with the system runtime.
public static boolean isSystemJdk(CfVm vm) {
- String version = System.getProperty("java.version");
- switch (vm) {
- case JDK8:
- return version.startsWith("1.8.");
- case JDK9:
- return version.startsWith("9.");
- case JDK11:
- return version.startsWith("11.");
- }
- throw new Unreachable();
- }
-
- private static boolean isSupportedJdk(CfVm vm) {
- return isSystemJdk(vm) || TestRuntime.isCheckedInJDK(vm);
+ TestRuntime systemRuntime = TestRuntime.getSystemRuntime();
+ return systemRuntime.isCf() && systemRuntime.asCf().getVm().equals(vm);
}
public static boolean isRuntimesPropertySet() {
return getRuntimesProperty() != null;
}
- private static String getRuntimesProperty() {
+ public static String getRuntimesProperty() {
return System.getProperty("runtimes");
}
- private static Stream<TestRuntime> getAvailableRuntimes() {
- Stream<TestRuntime> runtimes;
- if (isRuntimesPropertySet()) {
- runtimes =
- Arrays.stream(getRuntimesProperty().split(":"))
- .filter(s -> !s.isEmpty())
- .map(
- name -> {
- TestRuntime runtime = TestRuntime.fromName(name);
- if (runtime != null) {
- return runtime;
- }
- throw new RuntimeException("Unexpected runtime property name: " + name);
- });
- } else {
- runtimes =
- Stream.concat(
- Stream.of(NoneRuntime.getInstance()),
- Stream.concat(
- Arrays.stream(TestRuntime.CfVm.values()).map(CfRuntime::new),
- Arrays.stream(DexVm.Version.values()).map(DexRuntime::new)));
+ private static Stream<TestRuntime> getUnfilteredAvailableRuntimes() {
+ // The runtimes are built in a linked hash map to ensure a deterministic order and avoid
+ // duplicates.
+ LinkedHashMap<TestRuntime, TestRuntime> runtimes = new LinkedHashMap<>();
+ // Place the none-runtime first.
+ NoneRuntime noneRuntime = NoneRuntime.getInstance();
+ runtimes.putIfAbsent(noneRuntime, noneRuntime);
+ // Then the checked in runtimes (CF and DEX).
+ for (TestRuntime checkedInRuntime : TestRuntime.getCheckedInRuntimes()) {
+ runtimes.putIfAbsent(checkedInRuntime, checkedInRuntime);
}
- // TODO(b/127785410) Support multiple VMs at the same time.
- return runtimes.filter(runtime -> !runtime.isCf() || isSupportedJdk(runtime.asCf().getVm()));
+ // Then finally the system runtime. It will likely be the same as a checked in and adding it
+ // makes the overall order more stable.
+ TestRuntime systemRuntime = TestRuntime.getSystemRuntime();
+ runtimes.putIfAbsent(systemRuntime, systemRuntime);
+ return runtimes.values().stream();
+ }
+
+ private static Stream<TestRuntime> getAvailableRuntimes() {
+ if (isRuntimesPropertySet()) {
+ String[] runtimeFilters = getRuntimesProperty().split(":");
+ return getUnfilteredAvailableRuntimes()
+ .filter(
+ runtime ->
+ Arrays.stream(runtimeFilters).anyMatch(filter -> runtime.name().equals(filter)));
+ }
+ return getUnfilteredAvailableRuntimes();
}
public static List<CfVm> getAvailableCfVms() {
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index d834ebc..c008660 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -6,31 +6,20 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableMap;
+import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
// Base class for the runtime structure in the test parameters.
-public class TestRuntime {
-
- public static TestRuntime fromName(String name) {
- if (NoneRuntime.NAME.equals(name)) {
- return NoneRuntime.getInstance();
- }
- CfVm cfVm = CfVm.fromName(name);
- if (cfVm != null) {
- return new CfRuntime(cfVm);
- }
- if (name.startsWith("dex-")) {
- DexVm dexVm = DexVm.fromShortName(name.substring(4) + "_host");
- if (dexVm != null) {
- return new DexRuntime(dexVm);
- }
- }
- return null;
- }
+public abstract class TestRuntime {
// Enum describing the possible/supported CF runtimes.
public enum CfVm {
@@ -79,51 +68,99 @@
}
}
- // Values are the path in third_party/openjdk to the repository with bin
- public static ImmutableMap<CfVm, Path> CHECKED_IN_JDKS = initializeCheckedInJDKs();
+ private static final Path JDK8_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk8");
+ private static final Path JDK9_PATH =
+ Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "openjdk-9.0.4");
+ private static final Path JDK11_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-11");
- public static ImmutableMap<CfVm, Path> initializeCheckedInJDKs() {
+ public static CfRuntime getCheckedInJdk8() {
+ Path home;
if (ToolHelper.isLinux()) {
- return ImmutableMap.of(
- CfVm.JDK8,
- Paths.get("jdk8", "linux-x86"),
- CfVm.JDK9,
- Paths.get("openjdk-9.0.4", "linux"),
- CfVm.JDK11,
- Paths.get("jdk-11", "Linux"));
+ home = JDK8_PATH.resolve("linux-x86");
+ } else if (ToolHelper.isMac()) {
+ home = JDK8_PATH.resolve("darwin-x86");
+ } else {
+ assert ToolHelper.isWindows();
+ return null;
}
- if (ToolHelper.isMac()) {
- return ImmutableMap.of(
- CfVm.JDK8,
- Paths.get("jdk8", "darwin-x86"),
- CfVm.JDK9,
- Paths.get("openjdk-9.0.4", "osx"),
- CfVm.JDK11,
- Paths.get("jdk-11", "Mac", "Contents", "Home"));
+ return new CfRuntime(CfVm.JDK8, home);
+ }
+
+ public static CfRuntime getCheckedInJdk9() {
+ Path home;
+ if (ToolHelper.isLinux()) {
+ home = JDK9_PATH.resolve("linux");
+ } else if (ToolHelper.isMac()) {
+ home = JDK9_PATH.resolve("osx");
+ } else {
+ assert ToolHelper.isWindows();
+ home = JDK9_PATH.resolve("windows");
}
- assert ToolHelper.isWindows();
- return ImmutableMap.of(
- CfVm.JDK9,
- Paths.get("openjdk-9.0.4", "windows"),
- CfVm.JDK11,
- Paths.get("jdk-11", "Windows"));
+ return new CfRuntime(CfVm.JDK9, home);
}
- public static boolean isCheckedInJDK(CfVm jdk) {
- return CHECKED_IN_JDKS.containsKey(jdk);
+ public static CfRuntime getCheckedInJdk11() {
+ Path home;
+ if (ToolHelper.isLinux()) {
+ home = JDK11_PATH.resolve("Linux");
+ } else if (ToolHelper.isMac()) {
+ home = Paths.get(JDK11_PATH.toString(), "Mac", "Contents", "Home");
+ } else {
+ assert ToolHelper.isWindows();
+ home = JDK11_PATH.resolve("Windows");
+ }
+ return new CfRuntime(CfVm.JDK11, home);
}
- public static Path getCheckedInJDKHome(CfVm jdk) {
- return Paths.get("third_party", "openjdk").resolve(CHECKED_IN_JDKS.get(jdk));
+ public static List<CfRuntime> getCheckedInCfRuntimes() {
+ CfRuntime[] jdks =
+ new CfRuntime[] {getCheckedInJdk8(), getCheckedInJdk9(), getCheckedInJdk11()};
+ Builder<CfRuntime> builder = ImmutableList.builder();
+ for (CfRuntime jdk : jdks) {
+ if (jdk != null) {
+ builder.add(jdk);
+ }
+ }
+ return builder.build();
}
- public static Path getCheckedInJDKPathFor(CfVm jdk) {
- return getCheckedInJDKHome(jdk).resolve(Paths.get("bin", "java"));
+ private static List<DexRuntime> getCheckedInDexRuntimes() {
+ if (ToolHelper.isLinux()) {
+ return ListUtils.map(Arrays.asList(DexVm.Version.values()), DexRuntime::new);
+ }
+ assert ToolHelper.isMac() || ToolHelper.isWindows();
+ return ImmutableList.of();
}
+ // For compatibility with old tests not specifying a Java runtime
+ @Deprecated
public static TestRuntime getDefaultJavaRuntime() {
- // For compatibility with old tests not specifying a Java runtime
- return new CfRuntime(CfVm.JDK9);
+ return getCheckedInJdk9();
+ }
+
+ public static List<TestRuntime> getCheckedInRuntimes() {
+ return ImmutableList.<TestRuntime>builder()
+ .addAll(getCheckedInCfRuntimes())
+ .addAll(getCheckedInDexRuntimes())
+ .build();
+ }
+
+ public static TestRuntime getSystemRuntime() {
+ String version = System.getProperty("java.version");
+ String home = System.getProperty("java.home");
+ if (version == null || version.isEmpty() || home == null || home.isEmpty()) {
+ throw new Unimplemented("Unable to create a system runtime");
+ }
+ if (version.startsWith("1.8.")) {
+ return new CfRuntime(CfVm.JDK8, Paths.get(home));
+ }
+ if (version.startsWith("9.")) {
+ return new CfRuntime(CfVm.JDK9, Paths.get(home));
+ }
+ if (version.startsWith("11.")) {
+ return new CfRuntime(CfVm.JDK11, Paths.get(home));
+ }
+ throw new Unimplemented("No support for JDK version: " + version);
}
public static class NoneRuntime extends TestRuntime {
@@ -138,9 +175,24 @@
}
@Override
+ public String name() {
+ return NAME;
+ }
+
+ @Override
public String toString() {
return NAME;
}
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
}
// Wrapper for the DEX runtimes.
@@ -158,6 +210,11 @@
}
@Override
+ public String name() {
+ return "dex-" + vm.getVersion().toString();
+ }
+
+ @Override
public boolean isDex() {
return true;
}
@@ -172,6 +229,20 @@
}
@Override
+ public boolean equals(Object other) {
+ if (!(other instanceof DexRuntime)) {
+ return false;
+ }
+ DexRuntime dexRuntime = (DexRuntime) other;
+ return vm == dexRuntime.vm;
+ }
+
+ @Override
+ public int hashCode() {
+ return vm.hashCode();
+ }
+
+ @Override
public String toString() {
return "dex-" + vm.getVersion().toString();
}
@@ -183,12 +254,26 @@
// Wrapper for the CF runtimes.
public static class CfRuntime extends TestRuntime {
-
private final CfVm vm;
+ private final Path home;
- public CfRuntime(CfVm vm) {
+ public CfRuntime(CfVm vm, Path home) {
assert vm != null;
this.vm = vm;
+ this.home = home.toAbsolutePath();
+ }
+
+ @Override
+ public String name() {
+ return vm.name().toLowerCase();
+ }
+
+ public Path getJavaHome() {
+ return home;
+ }
+
+ public Path getJavaExecutable() {
+ return home.resolve("bin").resolve("java");
}
@Override
@@ -212,7 +297,24 @@
@Override
public boolean equals(Object obj) {
- return obj instanceof CfRuntime && ((CfRuntime) obj).vm.equals(vm);
+ if (!(obj instanceof CfRuntime)) {
+ return false;
+ }
+ CfRuntime cfRuntime = (CfRuntime) obj;
+ return vm == cfRuntime.vm && home.equals(cfRuntime.home);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vm, home);
+ }
+
+ public boolean isNewerThan(CfVm version) {
+ return !vm.lessThanOrEqual(version);
+ }
+
+ public boolean isNewerThanOrEqual(CfVm version) {
+ return vm == version || !vm.lessThanOrEqual(version);
}
}
@@ -241,4 +343,12 @@
}
throw new Unreachable("Unexpected runtime without backend: " + this);
}
+
+ @Override
+ public abstract boolean equals(Object other);
+
+ @Override
+ public abstract int hashCode();
+
+ public abstract String name();
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index c6b305f..84fc2ee 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -145,6 +145,11 @@
"-keep class " + mainClass + " { public static void main(java.lang.String[]); }");
}
+ public T addKeepMainRules(List<String> mainClasses) {
+ mainClasses.forEach(this::addKeepMainRule);
+ return self();
+ }
+
public T addKeepMethodRules(Class<?> clazz, String... methodSignatures) {
StringBuilder sb = new StringBuilder();
sb.append("-keep class " + clazz.getTypeName() + " {\n");
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 0445166..772648ce 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.ToolHelper.DexVm.Kind;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.errors.Unreachable;
@@ -129,9 +129,12 @@
"third_party/rhino-android-1.1.1/rhino-android-1.1.1.jar";
public static final String RHINO_JAR = "third_party/rhino-1.7.10/rhino-1.7.10.jar";
static final String KT_PRELOADER = "third_party/kotlin/kotlinc/lib/kotlin-preloader.jar";
- static final String KT_COMPILER = "third_party/kotlin/kotlinc/lib/kotlin-compiler.jar";
+ public static final String KT_COMPILER = "third_party/kotlin/kotlinc/lib/kotlin-compiler.jar";
+ public static final String K2JVMCompiler = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler";
public static final String KT_STDLIB = "third_party/kotlin/kotlinc/lib/kotlin-stdlib.jar";
public static final String KT_REFLECT = "third_party/kotlin/kotlinc/lib/kotlin-reflect.jar";
+ public static final String KT_SCRIPT_RT =
+ "third_party/kotlin/kotlinc/lib/kotlin-script-runtime.jar";
private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
@@ -156,11 +159,16 @@
public static final Path R8_WITH_RELOCATED_DEPS_JAR_11 =
Paths.get(LIBS_DIR, "r8_with_relocated_deps_11.jar");
public static final Path R8LIB_JAR = Paths.get(LIBS_DIR, "r8lib.jar");
+ public static final Path R8LIB_MAP = Paths.get(LIBS_DIR, "r8lib.jar.map");
public static final Path COMPATDX_JAR = Paths.get(LIBS_DIR, "compatdx.jar");
public static final Path COMPATDXLIB_JAR = Paths.get(LIBS_DIR, "compatdxlib.jar");
+ public static final Path COMPATDXLIB_MAP = Paths.get(LIBS_DIR, "compatdxlib.jar.map");
public static final Path COMPATPROGUARD_JAR = Paths.get(LIBS_DIR, "compatproguard.jar");
public static final Path COMPATPROGUARDLIB_JAR = Paths.get(LIBS_DIR, "compatproguardlib.jar");
+ public static final Path COMPATPROGUARDLIB_MAP = Paths.get(LIBS_DIR, "compatproguardlib.jar.map");
public static final Path R8LIB_EXCLUDE_DEPS_JAR = Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar");
+ public static final Path R8LIB_EXCLUDE_DEPS_MAP =
+ Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar.map");
public static final Path DEPS_NOT_RELOCATED = Paths.get(LIBS_DIR, "deps-not-relocated.jar");
public static final Path DESUGAR_LIB_JSON_FOR_TESTING =
@@ -217,6 +225,8 @@
ART_8_1_0_HOST(Version.V8_1_0, Kind.HOST),
ART_9_0_0_TARGET(Version.V9_0_0, Kind.TARGET),
ART_9_0_0_HOST(Version.V9_0_0, Kind.HOST),
+ ART_10_0_0_TARGET(Version.V10_0_0, Kind.TARGET),
+ ART_10_0_0_HOST(Version.V10_0_0, Kind.HOST),
ART_DEFAULT(Version.DEFAULT, Kind.HOST);
private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP =
@@ -232,6 +242,7 @@
V7_0_0("7.0.0"),
V8_1_0("8.1.0"),
V9_0_0("9.0.0"),
+ V10_0_0("10.0.0"),
DEFAULT("default");
Version(String shortName) {
@@ -524,6 +535,7 @@
private static final Map<DexVm, String> ART_DIRS =
ImmutableMap.<DexVm, String>builder()
.put(DexVm.ART_DEFAULT, "art")
+ .put(DexVm.ART_10_0_0_HOST, "art-10.0.0")
.put(DexVm.ART_9_0_0_HOST, "art-9.0.0")
.put(DexVm.ART_8_1_0_HOST, "art-8.1.0")
.put(DexVm.ART_7_0_0_HOST, "art-7.0.0")
@@ -534,6 +546,7 @@
private static final Map<DexVm, String> ART_BINARY_VERSIONS =
ImmutableMap.<DexVm, String>builder()
.put(DexVm.ART_DEFAULT, "bin/art")
+ .put(DexVm.ART_10_0_0_HOST, "bin/art")
.put(DexVm.ART_9_0_0_HOST, "bin/art")
.put(DexVm.ART_8_1_0_HOST, "bin/art")
.put(DexVm.ART_7_0_0_HOST, "bin/art")
@@ -543,12 +556,13 @@
.put(DexVm.ART_4_0_4_HOST, "bin/dalvik").build();
private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 =
- ImmutableMap.of(
- DexVm.ART_DEFAULT, "bin/art",
- DexVm.ART_9_0_0_HOST, "bin/art",
- DexVm.ART_8_1_0_HOST, "bin/art",
- DexVm.ART_7_0_0_HOST, "bin/art",
- DexVm.ART_6_0_1_HOST, "bin/art");
+ ImmutableMap.<DexVm, String>builder()
+ .put(DexVm.ART_DEFAULT, "bin/art")
+ .put(DexVm.ART_10_0_0_HOST, "bin/art")
+ .put(DexVm.ART_9_0_0_HOST, "bin/art")
+ .put(DexVm.ART_8_1_0_HOST, "bin/art")
+ .put(DexVm.ART_7_0_0_HOST, "bin/art")
+ .put(DexVm.ART_6_0_1_HOST, "bin/art").build();
private static final List<String> DALVIK_BOOT_LIBS =
ImmutableList.of(
@@ -568,6 +582,7 @@
ImmutableMap.Builder<DexVm, List<String>> builder = ImmutableMap.builder();
builder
.put(DexVm.ART_DEFAULT, ART_BOOT_LIBS)
+ .put(DexVm.ART_10_0_0_HOST, ART_BOOT_LIBS)
.put(DexVm.ART_9_0_0_HOST, ART_BOOT_LIBS)
.put(DexVm.ART_8_1_0_HOST, ART_BOOT_LIBS)
.put(DexVm.ART_7_0_0_HOST, ART_BOOT_LIBS)
@@ -584,6 +599,7 @@
ImmutableMap.Builder<DexVm, String> builder = ImmutableMap.builder();
builder
.put(DexVm.ART_DEFAULT, "angler")
+ .put(DexVm.ART_10_0_0_HOST, "coral")
.put(DexVm.ART_9_0_0_HOST, "marlin")
.put(DexVm.ART_8_1_0_HOST, "marlin")
.put(DexVm.ART_7_0_0_HOST, "angler")
@@ -891,6 +907,8 @@
switch (dexVm.version) {
case DEFAULT:
return AndroidApiLevel.O;
+ case V10_0_0:
+ return AndroidApiLevel.Q;
case V9_0_0:
return AndroidApiLevel.P;
case V8_1_0:
@@ -1258,28 +1276,28 @@
public static ProcessResult runJava(List<String> vmArgs, List<Path> classpath, String... args)
throws IOException {
- return runJava(null, vmArgs, classpath, args);
+ return runJava(TestRuntime.getSystemRuntime().asCf(), vmArgs, classpath, args);
}
- public static ProcessResult runJava(CfVm runtime, List<Path> classpath, String... args)
+ public static ProcessResult runJava(CfRuntime runtime, List<Path> classpath, String... args)
throws IOException {
return runJava(runtime, ImmutableList.of(), classpath, args);
}
+ @Deprecated
public static ProcessResult runKotlinc(
- CfVm runtime,
List<Path> classPaths,
Path directoryToCompileInto,
List<String> extraOptions,
Path... filesToCompile)
throws IOException {
- List<String> cmdline = new ArrayList<>(Arrays.asList(getJavaExecutable(runtime)));
+ List<String> cmdline = new ArrayList<>(Arrays.asList(getJavaExecutable()));
cmdline.add("-jar");
cmdline.add(KT_PRELOADER);
cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
cmdline.add("-cp");
cmdline.add(KT_COMPILER);
- cmdline.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
+ cmdline.add(K2JVMCompiler);
String[] strings = Arrays.stream(filesToCompile).map(Path::toString).toArray(String[]::new);
Collections.addAll(cmdline, strings);
cmdline.add("-d");
@@ -1302,10 +1320,11 @@
}
public static ProcessResult runJava(
- CfVm runtime, List<String> vmArgs, List<Path> classpath, String... args) throws IOException {
+ CfRuntime runtime, List<String> vmArgs, List<Path> classpath, String... args)
+ throws IOException {
String cp =
classpath.stream().map(Path::toString).collect(Collectors.joining(CLASSPATH_SEPARATOR));
- List<String> cmdline = new ArrayList<String>(Arrays.asList(getJavaExecutable(runtime)));
+ List<String> cmdline = new ArrayList<>(Arrays.asList(runtime.getJavaExecutable().toString()));
cmdline.addAll(vmArgs);
cmdline.add("-cp");
cmdline.add(cp);
@@ -1407,27 +1426,13 @@
}
@Deprecated
- // Use getJavaExecutable(CfVm) to specify a JDK version or getSystemJavaExecutable
+ // Use CfRuntime.getJavaExecutable() for a specific JDK or getSystemJavaExecutable
public static String getJavaExecutable() {
return getSystemJavaExecutable();
}
public static String getSystemJavaExecutable() {
- return Paths.get(System.getProperty("java.home"), "bin", "java").toString();
- }
-
- public static String getJavaExecutable(CfVm runtime) {
- if (TestRuntime.isCheckedInJDK(runtime)) {
- return TestRuntime.getCheckedInJDKPathFor(runtime).toString();
- } else {
- // TODO(b/127785410): Always assume a non-null runtime.
- assert runtime == null || TestParametersBuilder.isSystemJdk(runtime);
- return getSystemJavaExecutable();
- }
- }
-
- public static Path getJavaHome(CfVm runtime) {
- return TestRuntime.getCheckedInJDKHome(runtime);
+ return TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString();
}
public static ProcessResult runArtRaw(ArtCommandBuilder builder) throws IOException {
@@ -1787,6 +1792,7 @@
public static ProcessResult runDex2OatRaw(Path file, Path outFile, DexVm vm) throws IOException {
// TODO(jmhenaff): find a way to run this on windows (push dex and run on device/emulator?)
Assume.assumeTrue(ToolHelper.isDex2OatSupported());
+ Assume.assumeFalse("b/144975341", vm.version == DexVm.Version.V10_0_0);
if (vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
// Run default dex2oat for tests on dalvik runtimes.
vm = DexVm.ART_DEFAULT;
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
rename to src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index a1e1f56..a19cfec 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -1,7 +1,7 @@
// 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 com.android.tools.r8.cf;
+package com.android.tools.r8.cf.bootstrap;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static com.google.common.io.ByteStreams.toByteArray;
@@ -15,6 +15,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.FileUtils;
@@ -54,6 +55,7 @@
private static Pair<Path, Path> r8R8Debug;
private static Pair<Path, Path> r8R8Release;
+ private final TestParameters parameters;
private static boolean testExternal = true;
@ClassRule public static TemporaryFolder testFolder = new TemporaryFolder();
@@ -72,7 +74,7 @@
}
public BootstrapCurrentEqualityTest(TestParameters parameters) {
- // TODO: use parameters to run on the right java.
+ this.parameters = parameters;
}
private static Pair<Path, Path> compileR8(CompilationMode mode) throws Exception {
@@ -80,7 +82,7 @@
final Path jar = testFolder.newFolder().toPath().resolve("out.jar");
final Path map = testFolder.newFolder().toPath().resolve("out.map");
if (testExternal) {
- testForExternalR8(newTempFolder(), Backend.CF)
+ testForExternalR8(newTempFolder(), Backend.CF, TestRuntime.getCheckedInJdk9())
.useR8WithRelocatedDeps()
.setMode(mode)
.addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
@@ -121,7 +123,7 @@
return;
}
Path runR81 =
- testForExternalR8(Backend.CF)
+ testForExternalR8(parameters.getBackend(), parameters.getRuntime())
.useProvidedR8(ToolHelper.R8LIB_JAR)
.addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
.addKeepRuleFiles(MAIN_KEEP)
@@ -129,7 +131,7 @@
.compile()
.outputJar();
Path runR82 =
- testForExternalR8(Backend.CF)
+ testForExternalR8(parameters.getBackend(), parameters.getRuntime())
.useProvidedR8(ToolHelper.R8LIB_EXCLUDE_DEPS_JAR)
.addR8ExternalDepsToClasspath()
.addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
@@ -151,7 +153,7 @@
private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
throws Exception {
ExternalR8TestCompileResult runR8Debug =
- testForExternalR8(newTempFolder(), Backend.CF)
+ testForExternalR8(newTempFolder(), parameters.getBackend(), parameters.getRuntime())
.useR8WithRelocatedDeps()
.addProgramFiles(program)
.addKeepRules(keep)
@@ -159,7 +161,7 @@
.compile();
assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar(), args).toString());
ExternalR8TestCompileResult runR8Release =
- testForExternalR8(newTempFolder(), Backend.CF)
+ testForExternalR8(newTempFolder(), parameters.getBackend(), parameters.getRuntime())
.useR8WithRelocatedDeps()
.addProgramFiles(program)
.addKeepRules(keep)
@@ -181,7 +183,7 @@
CompilationMode mode)
throws Exception {
ExternalR8TestCompileResult runR8R8 =
- testForExternalR8(newTempFolder(), Backend.CF)
+ testForExternalR8(newTempFolder(), parameters.getBackend(), parameters.getRuntime())
.useProvidedR8(r8.getFirst())
.addProgramFiles(program)
.addKeepRules(keep)
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
similarity index 99%
rename from src/test/java/com/android/tools/r8/cf/BootstrapTest.java
rename to src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
index addc3ec..722e4f6 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
@@ -1,7 +1,7 @@
// 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 com.android.tools.r8.cf;
+package com.android.tools.r8.cf.bootstrap;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static com.google.common.io.ByteStreams.toByteArray;
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/Hello.kt b/src/test/java/com/android/tools/r8/cf/bootstrap/Hello.kt
new file mode 100644
index 0000000..fdfc655
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/Hello.kt
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, 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.cf.bootstrap
+
+fun main(args : Array<String>) {
+ println("I'm Woody. Howdy, howdy, howdy.")
+}
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
new file mode 100644
index 0000000..c7a6bc0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2019, 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.cf.bootstrap;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.internal.CompilationTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Ignore;
+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 KotlinCompilerTreeShakingTest extends CompilationTestBase {
+
+ private static final String PKG_NAME = KotlinCompilerTreeShakingTest.class.getPackage().getName();
+ private static final Path HELLO_KT =
+ Paths.get(
+ ToolHelper.TESTS_DIR,
+ "java",
+ DescriptorUtils.getBinaryNameFromJavaType(PKG_NAME),
+ "Hello.kt");
+ private static final int MAX_SIZE = (int) (31361268 * 0.4);
+
+ private TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ public KotlinCompilerTreeShakingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testForRuntime() throws Exception {
+ // Compile Hello.kt and make sure it works as expected.
+ Path classPathBefore =
+ kotlinc(parameters.getRuntime().asCf())
+ .addSourceFiles(HELLO_KT)
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addClasspath(classPathBefore)
+ .run(parameters.getRuntime(), PKG_NAME + ".HelloKt")
+ .assertSuccessWithOutputLines("I'm Woody. Howdy, howdy, howdy.");
+ }
+
+ @Ignore(
+ "b/136457753: assertion error in static class merger; "
+ + "b/144861881: force-inlining of non-inlineable constructors in vertical class merger; "
+ + "b/144877828: assertion error in method naming state during interface method renaming; "
+ + "b/144859533: umbrella"
+ )
+ @Test
+ public void test() throws Exception {
+ List<Path> libs =
+ ImmutableList.of(
+ ToolHelper.getKotlinStdlibJar(),
+ ToolHelper.getKotlinReflectJar(),
+ Paths.get(ToolHelper.KT_SCRIPT_RT));
+ // Process kotlin-compiler.jar.
+ Path r8ProcessedKotlinc =
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(libs)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .addProgramFiles(Paths.get(ToolHelper.KT_COMPILER))
+ .addKeepAttributes("*Annotation*")
+ .addKeepClassAndMembersRules(ToolHelper.K2JVMCompiler)
+ .addKeepClassAndMembersRules("**.K2JVMCompilerArguments")
+ .addKeepClassAndMembersRules("**.*Argument*")
+ .addKeepClassAndMembersRules("**.Freezable")
+ .addKeepRules(
+ "-keepclassmembers class * {",
+ " *** parseCommandLineArguments(...);",
+ "}"
+ )
+ .addKeepRules(
+ "-keepclassmembers,allowoptimization enum * {",
+ " public static **[] values();",
+ " public static ** valueOf(java.lang.String);",
+ "}")
+ .addOptionsModification(o -> {
+ // Ignore com.sun.tools.javac.main.JavaCompiler and others
+ // Resulting jar may not be able to deal with .java source files, though.
+ o.ignoreMissingClasses = true;
+ // b/144861100: invoke-static on interface is allowed up to JDK 8.
+ o.testing.allowInvokeErrors = true;
+ // TODO(b/144861881): force-inlining of non-inlineable constructors.
+ o.enableVerticalClassMerging = false;
+ })
+ .compile()
+ .writeToZip();
+
+ // Copy libraries used by kotlin-compiler.jar so that Preloader can load them.
+ Path dir = r8ProcessedKotlinc.getParent();
+ for (Path lib : libs) {
+ Path newLib = dir.resolve(lib.getFileName());
+ Files.copy(lib, newLib, REPLACE_EXISTING);
+ }
+
+ // TODO(b/144859533): passing `dir` as -kotlin-home.
+ // Compile Hello.kt again with r8-processed kotlin-compiler.jar
+ Path classPathAfter =
+ kotlinc(parameters.getRuntime().asCf(), r8ProcessedKotlinc)
+ .addSourceFiles(HELLO_KT)
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addClasspath(classPathAfter)
+ .run(parameters.getRuntime(), PKG_NAME + ".HelloKt")
+ .assertSuccessWithOutputLines("I'm Woody. Howdy, howdy, howdy.");
+
+ int size = applicationSize(AndroidApp.builder().addProgramFile(r8ProcessedKotlinc).build());
+ assertTrue(size <= MAX_SIZE);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index 1a5c127..ae8012a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -5,12 +5,11 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.APIConversionTestBase;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -33,7 +32,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class CustomCollectionTest extends CoreLibDesugarTestBase {
+public class CustomCollectionTest extends APIConversionTestBase {
private final TestParameters parameters;
private final boolean shrinkDesugaredLibrary;
@@ -63,11 +62,10 @@
.compile()
.inspect(
inspector -> {
- this.assertNoWrappers(inspector);
this.assertCustomCollectionCallsCorrect(inspector, false);
})
.addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary,
+ this::buildDesugaredLibraryWithConversionExtension,
parameters.getApiLevel(),
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
@@ -82,13 +80,6 @@
// Expected output is emulated interfaces expected output.
assertLines2By2Correct(stdOut);
}
- String[] split = stdErr.split("Could not find method");
- if (split.length > 2) {
- fail("Could not find multiple methods");
- } else if (split.length == 2) {
- // On some VMs the Serialized lambda code is missing.
- assertTrue(stdErr.contains("SerializedLambda"));
- }
}
@Test
@@ -108,7 +99,6 @@
.compile()
.inspect(
inspector -> {
- this.assertNoWrappers(inspector);
this.assertCustomCollectionCallsCorrect(inspector, true);
})
.addDesugaredCoreLibraryRunClassPath(
@@ -121,11 +111,6 @@
assertResultCorrect(r8TestRunResult.getStdOut(), r8TestRunResult.getStdErr());
}
- private void assertNoWrappers(CodeInspector inspector) {
- assertTrue(inspector.allClasses().stream().noneMatch(cl -> cl.getOriginalName().startsWith(
- DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)));
- }
-
private void assertCustomCollectionCallsCorrect(CodeInspector inspector, boolean r8) {
MethodSubject direct = inspector.clazz(EXECUTOR).uniqueMethodWithName("directTypes");
// TODO(b/134732760): Due to memberRebinding, R8 is not as precise as D8 regarding
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index b60de38..df86791 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
@@ -86,6 +87,9 @@
clazz.getOriginalName().startsWith("j$.")
|| clazz
.getOriginalName()
+ .startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)
+ || clazz
+ .getOriginalName()
.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)));
assertThat(inspector.clazz("j$.time.Clock"), isPresent());
// Above N the following classes are removed instead of being desugared.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
index 0d4f8a2..c45b403 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import java.util.Arrays;
@@ -22,7 +21,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class APIConversionTest extends CoreLibDesugarTestBase {
+public class APIConversionTest extends APIConversionTestBase {
private final TestParameters parameters;
@@ -61,14 +60,14 @@
.enableCoreLibraryDesugaring(parameters.getApiLevel())
.compile()
.assertOnlyInfos() // No warnings.
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, parameters.getApiLevel())
.run(parameters.getRuntime(), Executor.class)
.assertSuccessWithOutput(
StringUtils.lines(
"[5, 6, 7]",
"$r8$wrapper$java$util$stream$IntStream$-V-WRP",
- "Unsupported conversion for java.util.IntSummaryStatistics. See compilation time"
- + " infos for more details."));
+ "IntSummaryStatistics"));
}
static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
index 7d816e5..29b316d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -26,7 +26,7 @@
+ " windows",
!ToolHelper.isWindows());
- CfRuntime runtime = new CfRuntime(CfVm.JDK8);
+ CfRuntime runtime = TestRuntime.getCheckedInJdk8();
Path conversionFolder = temp.newFolder("conversions").toPath();
// Compile the stubs to be able to compile the conversions.
@@ -51,6 +51,10 @@
}
protected Path buildDesugaredLibraryWithConversionExtension(AndroidApiLevel apiLevel) {
+ return buildDesugaredLibraryWithConversionExtension(apiLevel, "", false);
+ }
+
+ protected Path buildDesugaredLibraryWithConversionExtension(AndroidApiLevel apiLevel,String keepRules, boolean shrink) {
Path[] timeConversionClasses;
try {
timeConversionClasses = getConversionClasses();
@@ -59,6 +63,6 @@
}
ArrayList<Path> paths = new ArrayList<>();
Collections.addAll(paths, timeConversionClasses);
- return buildDesugaredLibrary(apiLevel, "", false, paths);
+ return buildDesugaredLibrary(apiLevel, keepRules, shrink, paths);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
new file mode 100644
index 0000000..79cad2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, 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.desugar.desugaredlibrary.conversiontests;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+
+public class DuplicateAPIDesugaredLibTest extends APIConversionTestBase {
+
+ @Test
+ public void testLib() throws Exception {
+ Box<Path> desugaredLibBox = new Box<>();
+ Path customLib =
+ testForD8()
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip();
+ String stdOut =
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ (AndroidApiLevel api) -> {
+ desugaredLibBox.set(this.buildDesugaredLibraryWithConversionExtension(api));
+ return desugaredLibBox.get();
+ },
+ AndroidApiLevel.B)
+ .addRunClasspathFiles(customLib)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertDupMethod(new CodeInspector(desugaredLibBox.get()));
+ assertLines2By2Correct(stdOut);
+ }
+
+ private void assertDupMethod(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz("j$.util.concurrent.ConcurrentHashMap");
+ assertEquals(
+ 2,
+ clazz.virtualMethods().stream().filter(m -> m.getOriginalName().equals("forEach")).count());
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ Map<Integer, Double> map = new ConcurrentHashMap<>();
+ map.put(1, 1.1);
+ map.put(2, 2.2);
+ BiConsumer<Integer, Double> biConsumer = (x, y) -> System.out.print(" " + x + " " + y);
+ map.forEach(biConsumer);
+ System.out.println();
+ CustomLibClass.javaForEach(map, biConsumer);
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+
+ @SuppressWarnings("WeakerAccess")
+ public static <K, V> void javaForEach(Map<K, V> map, BiConsumer<K, V> consumer) {
+ map.forEach(consumer);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
new file mode 100644
index 0000000..31ca3b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, 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.desugar.desugaredlibrary.conversiontests;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+
+public class DuplicateAPIProgramTest extends APIConversionTestBase {
+
+ @Test
+ public void testMap() throws Exception {
+ Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ String stdOut =
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class, MyMap.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .inspect(this::assertDupMethod)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+ .addRunClasspathFiles(customLib)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertLines2By2Correct(stdOut);
+ }
+
+ private void assertDupMethod(CodeInspector i) {
+ assertEquals(
+ 2,
+ i.clazz(MyMap.class).virtualMethods().stream()
+ .filter(m -> m.getOriginalName().equals("forEach"))
+ .count());
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ IdentityHashMap<Integer, Double> map = new MyMap<>();
+ map.put(1, 1.1);
+ map.put(2, 2.2);
+ BiConsumer<Integer, Double> biConsumer = (x, y) -> System.out.print(" " + x + " " + y);
+ map.forEach(biConsumer);
+ System.out.println();
+ CustomLibClass.javaForEach(map, biConsumer);
+ }
+ }
+
+ public static class MyMap<K, V> extends IdentityHashMap<K, V> {
+
+ @Override
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ System.out.print("ForEach");
+ super.forEach(action);
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+ @SuppressWarnings("WeakerAccess")
+ public static <K, V> void javaForEach(Map<K, V> map, BiConsumer<K, V> consumer) {
+ map.forEach(consumer);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
index 6e791b6..a281c3d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
@@ -60,6 +60,12 @@
"java.util.SortedSet": "j$.util.SortedSet",
"java.util.Set": "j$.util.Set",
"java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
+ },
+ "custom_conversion": {
+ "java.util.Optional": "j$.util.OptionalConversions",
+ "java.util.OptionalDouble": "j$.util.OptionalConversions",
+ "java.util.OptionalInt": "j$.util.OptionalConversions",
+ "java.util.OptionalLong": "j$.util.OptionalConversions"
}
}
],
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
index db0a09a..d2501e9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
@@ -10,7 +10,7 @@
import static org.hamcrest.CoreMatchers.endsWith;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.BooleanUtils;
@@ -65,7 +65,7 @@
public static void compileAtomicClasses() throws Exception {
File atomicClassesDir = new File(ATOMIC_COMPILED_TESTS_FOLDER.toString());
assert atomicClassesDir.exists() || atomicClassesDir.mkdirs();
- javac(CfVm.JDK11, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
.addSourceFiles(ATOMIC_TESTS_FILES)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
index 71458f0..ff779f5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
@@ -7,27 +7,23 @@
import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
-import static junit.framework.TestCase.assertTrue;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.endsWith;
-import static org.hamcrest.CoreMatchers.not;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -77,7 +73,7 @@
public static void compileConcurrentClasses() throws Exception {
File concurrentClassesDir = new File(CONCURRENT_COMPILED_TESTS_FOLDER.toString());
assert concurrentClassesDir.exists() || concurrentClassesDir.mkdirs();
- javac(CfVm.JDK11, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
.addSourceFiles(getAllFilesWithSuffixInDirectory(CONCURRENT_TESTS_FOLDER, JAVA_EXTENSION))
@@ -95,7 +91,7 @@
Path[] classesToCompile = concurrentHashFilesAndDependencies.toArray(new Path[0]);
File concurrentHashClassesDir = new File(CONCURRENT_HASH_COMPILED_TESTS_FOLDER.toString());
assert concurrentHashClassesDir.exists() || concurrentHashClassesDir.mkdirs();
- javac(CfVm.JDK11, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
.addSourceFiles(classesToCompile)
@@ -110,6 +106,8 @@
public void testD8Concurrent() throws Exception {
// TODO(b/134732760): Support Java 9+ libraries.
// We skip the ConcurrentRemoveIf test because of the non desugared class CompletableFuture.
+ Assume.assumeFalse("b/144975341",
+ parameters.getRuntime().asDex().getVm().getVersion() == Version.V10_0_0);
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
String verbosity = "2";
testForD8()
@@ -120,7 +118,6 @@
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.compile()
- .inspect(this::assertNoConversions)
.withArt6Plus64BitsLib()
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
@@ -132,15 +129,6 @@
endsWith(StringUtils.lines("ConcurrentModification: SUCCESS")));
}
- private void assertNoConversions(CodeInspector inspector) {
- assertTrue(
- inspector.allClasses().stream()
- .noneMatch(
- cl ->
- cl.getOriginalName()
- .startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)));
- }
-
private Path[] concurrentHashTestToCompile() {
// We exclude WhiteBox.class because of Method handles, they are not supported on old devices
// and the test uses methods not present even on 28.
@@ -175,6 +163,8 @@
@Test
public void testD8ConcurrentHash() throws Exception {
+ Assume.assumeFalse("b/144975341",
+ parameters.getRuntime().asDex().getVm().getVersion() == Version.V10_0_0);
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
String verbosity = "2";
D8TestCompileResult d8TestCompileResult =
@@ -191,7 +181,6 @@
parameters.getApiLevel(),
keepRuleConsumer.get(),
shrinkDesugaredLibrary);
- System.out.println(keepRuleConsumer.get());
for (String className : concurrentHashTestNGTestsToRun()) {
d8TestCompileResult
.run(parameters.getRuntime(), "TestNGMainRunner", verbosity, className)
@@ -202,9 +191,7 @@
// Failure implies a runtime exception.
// We ensure that everything could be resolved (no missing method or class)
// with the assertion on stderr.
- d8TestCompileResult
- .run(parameters.getRuntime(), className).assertSuccess()
- .assertStderrMatches(not(containsString("Could not")));
+ d8TestCompileResult.run(parameters.getRuntime(), className).assertSuccess();
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
index 9a0b63b..b3e6156 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
@@ -8,7 +8,7 @@
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -80,7 +80,7 @@
"java.base=ALL-UNNAMED",
"--patch-module",
"java.base=" + JDK_11_JAVA_BASE_EXTENSION_FILES_DIR);
- javac(CfVm.JDK11, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addOptions(options)
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
index 9f64237..69c3558 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import java.io.File;
@@ -68,14 +69,14 @@
public static void compileMathClasses() throws Exception {
File mathClassesDir = new File(JDK_11_MATH_TESTS_DIR.toString());
assert mathClassesDir.exists() || mathClassesDir.mkdirs();
- javac(CfVm.JDK11, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addSourceFiles(JDK_11_MATH_JAVA_FILES)
.setOutputPath(JDK_11_MATH_TESTS_DIR)
.compile();
File strictMathClassesDir = new File(JDK_11_STRICT_MATH_TESTS_DIR.toString());
assert strictMathClassesDir.exists() || strictMathClassesDir.mkdirs();
- javac(CfVm.JDK11, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addSourceFiles(JDK_11_STRICT_MATH_JAVA_FILES)
.setOutputPath(JDK_11_STRICT_MATH_TESTS_DIR)
.compile();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
index 730ee1b..c8a3e2d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
@@ -55,7 +56,7 @@
public static void compileObjectsClass() throws Exception {
File objectsDir = new File(JDK_11_OBJECTS_TESTS_DIR.toString());
assert objectsDir.exists() || objectsDir.mkdirs();
- javac(CfVm.JDK11, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addSourceFiles(JDK_11_OBJECTS_JAVA_DIR.resolve(BASIC_OBJECTS_TEST + JAVA_EXTENSION))
.setOutputPath(JDK_11_OBJECTS_TESTS_DIR)
.compile();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
index 784d320..62e1bf9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -172,7 +172,7 @@
"java.base=ALL-UNNAMED",
"--patch-module",
"java.base=" + JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR);
- javac(CfVm.JDK11, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addOptions(options)
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index 86a4edb..819e6ce 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -16,7 +16,7 @@
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -43,7 +43,7 @@
public static void compilePathBackport() throws Exception {
assumeTrue("JDK8 is not checked-in on Windows", !ToolHelper.isWindows());
pathMock = getStaticTemp().newFolder("PathMock").toPath();
- javac(CfVm.JDK8, getStaticTemp())
+ javac(TestRuntime.getCheckedInJdk8(), getStaticTemp())
.setOutputPath(pathMock)
.addSourceFiles(
getAllFilesWithSuffixInDirectory(Paths.get("src/test/r8OnArtBackport"), "java"))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index 882c545..2533619 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -10,11 +10,11 @@
import com.android.tools.r8.R8;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.cf.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
import java.io.File;
import java.nio.file.Path;
@@ -72,7 +72,7 @@
Path ouputThroughCf = ouputFolder.toPath().resolve("outThroughCf.zip").toAbsolutePath();
ProcessResult javaProcessResult =
ToolHelper.runJava(
- CfVm.JDK9,
+ TestRuntime.getCheckedInJdk9(),
Collections.singletonList(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR),
"-Xmx512m",
R8.class.getTypeName(),
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index 073eda1..1f6832c 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -11,9 +11,10 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.cf.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
@@ -51,7 +52,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withCfRuntime(CfVm.JDK11).build();
+ return getTestParameters().withCfRuntimes().build();
}
@BeforeClass
@@ -95,19 +96,21 @@
Path prevGeneratedJar = null;
String prevRunResult = null;
for (Path jar : jarsToCompare()) {
+ // All jars except ToolHelper.R8_WITH_RELOCATED_DEPS_JAR are compiled for JDK11.
+ TestRuntime runtime =
+ jar == ToolHelper.R8_WITH_RELOCATED_DEPS_JAR
+ ? parameters.getRuntime()
+ : TestRuntime.getCheckedInJdk11();
Path generatedJar =
- testForExternalR8(Backend.CF)
+ testForExternalR8(Backend.CF, runtime)
.useProvidedR8(jar)
- .useExternalJDK(jar == ToolHelper.R8_WITH_RELOCATED_DEPS_JAR ? null : CfVm.JDK11)
.addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION))
.addKeepRules(HELLO_KEEP)
.compile()
.outputJar();
String runResult =
ToolHelper.runJava(
- parameters.getRuntime().asCf().getVm(),
- ImmutableList.of(generatedJar),
- "hello.Hello")
+ parameters.getRuntime().asCf(), ImmutableList.of(generatedJar), "hello.Hello")
.toString();
if (prevRunResult != null) {
assertEquals(prevRunResult, runResult);
@@ -123,12 +126,13 @@
@Test
public void testR8() throws Exception {
Assume.assumeTrue(!ToolHelper.isWindows());
+ Assume.assumeTrue(parameters.isCfRuntime());
+ Assume.assumeTrue(CfVm.JDK11 == parameters.getRuntime().asCf().getVm());
Path prevGeneratedJar = null;
for (Path jar : jarsToCompare()) {
Path generatedJar =
- testForExternalR8(Backend.CF)
+ testForExternalR8(Backend.CF, parameters.getRuntime())
.useProvidedR8(jar)
- .useExternalJDK(CfVm.JDK11)
.addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION))
.addKeepRuleFiles(MAIN_KEEP)
.compile()
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
new file mode 100644
index 0000000..f72a98f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2019, 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.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeFinalTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeFinalTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCallingFinal()
+ throws IOException, CompilationFailedException, ExecutionException {
+ boolean hasIncorrectSuperLookup =
+ parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST);
+ TestRunResult<?> runResult =
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class, A.class)
+ .addProgramClassFileData(
+ getClassWithTransformedInvoked(B.class), getClassWithTransformedInvoked(C.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "Hello from B",
+ "Hello from B",
+ hasIncorrectSuperLookup ? "Hello from A" : "Hello from B",
+ "Hello from B",
+ "Hello from A",
+ "Hello from B");
+ }
+
+ private byte[] getClassWithTransformedInvoked(Class<?> clazz) throws IOException {
+ return transformer(clazz)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ // The super call to bar() is already INVOKESPECIAL.
+ assertTrue(name.equals("foo") || opcode == INVOKESPECIAL);
+ continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ public static class A {
+
+ public void foo() {
+ System.out.println("Hello from A");
+ }
+
+ public void bar() {
+ // TODO(b/110175213): We cannot change this to an invoke-special since this requires a
+ // direct bridge in DEX.
+ foo();
+ }
+ }
+
+ public static class B extends A {
+
+ // Having a final method allows us to rewrite invoke-special foo() to invoke-virtual foo().
+ public final void foo() {
+ System.out.println("Hello from B");
+ }
+
+ public void bar() {
+ foo();
+ ((A) this).foo();
+ super.bar();
+ }
+ }
+
+ public static class C extends B {
+
+ public void bar() {
+ foo();
+ ((B) this).foo();
+ ((A) this).foo();
+ super.bar();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new C().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
new file mode 100644
index 0000000..816288d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2019, 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.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialForInvokeVirtualTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialForInvokeVirtualTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ TestRunResult<?> runResult =
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getClassBWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ private byte[] getClassBWithTransformedInvoked() throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(INVOKEVIRTUAL, opcode);
+ continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ public static class A {
+
+ void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class B extends A {
+
+ void bar() {
+ foo();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
new file mode 100644
index 0000000..a54fa1d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2019, 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.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialForNonDeclaredInvokeVirtualTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialForNonDeclaredInvokeVirtualTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ TestRunResult<?> runResult =
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addProgramClassFileData(getClassCWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ private byte[] getClassCWithTransformedInvoked() throws IOException {
+ return transformer(C.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(INVOKEVIRTUAL, opcode);
+ continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ public static class A {
+
+ void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class B extends A {}
+
+ public static class C extends B {
+
+ void bar() {
+ foo();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new C().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
new file mode 100644
index 0000000..f49c764
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2019, 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.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialInterfaceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ boolean hasSegmentationFaultOnInvokeSuper =
+ parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST);
+ TestRunResult<?> runResult =
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(I.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class);
+ // TODO(b/110175213): Remove when fixed.
+ if (parameters.isCfRuntime()) {
+ runResult.assertSuccessWithOutputLines("Hello World!");
+ } else {
+ runResult.assertFailureWithErrorThatMatches(
+ containsString(hasSegmentationFaultOnInvokeSuper ? "SIGSEGV" : "NoSuchMethodError"));
+ }
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(INVOKEVIRTUAL, opcode);
+ continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ public interface I {
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class B implements I {
+
+ public void bar() {
+ foo(); // Will be rewritten to invoke-special B.foo()
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
new file mode 100644
index 0000000..1253df8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, 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.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialInterfaceWithBridgeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialInterfaceWithBridgeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ TestRunResult<?> runResult =
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(I.class, A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(INVOKEVIRTUAL, opcode);
+ assertEquals(owner, DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()));
+ continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ public interface I {
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class A implements I {}
+
+ public static class B extends A {
+
+ public void bar() {
+ foo(); // Will be rewritten to invoke-special A.foo()
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
new file mode 100644
index 0000000..e046730
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, 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.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialMissingInvokeVirtualTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialMissingInvokeVirtualTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ TestRunResult<?> runResult =
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(B.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(INVOKEVIRTUAL, opcode);
+ assertEquals("notify", name);
+ continuation.apply(
+ INVOKESPECIAL,
+ DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+ "foo",
+ descriptor,
+ isInterface);
+ })
+ .transform();
+ }
+
+ public static class A {}
+
+ public static class B extends A {
+
+ public void bar() {
+ notify(); // Will be rewritten to invoke-special A.foo() which is missing.
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
new file mode 100644
index 0000000..00f4bb3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2019, 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.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeSpecialOnSameClassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeSpecialOnSameClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ try {
+ testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ // TODO(b/110175213): Remove when fixed.
+ assertTrue(parameters.isCfRuntime());
+ } catch (CompilationFailedException compilation) {
+ assertThat(
+ compilation.getCause().getMessage(),
+ containsString("Failed to compile unsupported use of invokespecial"));
+ }
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(A.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ public static class A {
+
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+
+ public void bar() {
+ foo(); // Will be rewritten to invoke-special A.foo()
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new A().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
index 5e2d674..83d0b83 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.graph.invokespecial.Main;
@@ -52,13 +53,15 @@
.assertSuccessWithOutput(StringUtils.lines("true", "false"));
}
- @Test
+ @Test(expected = CompilationFailedException.class)
public void testD8Behavior() throws Exception {
// TODO(b/110175213): Should succeed with output "true\nfalse\n".
testForD8()
.addProgramFiles(inputJar)
- .run(Main.class)
- .assertFailureWithErrorThatMatches(containsString(getExpectedOutput()));
+ .compileWithExpectedDiagnostics(
+ testDiagnosticMessages ->
+ testDiagnosticMessages.assertErrorMessageThatMatches(
+ containsString("Failed to compile unsupported use of invokespecial")));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeVirtualFinalTest.java b/src/test/java/com/android/tools/r8/graph/InvokeVirtualFinalTest.java
new file mode 100644
index 0000000..781731f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeVirtualFinalTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2019, 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.hamcrest.CoreMatchers.containsString;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.transformers.ClassTransformer;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualFinalTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeVirtualFinalTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testInvokeSpecialOnClassWithFinal()
+ throws ExecutionException, CompilationFailedException, IOException {
+ String expectedError = "overrides final";
+ if (parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST)) {
+ expectedError = "LinkageError";
+ }
+ testForRuntime(parameters)
+ .addProgramClasses(B.class, Main.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString(expectedError));
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(A.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ if (name.equals("foo")) {
+ access |= ACC_FINAL;
+ }
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ })
+ .transform();
+ }
+
+ public static class A {
+ public void foo() {
+ System.out.println("Hello from A");
+ }
+ }
+
+ public static class B extends A {
+ public void foo() {
+ System.out.println("Hello from B");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().foo();
+ ((A) new B()).foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
new file mode 100644
index 0000000..67191c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2019, 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.invokestatic;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestRuntime.CfVm;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeStaticOnInterfaceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ public InvokeStaticOnInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws IOException, CompilationFailedException, ExecutionException {
+ assertTrue(parameters.isCfRuntime());
+ TestRunResult<?> runResult =
+ testForRuntime(parameters)
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .run(parameters.getRuntime(), Main.class);
+ if (parameters.getRuntime().asCf().isNewerThan(CfVm.JDK8)) {
+ runResult.assertFailureWithErrorThatMatches(
+ containsString(
+ "java.lang.IncompatibleClassChangeError: Method"
+ + " com.android.tools.r8.graph.invokestatic.InvokeStaticOnInterfaceTest$I.foo()V"
+ + " must be InterfaceMethodref constant"));
+ } else {
+ runResult.assertSuccessWithOutputLines("Hello World!");
+ }
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testCfInvokeOnStaticInterfaceMethod_failed()
+ throws ExecutionException, CompilationFailedException, IOException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class);
+ }
+
+ @Test
+ public void testCfInvokeOnStaticInterfaceMethod_errorAllowed()
+ throws ExecutionException, CompilationFailedException, IOException {
+ TestRunResult<?> runResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getClassWithTransformedInvoked())
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .addOptionsModification(o -> o.testing.allowInvokeErrors = true)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class);
+ if (parameters.getRuntime().asCf().isNewerThan(CfVm.JDK8)) {
+ runResult.assertFailureWithErrorThatMatches(
+ containsString(
+ "java.lang.IncompatibleClassChangeError: Method"
+ + " com.android.tools.r8.graph.invokestatic.a.a()V"
+ + " must be InterfaceMethodref constant"));
+ } else {
+ runResult.assertSuccessWithOutputLines("Hello World!");
+ }
+ }
+
+ private byte[] getClassWithTransformedInvoked() throws IOException {
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(INVOKESTATIC, opcode);
+ assertTrue(isInterface);
+ continuation.apply(opcode, owner, name, descriptor, false);
+ })
+ .transform();
+ }
+
+ @NeverMerge
+ public interface I {
+
+ @NeverInline
+ static void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ I.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 07ccabd..3127150 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -5,15 +5,15 @@
package com.android.tools.r8.internal.proto;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.List;
@@ -21,93 +21,178 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+// TODO(b/112437944): Strengthen test to ensure that builder inlining succeeds even without single-
+// and double-caller inlining.
@RunWith(Parameterized.class)
public class Proto2BuilderShrinkingTest extends ProtoShrinkingTestBase {
private static final String LITE_BUILDER = "com.google.protobuf.GeneratedMessageLite$Builder";
- private static final String TEST_CLASS = "proto2.BuilderTestClass";
private static List<Path> PROGRAM_FILES =
ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
- private final boolean enableMinification;
+ private final List<String> mains;
private final TestParameters parameters;
- @Parameterized.Parameters(name = "{1}, enable minification: {0}")
+ @Parameterized.Parameters(name = "{1}, {0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(
+ ImmutableList.of(
+ ImmutableList.of("proto2.BuilderWithOneofSetterTestClass"),
+ ImmutableList.of("proto2.BuilderWithPrimitiveSettersTestClass"),
+ ImmutableList.of("proto2.BuilderWithProtoBuilderSetterTestClass"),
+ ImmutableList.of("proto2.BuilderWithProtoSetterTestClass"),
+ ImmutableList.of("proto2.BuilderWithReusedSettersTestClass"),
+ ImmutableList.of(
+ "proto2.BuilderWithOneofSetterTestClass",
+ "proto2.BuilderWithPrimitiveSettersTestClass",
+ "proto2.BuilderWithProtoBuilderSetterTestClass",
+ "proto2.BuilderWithProtoSetterTestClass",
+ "proto2.BuilderWithReusedSettersTestClass")),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public Proto2BuilderShrinkingTest(boolean enableMinification, TestParameters parameters) {
- this.enableMinification = enableMinification;
+ public Proto2BuilderShrinkingTest(List<String> mains, TestParameters parameters) {
+ this.mains = mains;
this.parameters = parameters;
}
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
- .addProgramFiles(PROGRAM_FILES)
- .addKeepMainRule(TEST_CLASS)
- .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
- .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
- .addKeepRules("-neverinline class " + TEST_CLASS + " { <methods>; }")
- .addOptionsModification(
- options -> {
- options.enableFieldBitAccessAnalysis = true;
- options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
- options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
- options.enableStringSwitchConversion = true;
- })
- .allowAccessModification()
- .allowUnusedProguardConfigurationRules()
- .enableInliningAnnotations()
- .minification(enableMinification)
- .setMinApi(parameters.getRuntime())
- .compile()
- .inspect(this::inspect)
- .run(parameters.getRuntime(), TEST_CLASS)
- .assertSuccessWithOutputLines(
- "builderWithPrimitiveSetters",
- "17",
- "16",
- "builderWithReusedSetters",
- "1",
- "qux",
- "builderWithProtoBuilderSetter",
- "42",
- "builderWithProtoSetter",
- "42",
+ R8TestCompileResult result =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(PROGRAM_FILES)
+ .addKeepMainRules(mains)
+ .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+ .addOptionsModification(
+ options -> {
+ options.applyInliningToInlinee = true;
+ options.enableFieldBitAccessAnalysis = true;
+ options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+ options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+ options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
+ options.enableStringSwitchConversion = true;
+ })
+ .allowAccessModification()
+ .allowUnusedProguardConfigurationRules()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect);
+
+ // TODO(b/112437944): Should never allow dynamicMethod() to be inlined unless MethodToInvoke is
+ // guaranteed to be different from MethodToInvoke.BUILD_MESSAGE_INFO.
+ assumeTrue(mains.size() > 1);
+
+ for (String main : mains) {
+ result.run(parameters.getRuntime(), main).assertSuccessWithOutput(getExpectedOutput(main));
+ }
+ }
+
+ private static String getExpectedOutput(String main) {
+ switch (main) {
+ case "proto2.BuilderWithOneofSetterTestClass":
+ return StringUtils.lines(
"builderWithOneofSetter",
- "foo");
+ "false",
+ "0",
+ "true",
+ "foo",
+ "false",
+ "0",
+ "false",
+ "0",
+ "false",
+ "");
+ case "proto2.BuilderWithPrimitiveSettersTestClass":
+ return StringUtils.lines(
+ "builderWithPrimitiveSetters",
+ "true",
+ "17",
+ "false",
+ "",
+ "false",
+ "0",
+ "false",
+ "0",
+ "false",
+ "",
+ "false",
+ "0",
+ "false",
+ "",
+ "false",
+ "0",
+ "true",
+ "16",
+ "false",
+ "");
+ case "proto2.BuilderWithProtoBuilderSetterTestClass":
+ return StringUtils.lines("builderWithProtoBuilderSetter", "42");
+ case "proto2.BuilderWithProtoSetterTestClass":
+ return StringUtils.lines("builderWithProtoSetter", "42");
+ case "proto2.BuilderWithReusedSettersTestClass":
+ return StringUtils.lines(
+ "builderWithReusedSetters",
+ "true",
+ "1",
+ "false",
+ "",
+ "false",
+ "0",
+ "false",
+ "0",
+ "false",
+ "",
+ "true",
+ "1",
+ "false",
+ "",
+ "false",
+ "0",
+ "false",
+ "0",
+ "true",
+ "qux");
+ default:
+ throw new Unreachable();
+ }
}
private void inspect(CodeInspector outputInspector) {
- ClassSubject liteClassSubject = outputInspector.clazz(LITE_BUILDER);
- assertThat(liteClassSubject, isPresent());
+ // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
+ // is kept.
+ assertThat(outputInspector.clazz(LITE_BUILDER), isPresent());
- MethodSubject copyOnWriteMethodSubject = liteClassSubject.uniqueMethodWithName("copyOnWrite");
- assertThat(copyOnWriteMethodSubject, isPresent());
+ // TODO(b/112437944): Should be absent.
+ assertThat(
+ outputInspector.clazz("com.android.tools.r8.proto2.TestProto$NestedMessage$Builder"),
+ isNestedMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
- ClassSubject testClassSubject = outputInspector.clazz(TEST_CLASS);
- assertThat(testClassSubject, isPresent());
+ // TODO(b/112437944): Should be absent.
+ assertThat(
+ outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
+ isOuterMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
- List<String> testNames =
- ImmutableList.of(
- "builderWithPrimitiveSetters",
- "builderWithReusedSetters",
- "builderWithProtoBuilderSetter",
- "builderWithProtoSetter",
- "builderWithOneofSetter");
- for (String testName : testNames) {
- MethodSubject methodSubject = testClassSubject.uniqueMethodWithName(testName);
- assertThat(methodSubject, isPresent());
- assertTrue(
- methodSubject
- .streamInstructions()
- .filter(InstructionSubject::isInvoke)
- .map(InstructionSubject::getMethod)
- // TODO(b/112437944): Only builderWithReusedSetters() should invoke copyOnWrite().
- .anyMatch(method -> method == copyOnWriteMethodSubject.getMethod().method));
- }
+ // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
+ // is kept.
+ assertThat(
+ outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
+ isPrimitivesBuilderUsed(mains) ? isPresent() : not(isPresent()));
+ }
+
+ private static boolean isNestedMessageBuilderUsed(List<String> mains) {
+ return mains.contains("proto2.BuilderWithProtoBuilderSetterTestClass")
+ || mains.contains("proto2.BuilderWithProtoSetterTestClass");
+ }
+
+ private static boolean isOuterMessageBuilderUsed(List<String> mains) {
+ return isNestedMessageBuilderUsed(mains);
+ }
+
+ private static boolean isPrimitivesBuilderUsed(List<String> mains) {
+ return mains.contains("proto2.BuilderWithOneofSetterTestClass")
+ || mains.contains("proto2.BuilderWithPrimitiveSettersTestClass")
+ || mains.contains("proto2.BuilderWithReusedSettersTestClass");
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 2071b82..aed2c26 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -83,8 +83,9 @@
.addOptionsModification(
options -> {
options.enableFieldBitAccessAnalysis = true;
- options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+ options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+ options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
options.enableStringSwitchConversion = true;
})
.allowAccessModification(allowAccessModification)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index 7a67427..faa91e5 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -57,8 +57,10 @@
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
.addOptionsModification(
options -> {
- options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+ options.enableFieldBitAccessAnalysis = true;
options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+ options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+ options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
options.enableStringSwitchConversion = true;
})
.allowAccessModification(allowAccessModification)
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index e210196..25c4a53 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -40,7 +40,6 @@
@RunWith(Parameterized.class)
public class GenerateBackportMethods extends TestBase {
- static final Path javaExecutable = Paths.get(ToolHelper.getJavaExecutable(CfVm.JDK9));
static final Path googleFormatDir = Paths.get(ToolHelper.THIRD_PARTY_DIR, "google-java-format");
static final Path googleFormatJar =
googleFormatDir.resolve("google-java-format-1.7-all-deps.jar");
@@ -90,7 +89,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
+ return getTestParameters().withCfRuntime(CfVm.JDK9).build();
}
public GenerateBackportMethods(TestParameters parameters) {
@@ -104,15 +103,18 @@
assertEquals("Classes should be listed in sorted order", sorted, methodTemplateClasses);
assertEquals(
FileUtils.readTextFile(backportMethodsFile, StandardCharsets.UTF_8),
- generateBackportMethods());
+ generateBackportMethods(parameters.getRuntime().asCf().getJavaExecutable().toString()));
}
// Running this method will regenerate / overwrite the content of the backport methods.
public static void main(String[] args) throws Exception {
- FileUtils.writeToFile(backportMethodsFile, null, generateBackportMethods().getBytes());
+ FileUtils.writeToFile(
+ backportMethodsFile,
+ null,
+ generateBackportMethods(ToolHelper.getSystemJavaExecutable()).getBytes());
}
- private static String generateBackportMethods() throws IOException {
+ private static String generateBackportMethods(String javaExecutable) throws IOException {
InternalOptions options = new InternalOptions();
CfCodePrinter codePrinter = new CfCodePrinter();
JarClassFileReader reader =
@@ -146,7 +148,7 @@
ProcessBuilder builder =
new ProcessBuilder(
ImmutableList.of(
- javaExecutable.toString(),
+ javaExecutable,
"-jar",
googleFormatJar.toString(),
outfile.toAbsolutePath().toString()));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithControlFlowTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithControlFlowTest.java
new file mode 100644
index 0000000..44db913
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithControlFlowTest.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2019, 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerBuilderWithControlFlowTest extends ClassInlinerTestBase {
+
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "flow = >0>0>-1>1234>1236>7>3>1240>1266>6>1248>3798>8>1254>10>1264>8885>12>1273>19063>14>"
+ + "16>1288>39449>18>1301>80229>20>1315>161806>23>1335>324994>25>1353>651383>27>1372>1"
+ + "304183>29>1392>2609806>32>1418>5221092>34>1442>10443683>36>1467>20888893>38>1493>4"
+ + "1779342>40>1520>42>1551>83560313>44>1581>167122280>46>1612>334246248>48>1644>66849"
+ + "4219>50>1677>1336990197>52>54>1716>-1620985089>56>1753>1052998963>58>1791>21059998"
+ + "12>60>1830>-82965744>62>1870>-165929517>64>1911>-331857019>");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlinerBuilderWithControlFlowTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlinerBuilderWithControlFlowTest.class)
+ .addKeepMainRule(TestClass.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testExpectedBehavior() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(ClassInlinerBuilderWithControlFlowTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+
+ assertEquals(
+ Collections.singleton(StringBuilder.class.getTypeName()), collectTypes(clazz.mainMethod()));
+
+ assertThat(inspector.clazz(ControlFlow.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ ControlFlow flow = new ControlFlow(-1, 2, 7);
+ for (int k = 0; k < 25; k++) {
+ if (k % 3 == 0) {
+ flow.foo(k);
+ } else if (k % 3 == 1) {
+ flow.bar(1, 2, 3, 4);
+ }
+ }
+ System.out.println("flow = " + flow.toString());
+ }
+ }
+
+ static class ControlFlow {
+
+ int a;
+ int b;
+ int c = 1234;
+ int d;
+ String s = ">";
+
+ ControlFlow(int b, int c, int d) {
+ this.s += this.a++ + ">";
+ this.s += this.b + ">";
+ this.b = b;
+ this.s += this.b + ">";
+ this.s += this.c + ">";
+ this.c += c;
+ this.s += this.c + ">";
+ this.s += (this.d = d) + ">";
+ }
+
+ void foo(int count) {
+ for (int i = 0; i < count; i++) {
+ switch (i % 4) {
+ case 0:
+ this.s += ++this.a + ">";
+ break;
+ case 1:
+ this.c += this.b;
+ this.s += this.c + ">";
+ break;
+ case 2:
+ this.d += this.d++ + this.c++ + this.b++ + this.a++;
+ this.s += this.d + ">";
+ break;
+ }
+ }
+ }
+
+ void bar(int a, int b, int c, int d) {
+ this.a += a;
+ this.b += b;
+ this.c += c;
+ this.d += d;
+ }
+
+ @Override
+ public String toString() {
+ return s;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithMoreControlFlowTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithMoreControlFlowTest.java
new file mode 100644
index 0000000..3778db4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithMoreControlFlowTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2019, 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerBuilderWithMoreControlFlowTest extends ClassInlinerTestBase {
+
+ private static final String EXPECTED = StringUtils.lines("Pos(x=0, y=10)");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlinerBuilderWithMoreControlFlowTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlinerBuilderWithMoreControlFlowTest.class)
+ .addKeepMainRule(TestClass.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testExpectedBehavior() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(ClassInlinerBuilderWithMoreControlFlowTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+
+ assertEquals(
+ Collections.singleton(StringBuilder.class.getTypeName()), collectTypes(clazz.mainMethod()));
+
+ assertThat(inspector.clazz(Pos.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ String str = "1234567890";
+ Pos pos = new Pos();
+ while (pos.y < str.length()) {
+ pos.x = pos.y;
+ pos.y = pos.x;
+
+ if (str.charAt(pos.x) != '*') {
+ if ('0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
+ while (pos.y < str.length() && '0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
+ pos.y++;
+ }
+ }
+ }
+ }
+ System.out.println(pos.myToString());
+ }
+ }
+
+ static class Pos {
+
+ int x = 0;
+ int y = 0;
+
+ String myToString() {
+ return "Pos(x=" + x + ", y=" + y + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
new file mode 100644
index 0000000..f751e25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2019, 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerSimplePairBuilderTest extends ClassInlinerTestBase {
+
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "[before] first = null",
+ "[after] first = f1",
+ "Pair(f1, <null>)",
+ "[before] second = null",
+ "[after] second = s2",
+ "Pair(<null>, s2)",
+ "[before] first = null",
+ "[after] first = f3",
+ "[before] second = null",
+ "[after] second = s4",
+ "Pair(f3, s4)");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlinerSimplePairBuilderTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlinerSimplePairBuilderTest.class)
+ .addKeepMainRule(TestClass.class)
+ // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6.
+ .addOptionsModification(options -> options.inliningInstructionLimit = 6)
+ .enableInliningAnnotations()
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testExpectedBehavior() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(ClassInlinerSimplePairBuilderTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ if (parameters.isCfRuntime()) {
+ assertThat(inspector.clazz(PairBuilder.class), isPresent());
+
+ // const-string canonicalization is disabled in CF, which helps ClassInliner identify
+ // PairBuilder as candidate.
+ Set<String> expected =
+ ImmutableSet.of(StringBuilder.class.getTypeName(), PairBuilder.class.getTypeName());
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
+ } else {
+ assertThat(inspector.clazz(PairBuilder.class), not(isPresent()));
+
+ Set<String> expected = ImmutableSet.of(StringBuilder.class.getTypeName());
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
+ assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testSimpleBuilder1();
+ testSimpleBuilder2();
+ testSimpleBuilder3();
+ }
+
+ @NeverInline
+ static void testSimpleBuilder1() {
+ System.out.println(new PairBuilder<String, String>().setFirst("f1").build().myToString());
+ }
+
+ @NeverInline
+ static void testSimpleBuilder2() {
+ System.out.println(new PairBuilder<String, String>().setSecond("s2").build().myToString());
+ }
+
+ @NeverInline
+ static void testSimpleBuilder3() {
+ System.out.println(
+ new PairBuilder<String, String>().setFirst("f3").setSecond("s4").build().myToString());
+ }
+ }
+
+ static class Pair<F, S> {
+ final F first;
+ final S second;
+
+ Pair(F first, S second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ String myToString() {
+ return "Pair("
+ + (first == null ? "<null>" : first)
+ + ", "
+ + (second == null ? "<null>" : second)
+ + ")";
+ }
+ }
+
+ static class PairBuilder<F, S> {
+
+ F first;
+ S second = null;
+
+ PairBuilder<F, S> setFirst(F first) {
+ System.out.println("[before] first = " + this.first);
+ this.first = first;
+ System.out.println("[after] first = " + this.first);
+ return this;
+ }
+
+ PairBuilder<F, S> setSecond(S second) {
+ System.out.println("[before] second = " + this.second);
+ this.second = second;
+ System.out.println("[after] second = " + this.second);
+ return this;
+ }
+
+ public Pair<F, S> build() {
+ return new Pair<>(first, second);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java
new file mode 100644
index 0000000..6e5dc39
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2019, 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerSimplePairBuilderWithMultipleBuildsTest extends ClassInlinerTestBase {
+
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "Pair(<null>, <null>)",
+ "[before] first = null",
+ "[after] first = f1",
+ "Pair(f1, <null>)",
+ "[before] second = null",
+ "[after] second = s2",
+ "Pair(f1, s2)");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlinerSimplePairBuilderWithMultipleBuildsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlinerSimplePairBuilderWithMultipleBuildsTest.class)
+ .addKeepMainRule(TestClass.class)
+ // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6.
+ .addOptionsModification(options -> options.inliningInstructionLimit = 6)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testExpectedBehavior() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(ClassInlinerSimplePairBuilderWithMultipleBuildsTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+
+ // Note that Pair created instances were also inlined in the following method since
+ // we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
+ // as in the above method, the instance of pair would be passed to println() which
+ // would make it not eligible for inlining.
+ assertEquals(
+ Collections.singleton(StringBuilder.class.getTypeName()), collectTypes(clazz.mainMethod()));
+
+ assertThat(inspector.clazz(PairBuilder.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ PairBuilder<String, String> builder = new PairBuilder<>();
+ Pair p1 = builder.build();
+ System.out.println(p1.myToString());
+ builder.setFirst("f1");
+ Pair p2 = builder.build();
+ System.out.println(p2.myToString());
+ builder.setSecond("s2");
+ Pair p3 = builder.build();
+ System.out.println(p3.myToString());
+ }
+ }
+
+ static class Pair<F, S> {
+
+ final F first;
+ final S second;
+
+ Pair(F first, S second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ String myToString() {
+ return "Pair("
+ + (first == null ? "<null>" : first)
+ + ", "
+ + (second == null ? "<null>" : second)
+ + ")";
+ }
+ }
+
+ static class PairBuilder<F, S> {
+
+ F first;
+ S second = null;
+
+ void setFirst(F first) {
+ System.out.println("[before] first = " + this.first);
+ this.first = first;
+ System.out.println("[after] first = " + this.first);
+ }
+
+ void setSecond(S second) {
+ System.out.println("[before] second = " + this.second);
+ this.second = second;
+ System.out.println("[after] second = " + this.second);
+ }
+
+ public Pair<F, S> build() {
+ return new Pair<>(first, second);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index e088bf0..325bdd5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -11,18 +11,11 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
-import com.android.tools.r8.ir.optimize.classinliner.builders.ControlFlow;
-import com.android.tools.r8.ir.optimize.classinliner.builders.Pair;
-import com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder;
-import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
@@ -40,41 +33,33 @@
import com.android.tools.r8.ir.optimize.classinliner.trivial.TrivialTestClass;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
import java.util.Collections;
-import java.util.Iterator;
import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class ClassInlinerTest extends TestBase {
+public class ClassInlinerTest extends ClassInlinerTestBase {
- private final Backend backend;
+ private final TestParameters parameters;
- @Parameterized.Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public ClassInlinerTest(Backend backend) {
- this.backend = backend;
+ public ClassInlinerTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
@@ -96,7 +81,7 @@
};
String javaOutput = runOnJava(main);
TestRunResult result =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
.addKeepMainRule(main)
@@ -112,132 +97,56 @@
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
- collectTypes(clazz, "testInner", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testInner")));
assertEquals(
Collections.emptySet(),
- collectTypes(clazz, "testConstructorMapping1", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testConstructorMapping1")));
assertEquals(
Collections.emptySet(),
- collectTypes(clazz, "testConstructorMapping2", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testConstructorMapping2")));
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
- collectTypes(clazz, "testConstructorMapping3", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testConstructorMapping3")));
assertEquals(
- Collections.emptySet(),
- collectTypes(clazz, "testEmptyClass", "void"));
+ Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("testEmptyClass")));
assertEquals(
Collections.singleton(
"com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer"),
- collectTypes(clazz, "testEmptyClassWithInitializer", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testEmptyClassWithInitializer")));
assertEquals(
Collections.singleton(
"com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal"),
- collectTypes(clazz, "testClassWithFinalizer", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testClassWithFinalizer")));
assertEquals(
- Collections.emptySet(),
- collectTypes(clazz, "testCallOnIface1", "void"));
+ Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("testCallOnIface1")));
assertEquals(
- Collections.singleton(
- "com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
- collectTypes(clazz, "testCallOnIface2", "void"));
+ Collections.singleton("com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
+ collectTypes(clazz.uniqueMethodWithName("testCallOnIface2")));
assertEquals(
Sets.newHashSet(
"com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB",
"java.lang.StringBuilder"),
- collectTypes(clazz, "testCycles", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testCycles")));
assertEquals(
- Sets.newHashSet("java.lang.StringBuilder",
+ Sets.newHashSet(
+ "java.lang.StringBuilder",
"com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB"),
- collectTypes(inspector.clazz(CycleReferenceAB.class), "foo", "void", "int"));
+ collectTypes(inspector.clazz(CycleReferenceAB.class).uniqueMethodWithName("foo")));
assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
}
@Test
- public void testBuilders() throws Exception {
- Class<?> main = BuildersTestClass.class;
- Class<?>[] classes = {
- NeverInline.class,
- BuildersTestClass.class,
- BuildersTestClass.Pos.class,
- Tuple.class,
- Pair.class,
- PairBuilder.class,
- ControlFlow.class,
- };
- String javaOutput = runOnJava(main);
- TestRunResult result =
- testForR8(backend)
- .addProgramClasses(classes)
- .enableInliningAnnotations()
- .addKeepMainRule(main)
- .addKeepAttributes("LineNumberTable")
- .addOptionsModification(
- o -> {
- o.inliningInstructionLimit = 6;
- configure(o);
- })
- .allowAccessModification()
- .noMinification()
- .run(main)
- .assertSuccessWithOutput(javaOutput);
-
- CodeInspector inspector = result.inspector();
- ClassSubject clazz = inspector.clazz(main);
-
- for (int i = 1; i <= 3; i++) {
- Set<String> expected =
- backend == Backend.CF
- // const-string canonicalization is disabled in CF, which helps ClassInliner identify
- // PairBuilder as candidate.
- ? ImmutableSet.of(
- "java.lang.StringBuilder",
- "com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder")
- : ImmutableSet.of("java.lang.StringBuilder");
- assertEquals(expected, collectTypes(clazz, "testSimpleBuilder" + i, "void"));
- }
-
- // Note that Pair created instances were also inlined in the following method since
- // we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
- // as in the above method, the instance of pair would be passed to println() which
- // would make it not eligible for inlining.
- // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6.
- assertEquals(
- Collections.singleton("java.lang.StringBuilder"),
- collectTypes(clazz, "testSimpleBuilderWithMultipleBuilds", "void"));
-
- if (backend == Backend.DEX) {
- assertFalse(inspector.clazz(PairBuilder.class).isPresent());
- }
-
- assertEquals(
- Collections.singleton("java.lang.StringBuilder"),
- collectTypes(clazz, "testBuilderConstructors", "void"));
-
- assertFalse(inspector.clazz(Tuple.class).isPresent());
-
- assertEquals(
- Collections.singleton("java.lang.StringBuilder"),
- collectTypes(clazz, "testWithControlFlow", "void"));
-
- assertFalse(inspector.clazz(ControlFlow.class).isPresent());
-
- assertEquals(Collections.emptySet(), collectTypes(clazz, "testWithMoreControlFlow", "void"));
-
- assertFalse(inspector.clazz(BuildersTestClass.Pos.class).isPresent());
- }
-
- @Test
public void testErroneousInput() throws Exception {
JasminBuilder builder = new JasminBuilder();
@@ -258,7 +167,11 @@
" return");
AndroidApp compiled =
- compileWithR8(builder.build(), getProguardConfig(mainClass.name), this::configure, backend);
+ compileWithR8(
+ builder.build(),
+ getProguardConfig(mainClass.name),
+ this::configure,
+ parameters.getBackend());
// Check that the code fails with an IncompatibleClassChangeError with Java.
ProcessResult javaResult =
@@ -267,7 +180,7 @@
// Check that the code fails with an IncompatibleClassChangeError with ART.
ProcessResult result =
- backend == Backend.DEX
+ parameters.isDexRuntime()
? runOnArtRaw(compiled, mainClass.name)
: runOnJavaRaw(compiled, mainClass.name, Collections.emptyList());
assertThat(result.stderr, containsString("IncompatibleClassChangeError"));
@@ -284,7 +197,7 @@
};
String javaOutput = runOnJava(main);
TestRunResult result =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
.addKeepMainRule(main)
@@ -298,17 +211,11 @@
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(C.class);
- assertEquals(
- Collections.emptySet(),
- collectTypes(clazz, "method1", "int"));
+ assertEquals(Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("method1")));
- assertEquals(
- Collections.emptySet(),
- collectTypes(clazz, "method2", "int"));
+ assertEquals(Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("method2")));
- assertEquals(
- Collections.emptySet(),
- collectTypes(clazz, "method3", "int"));
+ assertEquals(Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("method3")));
assertFalse(inspector.clazz(C.L.class).isPresent());
assertFalse(inspector.clazz(C.F.class).isPresent());
@@ -326,7 +233,7 @@
};
String javaOutput = runOnJava(main);
TestRunResult result =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableProguardTestOptions()
.enableInliningAnnotations()
@@ -350,15 +257,15 @@
// TODO(b/143129517, 141719453): This expectation relies on the class inlining limits.
assertEquals(
Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"),
- collectTypes(clazz, "testExtraNeverReturnsNormally", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testExtraNeverReturnsNormally")));
assertEquals(
Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"),
- collectTypes(clazz, "testDirectNeverReturnsNormally", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testDirectNeverReturnsNormally")));
assertEquals(
Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"),
- collectTypes(clazz, "testInitNeverReturnsNormally", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testInitNeverReturnsNormally")));
assertThat(inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class), isPresent());
assertThat(
@@ -367,7 +274,7 @@
// TODO(b/143129517, b/141719453): This expectation relies on the class inlining limits.
assertEquals(
Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"),
- collectTypes(clazz, "testRootInvalidatesAfterInlining", "void"));
+ collectTypes(clazz.uniqueMethodWithName("testRootInvalidatesAfterInlining")));
assertThat(inspector.clazz(InvalidRootsTestClass.A.class), not(isPresent()));
assertThat(inspector.clazz(InvalidRootsTestClass.B.class), not(isPresent()));
@@ -375,7 +282,7 @@
@Test
public void testDesugaredLambdas() throws Exception {
- Assume.assumeFalse("No desugaring with CF backend", backend == Backend.CF);
+ Assume.assumeFalse("No desugaring with CF backend", parameters.isCfRuntime());
Class<?> main = LambdasTestClass.class;
Class<?>[] classes = {
LambdasTestClass.class,
@@ -384,7 +291,7 @@
};
String javaOutput = runOnJava(main);
TestRunResult result =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramClasses(classes)
.addKeepMainRule(main)
.addKeepAttributes("LineNumberTable")
@@ -404,9 +311,8 @@
ClassSubject clazz = inspector.clazz(main);
assertEquals(
- Sets.newHashSet(
- "java.lang.StringBuilder"),
- collectTypes(clazz, "testStatelessLambda", "void"));
+ Sets.newHashSet("java.lang.StringBuilder"),
+ collectTypes(clazz.uniqueMethodWithName("testStatelessLambda")));
// TODO(b/120814598): Should only be "java.lang.StringBuilder". Lambdas are not class inlined
// because parameter usage is not available for each lambda constructor.
@@ -416,9 +322,7 @@
.map(FoundClassSubject::getFinalName)
.filter(name -> name.contains(LAMBDA_CLASS_NAME_PREFIX))
.collect(Collectors.toList()));
- assertEquals(
- expectedTypes,
- collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+ assertEquals(expectedTypes, collectTypes(clazz.uniqueMethodWithName("testStatefulLambda")));
// TODO(b/120814598): Should be 0. Lambdas are not class inlined because parameter usage is not
// available for each lambda constructor.
@@ -427,36 +331,6 @@
inspector.allClasses().stream().filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
}
- private Set<String> collectTypes(
- ClassSubject clazz, String methodName, String retValue, String... params) {
- return Stream.concat(
- collectNewInstanceTypesWithRetValue(clazz, methodName, retValue, params),
- collectStaticGetTypesWithRetValue(clazz, methodName, retValue, params)
- ).collect(Collectors.toSet());
- }
-
- private Stream<String> collectNewInstanceTypesWithRetValue(
- ClassSubject clazz, String methodName, String retValue, String... params) {
- assertNotNull(clazz);
- MethodSignature signature = new MethodSignature(methodName, retValue, params);
- Iterator<InstructionSubject> iterator = clazz.method(signature).iterateInstructions();
- return Streams.stream(iterator)
- .filter(InstructionSubject::isNewInstance)
- .map(is -> ((NewInstanceInstructionSubject) is).getType().toSourceString());
- }
-
- private Stream<String> collectStaticGetTypesWithRetValue(
- ClassSubject clazz, String methodName, String retValue, String... params) {
- assertNotNull(clazz);
- MethodSignature signature = new MethodSignature(methodName, retValue, params);
- Iterator<InstructionSubject> iterator = clazz.method(signature).iterateInstructions();
- return Streams.stream(iterator)
- .filter(InstructionSubject::isStaticGet)
- .map(is -> (FieldAccessInstructionSubject) is)
- .filter(fais -> fais.holder().is(fais.type()))
- .map(fais -> fais.holder().toString());
- }
-
private String getProguardConfig(String main) {
return StringUtils.joinLines(
keepMainProguardConfiguration(main),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTestBase.java
new file mode 100644
index 0000000..71e7aab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTestBase.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2019, 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public abstract class ClassInlinerTestBase extends TestBase {
+
+ protected Set<String> collectTypes(MethodSubject methodSubject) {
+ assertNotNull(methodSubject);
+ assertThat(methodSubject, isPresent());
+ return Stream.concat(
+ collectNewInstanceTypesWithRetValue(methodSubject),
+ collectStaticGetTypesWithRetValue(methodSubject))
+ .collect(Collectors.toSet());
+ }
+
+ private Stream<String> collectNewInstanceTypesWithRetValue(MethodSubject methodSubject) {
+ return methodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isNewInstance)
+ .map(is -> ((NewInstanceInstructionSubject) is).getType().toSourceString());
+ }
+
+ private Stream<String> collectStaticGetTypesWithRetValue(MethodSubject methodSubject) {
+ return methodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isStaticGet)
+ .map(is -> (FieldAccessInstructionSubject) is)
+ .filter(fais -> fais.holder().is(fais.type()))
+ .map(fais -> fais.holder().toString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java
new file mode 100644
index 0000000..1b5d955
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java
@@ -0,0 +1,140 @@
+// Copyright (c) 2019, 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerTupleBuilderConstructorsTest extends ClassInlinerTestBase {
+
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "Tuple1(false, 0, 0, 0, 0, 0, 0.0, 0.0, <null>)",
+ "Tuple1(true, 77, 9977, 35, 42, 987654321123456789, -12.34, 43210.98765, s)");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlinerTupleBuilderConstructorsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlinerTupleBuilderConstructorsTest.class)
+ .addKeepMainRule(TestClass.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testJVM() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+
+ assertEquals(
+ Collections.singleton(StringBuilder.class.getTypeName()), collectTypes(clazz.mainMethod()));
+
+ assertThat(inspector.clazz(Tuple.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Tuple().myToString());
+ System.out.println(
+ new Tuple(
+ true,
+ (byte) 77,
+ (short) 9977,
+ '#',
+ 42,
+ 987654321123456789L,
+ -12.34f,
+ 43210.98765,
+ "s")
+ .myToString());
+ }
+ }
+
+ static class Tuple {
+
+ boolean z;
+ byte b;
+ short s;
+ char c;
+ int i;
+ long l;
+ float f;
+ double d;
+ Object o;
+
+ Tuple() {}
+
+ Tuple(boolean z, byte b, short s, char c, int i, long l, float f, double d, Object o) {
+ this.z = z;
+ this.b = b;
+ this.s = s;
+ this.c = c;
+ this.i = i;
+ this.l = l;
+ this.f = f;
+ this.d = d;
+ this.o = o;
+ }
+
+ String myToString() {
+ return "Tuple1("
+ + z
+ + ", "
+ + b
+ + ", "
+ + s
+ + ", "
+ + ((int) c)
+ + ", "
+ + i
+ + ", "
+ + l
+ + ", "
+ + f
+ + ", "
+ + d
+ + ", "
+ + (o == null ? "<null>" : o)
+ + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
deleted file mode 100644
index 4a40fe9..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
+++ /dev/null
@@ -1,104 +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 com.android.tools.r8.ir.optimize.classinliner.builders;
-
-import com.android.tools.r8.NeverInline;
-
-public class BuildersTestClass {
- private static int ID = 0;
-
- private static int nextInt() {
- return ID++;
- }
-
- private static String next() {
- return Integer.toString(nextInt());
- }
-
- public static void main(String[] args) {
- BuildersTestClass test = new BuildersTestClass();
- test.testSimpleBuilder1();
- test.testSimpleBuilderWithMultipleBuilds();
- test.testBuilderConstructors();
- test.testWithControlFlow();
- test.testWithMoreControlFlow();
- }
-
- @NeverInline
- private void testSimpleBuilder1() {
- System.out.println(
- new PairBuilder<String, String>().setFirst("f-" + next()).build().myToString());
- testSimpleBuilder2();
- testSimpleBuilder3();
- }
-
- @NeverInline
- private void testSimpleBuilder2() {
- System.out.println(
- new PairBuilder<String, String>().setSecond("s-" + next()).build().myToString());
- }
-
- @NeverInline
- private void testSimpleBuilder3() {
- System.out.println(new PairBuilder<String, String>()
- .setFirst("f-" + next()).setSecond("s-" + next()).build().myToString());
- }
-
- @NeverInline
- private void testSimpleBuilderWithMultipleBuilds() {
- PairBuilder<String, String> builder = new PairBuilder<>();
- Pair p1 = builder.build();
- System.out.println(p1.myToString());
- builder.setFirst("f-" + next());
- Pair p2 = builder.build();
- System.out.println(p2.myToString());
- builder.setSecond("s-" + next());
- Pair p3 = builder.build();
- System.out.println(p3.myToString());
- }
-
- @NeverInline
- private void testBuilderConstructors() {
- System.out.println(new Tuple().myToString());
- System.out.println(new Tuple(true, (byte) 77, (short) 9977, '#', 42,
- 987654321123456789L, -12.34f, 43210.98765, "s-" + next() + "-s").myToString());
- }
-
- @NeverInline
- private void testWithControlFlow() {
- ControlFlow flow = new ControlFlow(-1, 2, 7);
- for (int k = 0; k < 25; k++) {
- if (k % 3 == 0) {
- flow.foo(k);
- } else if (k % 3 == 1) {
- flow.bar(nextInt(), nextInt(), nextInt(), nextInt());
- }
- }
- System.out.println("flow = " + flow.toString());
- }
-
- @NeverInline
- private void testWithMoreControlFlow() {
- String str = "1234567890";
- Pos pos = new Pos();
- while (pos.y < str.length()) {
- pos.x = pos.y;
- pos.y = pos.x;
-
- if (str.charAt(pos.x) != '*') {
- if ('0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
- while (pos.y < str.length() && '0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
- pos.y++;
- }
- }
- }
- }
- }
-
- public static class Pos {
- public int x = 0;
- public int y = 0;
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.java
deleted file mode 100644
index 7b95dc1..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.java
+++ /dev/null
@@ -1,54 +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 com.android.tools.r8.ir.optimize.classinliner.builders;
-
-public class ControlFlow {
- int a;
- int b;
- int c = 1234;
- int d;
- String s = ">";
-
- ControlFlow(int b, int c, int d) {
- this.s += this.a++ + ">";
- this.s += this.b + ">";
- this.b = b;
- this.s += this.b + ">";
- this.s += this.c + ">";
- this.c += c;
- this.s += this.c + ">";
- this.s += (this.d = d) + ">";
- }
-
- public void foo(int count) {
- for (int i = 0; i < count; i++) {
- switch (i % 4) {
- case 0:
- this.s += ++this.a + ">";
- break;
- case 1:
- this.c += this.b;
- this.s += this.c + ">";
- break;
- case 2:
- this.d += this.d++ + this.c++ + this.b++ + this.a++;
- this.s += this.d + ">";
- break;
- }
- }
- }
-
- public void bar(int a, int b, int c, int d) {
- this.a += a;
- this.b += b;
- this.c += c;
- this.d += d;
- }
-
- @Override
- public String toString() {
- return s;
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java
deleted file mode 100644
index fcbea05..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java
+++ /dev/null
@@ -1,21 +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 com.android.tools.r8.ir.optimize.classinliner.builders;
-
-public class Pair<F, S> {
- public final F first;
- public final S second;
-
- public Pair(F first, S second) {
- this.first = first;
- this.second = second;
- }
-
- public String myToString() {
- return "Pair(" +
- (first == null ? "<null>" : first) + ", " +
- (second == null ? "<null>" : second) + ")";
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java
deleted file mode 100644
index 0c80c53..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java
+++ /dev/null
@@ -1,29 +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 com.android.tools.r8.ir.optimize.classinliner.builders;
-
-public class PairBuilder<F, S> {
- public F first;
- public S second = null;
-
- public PairBuilder<F, S> setFirst(F first) {
- System.out.println("[before] first = " + this.first);
- this.first = first;
- System.out.println("[after] first = " + this.first);
- return this;
- }
-
- public PairBuilder<F, S> setSecond(S second) {
- System.out.println("[before] second = " + this.second);
- this.second = second;
- System.out.println("[after] second = " + this.second);
- return this;
- }
-
- public Pair<F, S> build() {
- return new Pair<>(first, second);
- }
-}
-
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java
deleted file mode 100644
index 1c0718b..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java
+++ /dev/null
@@ -1,38 +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 com.android.tools.r8.ir.optimize.classinliner.builders;
-
-public class Tuple {
- public boolean z;
- public byte b;
- public short s;
- public char c;
- public int i;
- public long l;
- public float f;
- public double d;
- public Object o;
-
- Tuple() {
- }
-
- Tuple(boolean z, byte b, short s, char c, int i, long l, float f, double d, Object o) {
- this.z = z;
- this.b = b;
- this.s = s;
- this.c = c;
- this.i = i;
- this.l = l;
- this.f = f;
- this.d = d;
- this.o = o;
- }
-
- public String myToString() {
- return "Tuple1(" + z + ", " + b + ", " + s + ", " +
- ((int) c) + ", " + i + ", " + l + ", " + f + ", " +
- d + ", " + (o == null ? "<null>" : o) + ")";
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
deleted file mode 100644
index a3d644f..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright (c) 2019, 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.ir.optimize.membervaluepropagation;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class InstanceFieldValuePropagationTest extends TestBase {
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
- }
-
- public InstanceFieldValuePropagationTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForR8(parameters.getBackend())
- .addInnerClasses(InstanceFieldValuePropagationTest.class)
- .addKeepMainRule(TestClass.class)
- .enableClassInliningAnnotations()
- .enableInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .compile()
- .inspect(this::inspect)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(
- StringUtils.times(StringUtils.lines("A", "42", "Hello world!"), 2));
- }
-
- private void inspect(CodeInspector inspector) {
- ClassSubject testClassSubject = inspector.clazz(TestClass.class);
- assertThat(testClassSubject, isPresent());
-
- // Verify that all instance-get instructions in testDefinitelyNotNull() has been removed by
- // member value propagation.
- MethodSubject testDefinitelyNotNullMethodSubject =
- testClassSubject.uniqueMethodWithName("testDefinitelyNotNull");
- assertThat(testDefinitelyNotNullMethodSubject, isPresent());
- assertTrue(
- testDefinitelyNotNullMethodSubject
- .streamInstructions()
- .noneMatch(InstructionSubject::isInstanceGet));
- // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
- // ends up being unused.
- assertTrue(
- testDefinitelyNotNullMethodSubject
- .streamInstructions()
- .anyMatch(InstructionSubject::isNewInstance));
-
- // Verify that all instance-get instructions in testMaybeNull() has been removed by member value
- // propagation.
- MethodSubject testMaybeNullMethodSubject =
- testClassSubject.uniqueMethodWithName("testMaybeNull");
- assertThat(testMaybeNullMethodSubject, isPresent());
- // TODO(b/125282093): Should synthesize a null-check and still propagate the field values even
- // when the receiver is nullable.
- assertTrue(
- testMaybeNullMethodSubject
- .streamInstructions()
- .anyMatch(InstructionSubject::isInstanceGet));
- // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
- // ends up being unused.
- assertTrue(
- testMaybeNullMethodSubject
- .streamInstructions()
- .anyMatch(InstructionSubject::isNewInstance));
-
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
- // TODO(b/125282093): Need to remove the instance-put instructions in A.<init>(). This can not
- // be done safely by the time we process A.<init>(), so some kind of post-processing is needed.
- assertEquals(3, aClassSubject.allInstanceFields().size());
- }
-
- static class TestClass {
-
- public static void main(String[] args) {
- testDefinitelyNotNull();
- testMaybeNull();
- }
-
- @NeverInline
- static void testDefinitelyNotNull() {
- A a = new A();
- System.out.println(a.e);
- System.out.println(a.i);
- System.out.println(a.s);
- }
-
- @NeverInline
- static void testMaybeNull() {
- A a = System.currentTimeMillis() >= 0 ? new A() : null;
- System.out.println(a.e);
- System.out.println(a.i);
- System.out.println(a.s);
- }
- }
-
- @NeverClassInline
- static class A {
-
- MyEnum e = MyEnum.A;
- int i = 42;
- String s = "Hello world!";
- }
-
- enum MyEnum {
- A,
- B
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
deleted file mode 100644
index d2b739e..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2019, 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.ir.optimize.membervaluepropagation;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class InstanceFieldValuePropagationWithMultipleInstanceInitializersTest extends TestBase {
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
- }
-
- public InstanceFieldValuePropagationWithMultipleInstanceInitializersTest(
- TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForR8(parameters.getBackend())
- .addInnerClasses(InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.class)
- .addKeepMainRule(TestClass.class)
- .setMinApi(parameters.getApiLevel())
- .compile()
- .inspect(this::inspect)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello world!");
- }
-
- private void inspect(CodeInspector inspector) {
- ClassSubject testClassSubject = inspector.clazz(TestClass.class);
- assertThat(testClassSubject, isPresent());
-
- // Verify that the instance-get instruction in main() is still present in main(), since the
- // value of `a.greeting` depends on the constructor being used.
- MethodSubject mainMethodSubject = testClassSubject.mainMethod();
- assertThat(mainMethodSubject, isPresent());
- assertTrue(mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isInstanceGet));
-
- // Verify that the `greeting` field is still present.
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
- assertThat(aClassSubject.uniqueFieldWithName("greeting"), isPresent());
- }
-
- static class TestClass {
-
- public static void main(String[] args) {
- A a = System.currentTimeMillis() >= 0 ? new A() : new A(new Object());
- System.out.println(a.greeting);
- }
- }
-
- @NeverClassInline
- static class A {
-
- String greeting;
-
- A() {
- this.greeting = "Hello world!";
- }
-
- A(Object unused) {
- this.greeting = ":-(";
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
index 1907f33..44dc04c 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
@@ -5,7 +5,7 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.CompilationFailedException;
import com.google.common.collect.ImmutableList;
import org.junit.Rule;
import org.junit.Test;
@@ -71,11 +71,10 @@
String javaResult = runOnJava(builder, clazz.name);
assertEquals(expected, javaResult);
- // TODO(zerny): Should we fail early on the above code? Art fails with a verification error
- // because Test.foo is expected to be in the direct method table.
- if (ToolHelper.artSupported()) {
- thrown.expect(AssertionError.class);
- }
+ thrown.expect(CompilationFailedException.class);
+
+ // TODO(b/110175213): This will fail with a compilation exception since we cannot translate
+ // an invoke-special to a member on the same class.
String artResult = runOnArtD8(builder, clazz.name);
assertEquals(expected, artResult);
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
index e42b66b..c7ba86e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
@@ -57,7 +57,6 @@
ProcessResult processResult =
ToolHelper.runKotlinc(
null,
- null,
supertypeLibJar,
null,
getKotlinFileInTest(supertypeLibFolder, "impl"),
@@ -70,7 +69,6 @@
processResult =
ToolHelper.runKotlinc(
null,
- null,
extLibJar,
null,
getKotlinFileInTest(extLibFolder, "B")
@@ -102,26 +100,26 @@
// API entry is kept, hence the presence of Metadata.
DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
assertNotNull(metadata);
- // TODO(b/143687784): test its metadata doesn't point to shrunken itf.
+ assertThat(metadata.toString(), not(containsString("internal")));
+ assertThat(metadata.toString(), not(containsString("Itf")));
});
Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
compileResult.writeToZip(r8ProcessedLibZip);
String appFolder = PKG_PREFIX + "/supertype_app";
- ProcessResult kotlinTestCompileResult =
+ Path output =
kotlinc(parameters.getRuntime().asCf())
.addClasspathFiles(r8ProcessedLibZip)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
- // TODO(b/143687784): update to just .compile() once fixed.
.setOutputPath(temp.newFolder().toPath())
- .compileRaw();
+ .compile();
- // TODO(b/143687784): should be able to compile!
- assertNotEquals(0, kotlinTestCompileResult.exitCode);
- assertThat(
- kotlinTestCompileResult.stderr,
- containsString("unresolved supertypes: " + pkg + ".supertype_lib.internal.Itf"));
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), pkg + ".supertype_app.MainKt")
+ .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
}
@Test
@@ -152,7 +150,7 @@
// API entry is kept, hence the presence of Metadata.
DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
assertNotNull(metadata);
- // TODO(b/143687784): test its metadata doesn't point to shrunken Super.
+ assertThat(metadata.toString(), not(containsString("Super")));
});
Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
@@ -168,9 +166,6 @@
.compileRaw();
// TODO(b/143687784): should be able to compile!
assertNotEquals(0, kotlinTestCompileResult.exitCode);
- assertThat(
- kotlinTestCompileResult.stderr,
- containsString("unresolved supertypes: " + pkg + ".extension_lib.Super"));
assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java b/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
index d028564..59b273a 100644
--- a/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
+++ b/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
@@ -7,6 +7,7 @@
import static junit.framework.TestCase.assertEquals;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -31,6 +32,7 @@
}
}
+ @NeverClassInline
public static class B extends A {
public int f0;
@@ -48,6 +50,7 @@
}
}
+ @NeverClassInline
public static class C extends A {
public int f0;
@@ -77,7 +80,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public FieldNamingObfuscationDictionaryTest(TestParameters parameters) {
@@ -94,7 +97,8 @@
.addInnerClasses(FieldNamingObfuscationDictionaryTest.class)
.addKeepRules("-overloadaggressively", "-obfuscationdictionary " + dictionary.toString())
.addKeepMainRule(Runner.class)
- .setMinApi(parameters.getRuntime())
+ .enableClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Runner.class, "HELLO", "WORLD")
.assertSuccessWithOutputLines("2HELLO WORLD", "2HELLO WORLD")
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
index b2ed534..ebd7420 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
@@ -26,7 +26,6 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.BeforeParam;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
@@ -44,11 +43,6 @@
@ClassRule public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
- @BeforeParam
- public static void forceCompilation(TestParameters parameters) {
- compilation.apply(parameters.getBackend());
- }
-
private static R8TestCompileResult compile(Backend backend)
throws com.android.tools.r8.CompilationFailedException, IOException, ExecutionException {
R8TestCompileResult compileResult =
@@ -86,7 +80,7 @@
@Test
public void test() throws Throwable {
- compile(parameters.getBackend())
+ compilation.apply(parameters.getBackend())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
index 98780e9..7fa0543 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
@@ -31,7 +31,6 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.BeforeParam;
@RunWith(Parameterized.class)
public class ApplyMappingAfterVerticalMergingMethodTest extends TestBase {
@@ -100,11 +99,6 @@
@ClassRule
public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
- @BeforeParam
- public static void forceCompilation(TestParameters parameters) {
- compilationResults.apply(parameters.getBackend());
- }
-
public static CompilationResult compile(Backend backend)
throws ExecutionException, CompilationFailedException, IOException {
R8TestCompileResult library = compileLibrary(backend);
diff --git a/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
index 42a64b5..d3604b6 100644
--- a/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
+++ b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
@@ -8,20 +8,38 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.code.MoveWide;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Arrays;
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 Regress142682636Runner extends TestBase {
- private final Class<?> testClass = Regress142682636.class;
+ private static final Class<?> testClass = Regress142682636.class;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public Regress142682636Runner(TestParameters parameters) {
+ this.parameters = parameters;
+ }
@Test
public void test() throws Exception {
CodeInspector inspector = testForD8()
.addProgramClasses(testClass)
+ .setMinApi(parameters.getApiLevel())
.release()
.compile()
.inspector();
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
index 4dff5f4..c9d94ad 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
@@ -4,24 +4,19 @@
package com.android.tools.r8.resolution;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
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.Version;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.references.Reference;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
@RunWith(Parameterized.class)
public class InvokeInterfaceOnClassTest extends TestBase {
@@ -39,54 +34,49 @@
@Test
public void testReference() throws Exception {
- testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ testForRuntime(parameters)
.addProgramClasses(I.class, C1.class, C2.class)
- .addProgramClassFileData(DumpMain.dump())
+ .addProgramClassFileData(transformMain())
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatMatches(getExpectedFailureMatcher(false));
}
@Test
public void testR8() throws Exception {
- try {
- testForR8(parameters.getBackend())
- .addProgramClasses(I.class, C1.class, C2.class)
- .addProgramClassFileData(DumpMain.dump())
- .addKeepMainRule(Main.class)
- .setMinApi(parameters.getApiLevel())
- .compile()
- .run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(true));
- } catch (CompilationFailedException e) {
- // TODO(b/144085169): The class file pipeline throws an assertion error, but should not.
- assertTrue(parameters.isCfRuntime());
- }
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, C1.class, C2.class)
+ .addProgramClassFileData(transformMain())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.testing.allowInvokeErrors = true)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(true));
}
private Matcher<String> getExpectedFailureMatcher(boolean isR8) {
- if (parameters.getRuntime().isDex()
- && parameters
- .getRuntime()
- .asDex()
- .getVm()
- .getVersion()
- .isOlderThanOrEqual(Version.V4_4_4)) {
+ // Old runtimes are implemented to throw the wrong error, so NoSuchMethodError is expected.
+ if (isDexVmOlderThanOrEqualTo(Version.V4_4_4)) {
return containsString("NoSuchMethodError");
}
- if (isR8
- && parameters.getRuntime().isDex()
- && parameters
- .getRuntime()
- .asDex()
- .getVm()
- .getVersion()
- .isOlderThanOrEqual(Version.V6_0_1)) {
- // TODO(b/144085169): R8 ends up causing a code change changing the error on these runtimes.
+ // For 5 and 6, the error is correct, but only as long as the class has a non-abstract method.
+ // R8 will not trace the C1.f and C2.f as the resolution of I.f fails. The implementation
+ // methods are removed and this again causes the runtime to throw the wrong error.
+ if (isR8 && isDexVmOlderThanOrEqualTo(Version.V6_0_1)) {
return containsString("NoSuchMethodError");
}
return containsString("IncompatibleClassChangeError");
}
+ private boolean isDexVmOlderThanOrEqualTo(Version version) {
+ return parameters.getRuntime().isDex()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .getVm()
+ .getVersion()
+ .isOlderThanOrEqual(version);
+ }
+
public abstract static class I {
public abstract void f();
}
@@ -115,91 +105,19 @@
}
}
- static class DumpMain implements Opcodes {
-
- public static void main(String[] args) throws Exception {
- ASMifier.main(
- new String[] {"-debug", ToolHelper.getClassFileForTestClass(Main.class).toString()});
- }
-
- public static byte[] dump() {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_SUPER,
- DescriptorUtils.getBinaryNameFromJavaType(Main.class.getName()),
- null,
- "java/lang/Object",
- null);
-
- {
- methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- {
- methodVisitor =
- classWriter.visitMethod(
- ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitInsn(ARRAYLENGTH);
- methodVisitor.visitInsn(ICONST_2);
- methodVisitor.visitInsn(IREM);
- Label label0 = new Label();
- methodVisitor.visitJumpInsn(IFNE, label0);
- methodVisitor.visitTypeInsn(
- NEW, "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$C1");
- methodVisitor.visitInsn(DUP);
- methodVisitor.visitMethodInsn(
- INVOKESPECIAL,
- "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$C1",
- "<init>",
- "()V",
- false);
- Label label1 = new Label();
- methodVisitor.visitJumpInsn(GOTO, label1);
- methodVisitor.visitLabel(label0);
- methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
- methodVisitor.visitTypeInsn(
- NEW, "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$C2");
- methodVisitor.visitInsn(DUP);
- methodVisitor.visitMethodInsn(
- INVOKESPECIAL,
- "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$C2",
- "<init>",
- "()V",
- false);
- methodVisitor.visitLabel(label1);
- methodVisitor.visitFrame(
- Opcodes.F_SAME1,
- 0,
- null,
- 1,
- new Object[] {"com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$I"});
- methodVisitor.visitVarInsn(ASTORE, 1);
- methodVisitor.visitVarInsn(ALOAD, 1);
- // Changed INVOKEVIRTUAL & false => INVOKEINTERFACE & true.
- methodVisitor.visitMethodInsn(
- INVOKEINTERFACE,
- "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$I",
- "f",
- "()V",
- true);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(2, 2);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
+ private static byte[] transformMain() throws Exception {
+ String binaryNameForI = Reference.classFromClass(I.class).getBinaryName();
+ return transformer(Main.class)
+ .transformMethodInsnInMethod("main",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (owner.equals(binaryNameForI) && name.equals("f")) {
+ assertEquals(Opcodes.INVOKEVIRTUAL, opcode);
+ assertFalse(isInterface);
+ continuation.apply(Opcodes.INVOKEINTERFACE, owner, name, descriptor, true);
+ } else {
+ continuation.apply(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
index 6c2effe..d0a4906 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
@@ -9,7 +9,6 @@
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -36,7 +35,7 @@
@Test
public void testReference() throws Exception {
- testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+ testForRuntime(parameters)
.addProgramClasses(I.class, C1.class, C2.class)
.addProgramClassFileData(transformMain())
.run(parameters.getRuntime(), Main.class)
@@ -45,45 +44,40 @@
@Test
public void testR8() throws Exception {
- try {
- testForR8(parameters.getBackend())
- .addProgramClasses(I.class, C1.class, C2.class)
- .addProgramClassFileData(transformMain())
- .addKeepMainRule(Main.class)
- .setMinApi(parameters.getApiLevel())
- .compile()
- .run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(true));
- } catch (CompilationFailedException e) {
- // TODO(b/144085169): The class file pipeline throws an assertion error, but should not.
- assertTrue(parameters.isCfRuntime());
- }
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, C1.class, C2.class)
+ .addProgramClassFileData(transformMain())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.testing.allowInvokeErrors = true)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(true));
}
private Matcher<String> getExpectedFailureMatcher(boolean isR8) {
- if (parameters.getRuntime().isDex()
- && parameters
- .getRuntime()
- .asDex()
- .getVm()
- .getVersion()
- .isOlderThanOrEqual(Version.V4_4_4)) {
+ // Old runtimes fail verification outright.
+ if (isDexVmOlderThanOrEqualTo(Version.V4_4_4)) {
return containsString("VerifyError");
}
- if (isR8
- && parameters.getRuntime().isDex()
- && parameters
- .getRuntime()
- .asDex()
- .getVm()
- .getVersion()
- .isOlderThanOrEqual(Version.V7_0_0)) {
- // TODO(b/144085169): R8 ends up causing a code change changing the error on these runtimes.
+ // For 5, 6 and 7, the error is correct, but only if the class has a non-abstract method.
+ // R8 will not trace the C1.f and C2.f as the resolution of I.f fails. The implementation
+ // methods are removed and this again causes the runtime to throw the wrong error.
+ if (isR8 && isDexVmOlderThanOrEqualTo(Version.V7_0_0)) {
return containsString("NoSuchMethodError");
}
return containsString("IncompatibleClassChangeError");
}
+ private boolean isDexVmOlderThanOrEqualTo(Version version) {
+ return parameters.getRuntime().isDex()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .getVm()
+ .getVersion()
+ .isOlderThanOrEqual(version);
+ }
+
public interface I {
void f();
}
@@ -121,9 +115,9 @@
if (owner.equals(binaryNameForI) && name.equals("f")) {
assertEquals(INVOKEINTERFACE, opcode);
assertTrue(isInterface);
- continuation.visitMethodInsn(INVOKEVIRTUAL, owner, name, descriptor, false);
+ continuation.apply(INVOKEVIRTUAL, owner, name, descriptor, false);
} else {
- continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ continuation.apply(opcode, owner, name, descriptor, isInterface);
}
})
.transform();
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index ef0e967..bae2760 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -101,7 +101,6 @@
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- // TODO(b/144085169) R8 masks ICCE.
- .assertSuccessWithOutput(EXPECTED.replace("ICCE", "InterfaceWithDefault"));
+ .assertSuccessWithOutput(EXPECTED);
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index a2df4aa..ec01f62 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -268,7 +268,7 @@
public void lookupSingleTarget() {
DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
Assert.assertNotNull(
- appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve());
+ appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder);
if (singleTargetHolderOrNull == null) {
Assert.assertNull(singleVirtualTarget);
@@ -283,7 +283,7 @@
public void lookupVirtualTargets() {
DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
Assert.assertNotNull(
- appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve());
+ appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
if (resolutionResult.isValidVirtualTarget(appInfo.app().options)) {
Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 6e87ef3..f3191ac 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -133,7 +133,7 @@
@Test
public void lookupSingleTarget() {
DexEncodedMethod resolved =
- appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB).asResultOfResolve();
+ appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB).getSingleTarget();
assertEquals(methodOnA, resolved.method);
DexEncodedMethod singleVirtualTarget =
appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index 968f21e..e876ce0 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -178,7 +178,7 @@
@Test
public void lookupVirtualTargets() {
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
- DexEncodedMethod resolved = resolutionResult.asResultOfResolve();
+ DexEncodedMethod resolved = resolutionResult.getSingleTarget();
assertEquals(methodOnA, resolved.method);
assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index f5a99fa..30295fd 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Collections;
@@ -65,7 +64,7 @@
.addProgramClasses(CLASSES)
.addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
+ .assertFailureWithErrorThatMatches(getExpectedErrorMatcher());
}
@Test
@@ -75,16 +74,13 @@
.addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> options.testing.allowNonAbstractClassesWithAbstractMethods = true)
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(true));
+ .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
}
- private Matcher<String> getExpectedErrorMatcher(boolean isR8) {
- if (isR8
- && (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L))) {
- // TODO(b/144085169): R8 replaces the entire main method by 'throw null', why?
- return containsString("NullPointerException");
- }
+ private Matcher<String> getExpectedErrorMatcher() {
if (parameters.isDexRuntime()
&& parameters
.getRuntime()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index bd2d4ac..6c4070e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Collections;
@@ -65,7 +64,7 @@
.addProgramClasses(CLASSES)
.addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
+ .assertFailureWithErrorThatMatches(getExpectedErrorMatcher());
}
@Test
@@ -75,16 +74,13 @@
.addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> options.testing.allowNonAbstractClassesWithAbstractMethods = true)
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(true));
+ .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
}
- private Matcher<String> getExpectedErrorMatcher(boolean isR8) {
- if (isR8
- && (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L))) {
- // TODO(b/144085169): R8 replaces the entire main method by 'throw null', why?
- return containsString("NullPointerException");
- }
+ private Matcher<String> getExpectedErrorMatcher() {
if (parameters.isDexRuntime()
&& parameters
.getRuntime()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index af19bf1..581554b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -5,7 +5,6 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -58,7 +57,6 @@
resolutionResult
.asFailedResolution()
.forEachFailureDependency(
- clazz -> fail("Unexpected class dependency"),
target -> holders.add(target.method.holder.toSourceString()));
assertEquals(ImmutableSet.of(L.class.getTypeName(), R.class.getTypeName()), holders);
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index 13de4d4..47d7d65 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -5,13 +5,11 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
@@ -60,7 +58,6 @@
resolutionResult
.asFailedResolution()
.forEachFailureDependency(
- clazz -> fail("Unexpected class dependency"),
m -> holders.add(m.method.holder.toSourceString()));
assertEquals(ImmutableSet.of(I.class.getTypeName(), J.class.getTypeName()), holders);
}
@@ -82,16 +79,7 @@
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .apply(r -> checkResultR8(r));
- }
-
- private void checkResultR8(TestRunResult<?> runResult) {
- // TODO(b/144085169): R8/CF produces incorrect result.
- if (parameters.getRuntime().isCf()) {
- runResult.assertFailureWithErrorThatMatches(containsString("NullPointerException"));
- } else {
- runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
- }
+ .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
}
public interface I {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index ab80fe5..298134a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -32,7 +32,6 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.BeforeParam;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
@@ -179,11 +178,6 @@
@ClassRule public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
- @BeforeParam
- public static void forceCompilation(TestParameters parameters) {
- compilationResults.apply(parameters.getBackend());
- }
-
private static Function<Backend, CompilationResults> compilationResults =
memoizeFunction(RemoveAssertionsTest::compileAll);
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 9da1f4a..5d110e4 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -82,7 +82,6 @@
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
- assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), 1);
} else {
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("simple"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -90,12 +89,12 @@
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inSwitch"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
- assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("phi"));
+ assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
@Test
@@ -126,14 +125,12 @@
assertNameReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), expectedConst);
assertNameReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
assertNameReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
- assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
} else {
assertNameWasNotReplaced(clazz.uniqueMethodWithName("simple"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("local"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
- assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
// TODO(jakew) this should be allowed!
@@ -141,6 +138,7 @@
assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("phi"));
+ assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
@Test
@@ -180,7 +178,6 @@
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), "TWO");
assertToStringReplacedWithConst(
clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
- assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
} else {
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("noToString"));
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -188,11 +185,11 @@
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
- assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("phi"));
+ assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
private static void assertOrdinalReplacedWithConst(MethodSubject method, int expectedConst) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
index 8f09a80..2ecc9eb 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
@@ -77,10 +77,12 @@
return number.name();
}
+ @AssumeMayHaveSideEffects
@NeverInline
private static String nonStaticGet() {
return new Names().two.name();
}
+
private final Number two = Number.TWO;
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
index 3617c8b..8055344 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
@@ -115,10 +115,12 @@
return number.toString();
}
+ @AssumeMayHaveSideEffects
@NeverInline
private static String nonStaticGet() {
return new ToStrings().two.toString();
}
+
private final NoToString two = NoToString.TWO;
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
index 92d16ae..ea96ead 100644
--- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
@@ -56,50 +56,43 @@
.enableMergeAnnotations()
.setMinApi(parameters.getRuntime())
.compile()
- .inspect(
- codeInspector -> {
- ClassSubject main = codeInspector.clazz(MAIN);
- assertThat(main, isPresent());
+ .inspect(codeInspector -> {
+ ClassSubject main = codeInspector.clazz(MAIN);
+ assertThat(main, isPresent());
- MethodSubject mainMethod = main.mainMethod();
- assertThat(mainMethod, isPresent());
+ MethodSubject mainMethod = main.mainMethod();
+ assertThat(mainMethod, isPresent());
- assertTrue(
- mainMethod
- .streamInstructions()
- .noneMatch(i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
- assertTrue(
- mainMethod
- .streamInstructions()
- .noneMatch(i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
- // TODO(b/138913138): not trivial; assigned only once in <init>
- assertFalse(
- mainMethod
- .streamInstructions()
- .noneMatch(i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
- assertTrue(
- mainMethod
- .streamInstructions()
- .noneMatch(i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
- assertTrue(
- mainMethod
- .streamInstructions()
- .noneMatch(i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
- // TODO(b/138913138): not trivial; assigned multiple times, but within a certain
- // range.
- assertFalse(
- mainMethod
- .streamInstructions()
- .noneMatch(i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
- assertTrue(
- mainMethod
- .streamInstructions()
- .noneMatch(i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
- assertTrue(
- mainMethod
- .streamInstructions()
- .noneMatch(i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
- })
+ assertTrue(
+ mainMethod.streamInstructions().noneMatch(
+ i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
+ // TODO(b/138913138): effectively final, and default value is set.
+ assertFalse(
+ mainMethod.streamInstructions().noneMatch(
+ i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
+ // TODO(b/138913138): not trivial; assigned only once in <init>
+ assertFalse(
+ mainMethod.streamInstructions().noneMatch(
+ i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
+ assertTrue(
+ mainMethod.streamInstructions().noneMatch(
+ i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
+ // TODO(b/138913138): effectively final, and default value is set.
+ assertFalse(
+ mainMethod.streamInstructions().noneMatch(
+ i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
+ // TODO(b/138913138): not trivial; assigned multiple times, but within a certain range.
+ assertFalse(
+ mainMethod.streamInstructions().noneMatch(
+ i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
+ assertTrue(
+ mainMethod.streamInstructions().noneMatch(
+ i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
+ // TODO(b/138913138): effectively final, and default value is set.
+ assertFalse(
+ mainMethod.streamInstructions().noneMatch(
+ i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
+ })
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("The end");
}
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 287b736..5220c01 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -35,7 +35,6 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.BeforeParam;
@RunWith(Parameterized.class)
public class NonVirtualOverrideTest extends TestBase {
@@ -91,14 +90,6 @@
@ClassRule public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
- @BeforeParam
- public static void forceCompilation(
- TestParameters parameters, boolean enableClassInlining, boolean enableVerticalClassMerging) {
- expectedResults.apply(isDexVmBetween5_1_1and7_0_0(parameters));
- compilationResults.apply(
- new Dimensions(parameters.getBackend(), enableClassInlining, enableVerticalClassMerging));
- }
-
private static Function<Boolean, String> expectedResults =
memoizeFunction(NonVirtualOverrideTest::getExpectedResult);
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
index b17fae6..4ea48cb 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
@@ -9,6 +9,8 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
@@ -16,14 +18,29 @@
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
class PrintConfigurationTestClass {
public static void main(String[] args) {}
}
+@RunWith(Parameterized.class)
public class PrintConfigurationTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ public PrintConfigurationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void testSingleConfigurationWithAbsolutePath() throws Exception {
Path printConfigurationFile = temp.newFile().toPath();
@@ -50,7 +67,7 @@
"-printconfiguration proguard-config-out.txt");
FileUtils.writeTextFile(proguardConfigFile, proguardConfig.trim());
- testForExternalR8(Backend.DEX)
+ testForExternalR8(Backend.DEX, parameters.getRuntime())
.addProgramClasses(PrintConfigurationTestClass.class)
.addKeepRuleFiles(proguardConfigFile)
.compile();
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
index 7c36a88..8c782f2 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
@@ -166,7 +166,8 @@
CodeInspector inspector,
Set<String> expected,
boolean interfaceMethodsKept,
- Shrinker shrinker) {
+ Shrinker shrinker,
+ Set<String> subSubImplExpected) {
ClassSubject superIfaceSubject = inspector.clazz(SuperIface.class);
assertThat(superIfaceSubject, isPresent());
if (interfaceMethodsKept) {
@@ -210,48 +211,71 @@
assertEquals(expected.contains("m5"), subImplSubject.uniqueMethodWithName("m5").isPresent());
ClassSubject subSubImplSubject = inspector.clazz(SubSubImpl.class);
assertThat(subSubImplSubject, isPresent());
- assertThat(subSubImplSubject.uniqueMethodWithName("m1"), not(isPresent()));
- assertThat(subSubImplSubject.uniqueMethodWithName("m2"), not(isPresent()));
- assertThat(subSubImplSubject.uniqueMethodWithName("m3"), not(isPresent()));
- assertThat(subSubImplSubject.uniqueMethodWithName("m4"), not(isPresent()));
- assertThat(subSubImplSubject.uniqueMethodWithName("m5"), not(isPresent()));
- assertEquals(expected.contains("m6"), subSubImplSubject.uniqueMethodWithName("m6").isPresent());
+ // FOO
+ assertEquals(
+ subSubImplExpected.contains("m1"),
+ subSubImplSubject.uniqueMethodWithName("m1").isPresent());
+ assertEquals(
+ subSubImplExpected.contains("m2"),
+ subSubImplSubject.uniqueMethodWithName("m2").isPresent());
+ assertEquals(
+ subSubImplExpected.contains("m3"),
+ subSubImplSubject.uniqueMethodWithName("m3").isPresent());
+ assertEquals(
+ subSubImplExpected.contains("m4"),
+ subSubImplSubject.uniqueMethodWithName("m4").isPresent());
+ assertEquals(
+ subSubImplExpected.contains("m5"),
+ subSubImplSubject.uniqueMethodWithName("m5").isPresent());
+ assertEquals(
+ subSubImplExpected.contains("m6"),
+ subSubImplSubject.uniqueMethodWithName("m6").isPresent());
}
private void checkAllMethodsInterfacesKept(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m1", "m2", "m3", "m4", "m5", "m6"), true, shrinker);
+ checkMethods(
+ inspector,
+ ImmutableSet.of("m1", "m2", "m3", "m4", "m5"),
+ true,
+ shrinker,
+ ImmutableSet.of("m6"));
}
- private void checkAllMethodsInterfacesNotKept(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m1", "m2", "m3", "m4", "m5", "m6"), false, shrinker);
+ private void checkAllMethodsInterfacesAreKeptOnClass(CodeInspector inspector, Shrinker shrinker) {
+ checkMethods(
+ inspector,
+ ImmutableSet.of("m1", "m2", "m3", "m4", "m5"),
+ false,
+ shrinker,
+ ImmutableSet.of("m1", "m2", "m3", "m6"));
}
private void checkOnlyM1(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m1"), true, shrinker);
+ checkMethods(inspector, ImmutableSet.of("m1"), true, shrinker, ImmutableSet.of());
}
private void checkOnlyM1InterfacesNotKept(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m1"), false, shrinker);
+ checkMethods(inspector, ImmutableSet.of("m1"), false, shrinker, ImmutableSet.of("m1"));
}
private void checkOnlyM2(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m2"), true, shrinker);
+ checkMethods(inspector, ImmutableSet.of("m2"), true, shrinker, ImmutableSet.of());
}
private void checkOnlyM3(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m3"), true, shrinker);
+ checkMethods(inspector, ImmutableSet.of("m3"), true, shrinker, ImmutableSet.of());
}
private void checkOnlyM4(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m4"), true, shrinker);
+ checkMethods(inspector, ImmutableSet.of("m4"), true, shrinker, ImmutableSet.of());
}
private void checkOnlyM5(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m5"), true, shrinker);
+ checkMethods(inspector, ImmutableSet.of("m5"), true, shrinker, ImmutableSet.of());
}
private void checkOnlyM6(CodeInspector inspector, Shrinker shrinker) {
- checkMethods(inspector, ImmutableSet.of("m6"), true, shrinker);
+ checkMethods(inspector, ImmutableSet.of(), true, shrinker, ImmutableSet.of("m6"));
}
public String allMethodsOutput() {
@@ -290,9 +314,9 @@
"SubImpl.m3 not found",
"SubImpl.m4 not found",
"SubImpl.m5 found",
- "SubSubImpl.m1 not found",
- "SubSubImpl.m2 not found",
- "SubSubImpl.m3 not found",
+ "SubSubImpl.m1 found",
+ "SubSubImpl.m2 found",
+ "SubSubImpl.m3 found",
"SubSubImpl.m4 not found",
"SubSubImpl.m5 not found",
"SubSubImpl.m6 found");
@@ -334,7 +358,7 @@
"SubImpl.m3 not found",
"SubImpl.m4 not found",
"SubImpl.m5 not found",
- "SubSubImpl.m1 not found",
+ "SubSubImpl.m1 found",
"SubSubImpl.m2 not found",
"SubSubImpl.m3 not found",
"SubSubImpl.m4 not found",
@@ -466,7 +490,7 @@
// interfaces with the default methods are not explicitly kept?
runTest(
"-keep class **.SubSubImpl { *; }",
- this::checkAllMethodsInterfacesNotKept,
+ this::checkAllMethodsInterfacesAreKeptOnClass,
allMethodsOutputInterfacesNotKept());
}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index ecfc895..62ec020 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -10,10 +10,14 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersBuilder;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
@@ -47,15 +51,20 @@
@Test
public void testStaticMethod() throws Exception {
- test(FooStaticMethod.class, TestStaticMethod.class);
+ test(FooStaticMethod.class, TestStaticMethod.class, null);
}
@Test
public void testStaticField() throws Exception {
- test(FooStaticField.class, TestStaticField.class);
+ test(
+ FooStaticField.class,
+ TestStaticField.class,
+ builder -> builder.enableInliningAnnotations().enableMemberValuePropagationAnnotations());
}
- private void test(Class<?> fooClass, Class<?> testClass) throws Exception {
+ private void test(
+ Class<?> fooClass, Class<?> testClass, ThrowableConsumer<R8FullTestBuilder> configuration)
+ throws Exception {
WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
GraphInspector inspector =
testForR8(parameters.getBackend())
@@ -63,6 +72,7 @@
.enableGraphInspector(whyAreYouKeepingConsumer)
.addProgramClasses(testClass, fooClass)
.addKeepMainRule(testClass)
+ .apply(configuration)
.run(parameters.getRuntime(), testClass)
.assertSuccessWithOutput(EXPECTED)
.graphInspector();
@@ -137,6 +147,10 @@
private FooStaticField() {}
+ // Ensure that toString() remains in TestStaticField.main(). Otherwise the expression
+ // `new TestStaticField().foo.toString()` can be optimized into "Foo!".
+ @NeverInline
+ @NeverPropagateValue
@Override
public String toString() {
return "Foo!";
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java
index 442fda4..2961a50 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java
@@ -10,11 +10,11 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
@@ -50,26 +50,29 @@
.addKeepMethodRules(J.class, "void foo()")
.addKeepRules("-dontwarn")
.compile()
- .inspect(AbstractInterfaceMethodsTest::inspectBaseInterfaceRemove);
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(J.class);
+ assertThat(clazz, isPresent());
+ assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
+ assertThat(inspector.clazz(I.class), not(isPresent()));
+ });
}
@Test
public void testSingleInheritanceR8()
throws CompilationFailedException, IOException, ExecutionException {
- // TODO(b/143590191): Fix expectation when resolved.
- testForR8(parameters.getBackend())
- .addProgramClasses(I.class, J.class)
- .setMinApi(parameters.getApiLevel())
- .addKeepMethodRules(J.class, "void foo()")
- .compile()
- .inspect(AbstractInterfaceMethodsTest::inspectBaseInterfaceRemove);
- }
-
- private static void inspectBaseInterfaceRemove(CodeInspector inspector) {
- ClassSubject clazz = inspector.clazz(J.class);
- assertThat(clazz, isPresent());
- assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
- assertThat(inspector.clazz(I.class), not(isPresent()));
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(J.class, "void foo()")
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
}
public interface I {
@@ -77,4 +80,19 @@
}
public interface J extends I {}
+
+ public static class A implements J {
+
+ @Override
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ((J) new A()).foo();
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
index c6feb05..f75f7bd 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
@@ -11,11 +11,12 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
@@ -51,50 +52,92 @@
.addKeepMethodRules(J.class, "void foo()")
.addKeepRules("-dontwarn")
.compile()
- .inspect(DefaultInterfaceMethodsTest::inspectBaseInterfaceRemove);
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(J.class);
+ assertThat(clazz, isPresent());
+ assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
+ assertThat(inspector.clazz(I.class), not(isPresent()));
+ });
}
@Test
- public void testSingleInheritanceR8()
+ public void testSingleInheritanceR8BeforeNougat()
throws CompilationFailedException, IOException, ExecutionException {
+ assumeTrue(parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(J.class, "void foo()")
+ .addOptionsModification(
+ internalOptions -> internalOptions.enableVerticalClassMerging = false)
+ .noMinification()
+ .compile();
+ // TODO(b/144269679): We should be able to compile and run this.
testForR8(parameters.getBackend())
- .addProgramClasses(I.class, J.class)
+ .addProgramClasses(ImplJ.class, Main.class)
+ .addClasspathClasses(I.class, J.class)
.setMinApi(parameters.getApiLevel())
- .addKeepMethodRules(J.class, "void foo()")
- .compile()
- .inspect(DefaultInterfaceMethodsTest::inspectBaseInterfaceRemove);
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(ImplJ.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class, ImplJ.class.getTypeName(), "foo")
+ .assertFailureWithErrorThatMatches(
+ containsString(
+ "com.android.tools.r8.shaking.methods.interfaces.DefaultInterfaceMethodsTest$I$-CC"));
}
- private static void inspectBaseInterfaceRemove(CodeInspector inspector) {
- ClassSubject clazz = inspector.clazz(J.class);
- assertThat(clazz, isPresent());
- assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
- assertThat(inspector.clazz(I.class), not(isPresent()));
+ @Test
+ public void testSingleInheritanceR8OnNougatAndForward()
+ throws CompilationFailedException, IOException, ExecutionException {
+ assumeTrue(
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(J.class, "void foo()")
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(ImplJ.class, Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class, ImplJ.class.getTypeName(), "foo")
+ .assertSuccessWithOutputLines("Hello World!");
}
@Test
public void testKeepInterfaceMethodOnSubInterface()
throws CompilationFailedException, IOException, ExecutionException {
- testForR8(parameters.getBackend())
- .addProgramClasses(Main.class, I.class, J.class, B.class)
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .addKeepClassAndMembersRules(B.class)
- .addKeepMethodRules(J.class, "void foo()")
- .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
- .assertFailureWithErrorThatMatches(containsString("NoSuchMethodException"));
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class, ImplJ.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(ImplJ.class)
+ .addKeepMethodRules(J.class, "void foo()")
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class, ImplJ.class.getTypeName(), "foo")
+ .assertSuccessWithOutputLines("Hello World!");
}
@Test
public void testKeepInterfaceMethodOnImplementingType()
throws CompilationFailedException, IOException, ExecutionException {
- testForR8(parameters.getBackend())
- .addProgramClasses(Main.class, I.class, A.class)
- .setMinApi(parameters.getApiLevel())
- .addKeepClassAndMembersRules(Main.class)
- .addKeepMethodRules(A.class, "void <init>()", "void foo()")
- .run(parameters.getRuntime(), Main.class, A.class.getTypeName(), "foo")
- .assertFailureWithErrorThatMatches(containsString("NoSuchMethodException"));
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class, ImplJ.class, SubImplJ.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(SubImplJ.class, "void <init>()", "void foo()")
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class, SubImplJ.class.getTypeName(), "foo")
+ .assertSuccessWithOutputLines("Hello World!");
}
public interface I {
@@ -106,9 +149,9 @@
public interface J extends I {}
- public static class A implements I {}
+ public static class ImplJ implements J {}
- public static class B implements J {}
+ public static class SubImplJ extends ImplJ {}
public static class Main {
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java
new file mode 100644
index 0000000..5171c66
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 ImplementingMethodInSubclassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ImplementingMethodInSubclassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testKeepingIFooAndNotA()
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8TestCompileResult libraryResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class, B.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(A.class, "void foo()")
+ .addKeepClassRules(B.class)
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(libraryResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World");
+ }
+
+ public interface I {
+ void foo();
+ }
+
+ public abstract static class A implements I {}
+
+ public static class B extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("Hello World");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ((A) new B()).foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/IndirectSuperInterfaceDefaultMethodTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/IndirectSuperInterfaceDefaultMethodTest.java
new file mode 100644
index 0000000..73398e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/IndirectSuperInterfaceDefaultMethodTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 IndirectSuperInterfaceDefaultMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ public IndirectSuperInterfaceDefaultMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testInvokeSpecial()
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class, K.class)
+ .addKeepClassRules(J.class)
+ .addKeepClassAndMembersRules(I.class)
+ .addKeepMethodRules(K.class, "void foo()")
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ public interface I {
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public interface J extends I {}
+
+ public interface K extends J {}
+
+ public static class A implements K {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ((K) new A()).foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceAbstractTriangleTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceAbstractTriangleTest.java
new file mode 100644
index 0000000..cf15643
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceAbstractTriangleTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InterfaceAbstractTriangleTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceAbstractTriangleTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void shouldKeepA() throws CompilationFailedException, IOException, ExecutionException {
+ R8TestCompileResult libraryResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class, A.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(A.class, "void foo()", "void <init>()")
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(B.class, Main.class)
+ .addRunClasspathFiles(libraryResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ public interface I {
+
+ void foo();
+ }
+
+ public interface J {
+
+ void foo();
+ }
+
+ public abstract static class A implements I, J {}
+
+ public static class B extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ((A) new B()).foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceDefaultMethodKeptTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceDefaultMethodKeptTest.java
new file mode 100644
index 0000000..b7b517b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceDefaultMethodKeptTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InterfaceDefaultMethodKeptTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+ .build();
+ }
+
+ public InterfaceDefaultMethodKeptTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testKeepingAFooAndIFoo()
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(I.class)
+ .addKeepMethodRules(A.class, "void <init>()", "void foo()")
+ .compile()
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(
+ codeInspector.clazz(A.class).uniqueMethodWithName("foo"), not(isPresent()));
+ });
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class, A.class.getTypeName(), "foo")
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @Test
+ public void testKeepingBFooAndIFoo()
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class, B.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(I.class)
+ .addKeepMethodRules(B.class, "void <init>()", "void foo()")
+ .compile()
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(A.class), not(isPresent()));
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(
+ codeInspector.clazz(B.class).uniqueMethodWithName("foo"), not(isPresent()));
+ });
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @Test
+ public void testKeepingBFooAndAFoo()
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class, B.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(A.class, "void <init>()", "void foo()")
+ .addKeepMethodRules(B.class, "void <init>()", "void foo()")
+ .noMinification()
+ .compile()
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(A.class).uniqueMethodWithName("foo"), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ // TODO(b/144409021): We should be able to remove this.
+ assertEquals(
+ parameters.isDexRuntime(),
+ codeInspector.clazz(B.class).uniqueMethodWithName("foo").isPresent());
+ });
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ interface I {
+
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class A implements I {}
+
+ public static class B extends A {}
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ Object o = Class.forName(args[0]).getDeclaredConstructor().newInstance();
+ o.getClass().getMethod(args[1]).invoke(o);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodDefinedInLibraryTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodDefinedInLibraryTest.java
new file mode 100644
index 0000000..1254706
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodDefinedInLibraryTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InterfaceMethodDefinedInLibraryTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceMethodDefinedInLibraryTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testMethodInLibrary()
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8TestCompileResult intermediateResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class)
+ .addClasspathClasses(I.class)
+ .addKeepMethodRules(A.class, "void foo()", "void <init>()")
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(intermediateResult.writeToZip())
+ .addRunClasspathFiles(
+ parameters.isDexRuntime()
+ ? testForD8()
+ .addProgramClasses(I.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip()
+ : ToolHelper.getClassPathForTests())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ public interface I {
+
+ default void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class A implements I {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java
new file mode 100644
index 0000000..cc6ec52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+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 InterfaceMethodKeepResolutionTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceMethodKeepResolutionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testKeepKShouldKeepK()
+ throws CompilationFailedException, IOException, ExecutionException {
+ runTest(
+ ImmutableList.of(I.class, J.class, K.class),
+ K.class,
+ ImmutableList.of(ImplK.class),
+ ImplK.class);
+ }
+
+ @Test
+ public void testKeepLShouldKeepIorL()
+ throws CompilationFailedException, IOException, ExecutionException {
+ runTest(
+ ImmutableList.of(I.class, J.class, L.class),
+ L.class,
+ ImmutableList.of(ImplL.class),
+ ImplL.class);
+ }
+
+ @Test
+ public void testKeepClassShouldKeepA()
+ throws CompilationFailedException, IOException, ExecutionException {
+ runTest(
+ ImmutableList.of(I.class, J.class, K.class, A.class),
+ A.class,
+ ImmutableList.of(B.class, Main.class),
+ Main.class);
+ }
+
+ private void runTest(
+ Collection<Class<?>> classPathClasses,
+ Class<?> libraryClassWithMethod,
+ Collection<Class<?>> programClasses,
+ Class<?> main)
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8FullTestBuilder libraryBuilder =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classPathClasses)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(libraryClassWithMethod, "void foo()");
+ if (!libraryClassWithMethod.isInterface()) {
+ libraryBuilder.addKeepClassRules(libraryClassWithMethod);
+ }
+ testForRuntime(parameters)
+ .addProgramClasses(programClasses)
+ .addRunClasspathFiles(libraryBuilder.compile().writeToZip())
+ .run(parameters.getRuntime(), main)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ public interface I {
+ void foo();
+ }
+
+ public interface J extends I {
+ void foo();
+ }
+
+ public interface K extends J {}
+
+ public interface L extends I, J {}
+
+ public abstract static class A implements K {}
+
+ public static class ImplK implements K {
+
+ @Override
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+
+ public static void main(String[] args) {
+ ((K) new ImplK()).foo();
+ }
+ }
+
+ public static class ImplL implements L {
+
+ @Override
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+
+ public static void main(String[] args) {
+ ((L) new ImplL()).foo();
+ }
+ }
+
+ public static class B extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ((A) new B()).foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/LibraryInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/LibraryInterfaceMethodsTest.java
new file mode 100644
index 0000000..73bc53c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/LibraryInterfaceMethodsTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 LibraryInterfaceMethodsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public LibraryInterfaceMethodsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testLibraryBridgeDesugaring()
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseInterface.class, SubInterface.class)
+ .addLibraryFiles(runtimeJar(parameters))
+ .addKeepMethodRules(SubInterface.class, "int hashCode()")
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class, A.class.getTypeName(), "hashCode")
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ public interface BaseInterface {
+
+ int hashCode();
+ }
+
+ public interface SubInterface extends BaseInterface {}
+
+ public static class A implements SubInterface {
+
+ @Override
+ public int hashCode() {
+ System.out.println("Hello World!");
+ return 42;
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ Object o = Class.forName(args[0]).getDeclaredConstructor().newInstance();
+ o.getClass().getMethod(args[1]).invoke(o);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleTargetTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleTargetTest.java
new file mode 100644
index 0000000..7e11e46
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleTargetTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 MultipleTargetTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public MultipleTargetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+ assumeTrue(
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+ testForRuntime(parameters)
+ .addProgramClasses(Top.class, Left.class, Right.class, Bottom.class, A.class, Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(A.class.getName());
+ }
+
+ @Test
+ public void testSingleInheritanceR8BeforeNougat()
+ throws CompilationFailedException, IOException, ExecutionException {
+ assumeTrue(parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Top.class, Left.class, Right.class, Bottom.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(Bottom.class, "java.lang.String name()")
+ .noMinification()
+ .compile();
+ // TODO(b/144269679): We should be able to compile and run this.
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, Main.class)
+ .addClasspathClasses(Top.class, Left.class, Right.class, Bottom.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(
+ containsString(
+ "com.android.tools.r8.shaking.methods.interfaces.MultipleTargetTest$Left$-CC"));
+ }
+
+ @Test
+ public void testKeepingBottomName()
+ throws CompilationFailedException, IOException, ExecutionException {
+ assumeTrue(
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Top.class, Left.class, Right.class, Bottom.class)
+ .addKeepMethodRules(Bottom.class, "java.lang.String name()")
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, Main.class)
+ .addRunClasspathFiles(compileResult.writeToZip())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(A.class.getName());
+ }
+
+ interface Top {
+ default String name() {
+ return "unnamed";
+ }
+ }
+
+ interface Left extends Top {
+ default String name() {
+ return getClass().getName();
+ }
+ }
+
+ interface Right extends Top {
+ /* No override of default String name() */
+ }
+
+ interface Bottom extends Left, Right {}
+
+ public static class A implements Bottom {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A().name());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest.java
new file mode 100644
index 0000000..96803bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class StaticMethodsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimes()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+ .build();
+ }
+
+ public StaticMethodsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testForRuntime() throws ExecutionException, CompilationFailedException, IOException {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, J.class)
+ .addProgramClassFileData(StaticMethodsTest$MainForJDump.dump())
+ .run(parameters.getRuntime(), MainForJ.class)
+ .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+ }
+
+ public interface I {
+ static String foo() {
+ return foo("Hello ");
+ }
+
+ static String foo(String x) {
+ return x + "World!";
+ }
+ }
+
+ public interface J extends I {}
+
+ public static class ImplJ implements J {}
+
+ public static class MainForJ {
+
+ public static void main(String[] args) {
+ System.out.println(I.foo());
+ }
+ }
+
+ public static class StaticMethodsTest$MainForJDump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$MainForJ",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitSource("StaticMethodsTest.java", null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$MainForJ",
+ "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest",
+ "MainForJ",
+ ACC_PUBLIC | ACC_STATIC);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$I",
+ "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest",
+ "I",
+ ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(70, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable(
+ "this",
+ "Lcom/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$MainForJ;",
+ null,
+ label0,
+ label1,
+ 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(73, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$J",
+ "foo",
+ "()Ljava/lang/String;",
+ true);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(74, label1);
+ methodVisitor.visitInsn(RETURN);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java
new file mode 100644
index 0000000..c8b8be2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, 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.shaking.methods.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This tests is showing the issue filed in b/143590191. The expectations for the test should
+ * reflect the decisions to keep the interface method or not in the super interface.
+ */
+@RunWith(Parameterized.class)
+public class SubTypeOverridesInterfaceMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SubTypeOverridesInterfaceMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testKeepInterfaceMethodOnImplementingType()
+ throws CompilationFailedException, IOException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, I.class, A.class, B.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepMethodRules(A.class, "void <init>()", "void foo()")
+ .addKeepClassRules(B.class)
+ .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
+ .assertSuccessWithOutputLines("Hello World!")
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(A.class);
+ assertThat(clazz, isPresent());
+ assertThat(clazz.uniqueMethodWithName("foo"), isPresent());
+ });
+ }
+
+ public interface I {
+ void foo();
+ }
+
+ public abstract static class A implements I {}
+
+ public static class B extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ Object o = Class.forName(args[0]).getDeclaredConstructor().newInstance();
+ o.getClass().getMethod(args[1]).invoke(o);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index a74db8e..b2313a7 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -38,7 +38,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public ProxiesTest(TestParameters parameters) {
@@ -71,7 +71,7 @@
o.inliningInstructionLimit = 4;
})
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(inspection)
.run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 8157f47..428f9bb 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -176,8 +176,7 @@
/** Continuation for transforming a method. Will continue with the super visitor if called. */
@FunctionalInterface
public interface MethodInsnTransformContinuation {
- void visitMethodInsn(
- int opcode, String owner, String name, String descriptor, boolean isInterface);
+ void apply(int opcode, String owner, String name, String descriptor, boolean isInterface);
}
public ClassFileTransformer transformMethodInsnInMethod(
diff --git a/src/test/java/com/android/tools/r8/utils/TestParametersTest.java b/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
index 2d26e5d..7d26b86 100644
--- a/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
+++ b/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
@@ -8,11 +8,14 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersBuilder;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -71,4 +74,42 @@
assertThat(apiLevels, hasItem(dexRuntime.getMinApiLevel()));
});
}
+
+ @Test
+ public void testJdk9Presence() {
+ assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
+ || TestParametersBuilder.getRuntimesProperty().contains("jdk9"));
+ assertTrue(TestParametersBuilder
+ .builder()
+ .withAllRuntimesAndApiLevels()
+ .build()
+ .stream()
+ .anyMatch(parameter -> parameter.getRuntime().equals(TestRuntime.getCheckedInJdk9())));
+ }
+
+ @Test
+ public void testDexDefaultPresence() {
+ assumeTrue(ToolHelper.isLinux());
+ assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
+ || TestParametersBuilder.getRuntimesProperty().contains("dex-default"));
+ assertTrue(TestParametersBuilder
+ .builder()
+ .withAllRuntimesAndApiLevels()
+ .build()
+ .stream()
+ .anyMatch(parameter -> parameter.getRuntime().name().equals("dex-default")));
+ }
+
+ @Test
+ public void testDex444Presence() {
+ assumeTrue(ToolHelper.isLinux());
+ assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
+ || TestParametersBuilder.getRuntimesProperty().contains("dex-4.4.4"));
+ assertTrue(TestParametersBuilder
+ .builder()
+ .withAllRuntimesAndApiLevels()
+ .build()
+ .stream()
+ .anyMatch(parameter -> parameter.getRuntime().name().equals("dex-4.4.4")));
+ }
}
diff --git a/tools/archive.py b/tools/archive.py
index 62e0e27..d75242f 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -163,6 +163,10 @@
if utils.cloud_storage_exists(destination) and not options.dry_run:
raise Exception('Target archive directory %s already exists' % destination)
with utils.TempDir() as temp:
+ # Create pom file for our maven repository that we build for testing.
+ default_pom_file = os.path.join(temp, 'r8.pom')
+ create_maven_release.write_default_r8_pom_file(default_pom_file, version)
+
version_file = os.path.join(temp, 'r8-version.properties')
with open(version_file,'w') as version_writer:
version_writer.write('version.sha=' + GetGitHash() + '\n')
@@ -221,10 +225,14 @@
if file == utils.R8_JAR:
maven_dst = GetUploadDestination(utils.get_maven_path('r8', version),
'r8-%s.jar' % version, is_master)
+ maven_pom_dst = GetUploadDestination(
+ utils.get_maven_path('r8', version),
+ 'r8-%s.pom' % version, is_master)
if options.dry_run:
print('Dry run, not actually creating maven repo for R8')
else:
utils.upload_file_to_cloud_storage(tagged_jar, maven_dst)
+ utils.upload_file_to_cloud_storage(default_pom_file, maven_pom_dst)
print('Maven repo root available at: %s' % GetMavenUrl(is_master))
# Upload desugar_jdk_libs configuration to a maven compatible location.
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 6d7d8ab..72186a8 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -258,6 +258,9 @@
group=group, artifact=artifact, version=version)
return result
+def write_default_r8_pom_file(pom_file, version):
+ write_pom_file(R8_POMTEMPLATE, pom_file, version, generate_dependencies(), '')
+
def write_pom_file(template, pom_file, version, dependencies='', library_licenses=''):
version_pom = template.substitute(
version=version, dependencies=dependencies, library_licenses=library_licenses)
diff --git a/tools/linux/README.art-versions b/tools/linux/README.art-versions
index 095872b..9cf0717 100644
--- a/tools/linux/README.art-versions
+++ b/tools/linux/README.art-versions
@@ -41,6 +41,28 @@
repo init -u https://android.googlesource.com/platform/manifest -m aosp_master_manifest.xml
<continue with repo sync as above>
+art-10.0.0 (Android Q)
+---------------------
+Build from branch android-10.0.0_r14.
+
+export BRANCH=android-10.0.0_r14
+mkdir ${BRANCH}
+cd ${BRANCH}
+repo init -u https://android.googlesource.com/platform/manifest -b ${BRANCH}
+repo sync -cq -j24
+source build/envsetup.sh
+lunch aosp_coral-userdebug
+m -j24
+m -j24 build-art
+m -j24 test-art-host
+
+Collected into tools/linux/art-10.0.0.
+
+ cd <r8 checkout>
+ scripts/update-host-art.sh \
+ --android-checkout /usr/local/ssd/android/${BRANCH} \
+ --art-dir art-10.0.0 \
+ --android-product coral
art-9.0.0 (Android P)
---------------------
diff --git a/tools/linux/art-10.0.0.tar.gz.sha1 b/tools/linux/art-10.0.0.tar.gz.sha1
new file mode 100644
index 0000000..d64a0d7
--- /dev/null
+++ b/tools/linux/art-10.0.0.tar.gz.sha1
@@ -0,0 +1 @@
+04316aaf3b05ebfe17a35ea5f47d900ac3c32d69
\ No newline at end of file
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index add4b77..0941623 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -512,6 +512,7 @@
extra_args.append('-Dcom.android.tools.r8.fieldBitAccessAnalysis=1')
extra_args.append('-Dcom.android.tools.r8.generatedExtensionRegistryShrinking=1')
extra_args.append('-Dcom.android.tools.r8.generatedMessageLiteShrinking=1')
+ extra_args.append('-Dcom.android.tools.r8.generatedMessageLiteBuilderShrinking=1')
extra_args.append('-Dcom.android.tools.r8.stringSwitchConversion=1')
extra_args.append('-Dcom.android.tools.r8.traverseOneOfAndRepeatedProtoFields=0')
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 85ef6f1..8b188f5 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -18,6 +18,7 @@
import utils
import zipfile
from xml.dom import minidom
+from datetime import datetime
import as_utils
import update_prebuilds_in_android
@@ -681,7 +682,8 @@
def BuildAppWithShrinker(
app, repo, shrinker, checkout_dir, out_dir, temp_dir, options,
keepRuleSynthesisForRecompilation=False):
- print('Building {} with {}{}'.format(
+ print('[{}] Building {} with {}{}'.format(
+ datetime.now().strftime("%H:%M:%S"),
app.name,
shrinker,
' for recompilation' if keepRuleSynthesisForRecompilation else ''))
@@ -1293,7 +1295,13 @@
with utils.TempDir() as temp_dir:
if not (options.no_build or options.golem):
- gradle.RunGradle(['r8', 'r8lib', '-Pno_internal'])
+ gradle.RunGradle(['r8', '-Pno_internal'])
+ build_r8lib = False
+ for shrinker in options.shrinker:
+ if IsMinifiedR8(shrinker):
+ build_r8lib = True
+ if build_r8lib:
+ gradle.RunGradle(['r8lib', '-Pno_internal'])
if options.hash:
# Download r8-<hash>.jar from
diff --git a/tools/test.py b/tools/test.py
index a51f89a..9925dea 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -22,6 +22,7 @@
ALL_ART_VMS = [
"default",
+ "10.0.0",
"9.0.0",
"8.1.0",
"7.0.0",
@@ -155,6 +156,11 @@
def Main():
(options, args) = ParseOptions()
+ # See b/144966342
+ if options.dex_vm == '10.0.0':
+ print 'Running on 10.0.0 is temporarily disabled, see b/144966342'
+ return 0
+
if utils.is_bot():
gradle.RunGradle(['--no-daemon', 'clean'])
@@ -433,7 +439,6 @@
test = href.replace('.html','').replace('#', '.').replace('.classMethod', '')
failing.add(test)
return list(failing)
-
if __name__ == '__main__':
return_code = Main()
if return_code != 0: