Merge commit '41c2eb659a45e0f909cc6b3906b2454d12ac5513' into dev-release
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 397f2c5..241f740 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -78,7 +78,8 @@
"java.net.URLDecoder": "j$.net.URLDecoder",
"java.net.URLEncoder": "j$.net.URLEncoder",
"java.lang.Desugar": "j$.lang.Desugar",
- "java.lang.ref.Cleaner": "j$.lang.ref.Cleaner"
+ "java.lang.ref.Cleaner": "j$.lang.ref.Cleaner",
+ "sun.security.action.": "j$.sun.security.action."
}
},
{
@@ -95,7 +96,6 @@
"java.io.DesugarFile" : "j$.io.DesugarFile",
"java.util.Desugar": "j$.util.Desugar",
"sun.misc.Desugar": "j$.sun.misc.Desugar",
- "sun.security.action.": "j$.sun.security.action.",
"jdk.internal.": "j$.jdk.internal.",
"java.nio.Desugar": "j$.nio.Desugar",
"java.nio.channels.AsynchronousChannel": "j$.nio.channels.AsynchronousChannel",
@@ -204,6 +204,13 @@
],
"program_flags": [
{
+ "api_level_below_or_equal": 10000,
+ "rewrite_prefix": {
+ "java.net.URLDecoder": "j$.net.URLDecoder",
+ "java.net.URLEncoder": "j$.net.URLEncoder"
+ }
+ },
+ {
"api_level_below_or_equal": 25,
"rewrite_prefix": {
"java.time.": "j$.time.",
@@ -286,6 +293,15 @@
"java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
"java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
}
+ },
+ {
+ "api_level_below_or_equal": 18,
+ "rewrite_prefix": {
+ "java.nio.charset.StandardCharsets": "j$.nio.charset.StandardCharsets"
+ },
+ "retarget_lib_member": {
+ "java.lang.Character#isBmpCodePoint": "j$.lang.DesugarCharacter"
+ }
}
],
"shrinker_config": [
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index a13b905..4eaea9f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.shaking.MissingClasses;
import com.android.tools.r8.synthesis.CommittedItems;
import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.SetUtils;
@@ -169,6 +170,22 @@
getSyntheticItems().addLegacySyntheticClass(clazz, context, featureSplit);
}
+ /** Primitive traversal over all (non-interface) superclasses of a given type. */
+ public TraversalContinuation traverseSuperClasses(
+ DexClass clazz, TriFunction<DexType, DexClass, DexClass, TraversalContinuation> fn) {
+ DexClass currentClass = clazz;
+ while (currentClass != null && currentClass.getSuperType() != null) {
+ DexClass superclass = definitionFor(currentClass.getSuperType());
+ TraversalContinuation stepResult =
+ fn.apply(currentClass.getSuperType(), superclass, currentClass);
+ if (stepResult.shouldBreak()) {
+ return stepResult;
+ }
+ currentClass = superclass;
+ }
+ return CONTINUE;
+ }
+
/**
* Primitive traversal over all supertypes of a given type.
*
@@ -291,6 +308,56 @@
.shouldBreak();
}
+ public boolean isSubtype(DexClass subclass, DexClass superclass) {
+ return superclass.isInterface()
+ ? isSubtype(subclass.getType(), superclass.getType())
+ : isSubtypeOfClass(subclass, superclass);
+ }
+
+ public boolean isSubtypeOfClass(DexClass subclass, DexClass superclass) {
+ assert subclass != null;
+ assert superclass != null;
+ assert !superclass.isInterface();
+ if (subclass.isInterface()) {
+ return superclass.getType() == dexItemFactory().objectType;
+ }
+ return subclass == superclass || isStrictSubtypeOfClass(subclass, superclass);
+ }
+
+ public boolean isStrictSubtypeOfClass(DexClass subclass, DexClass superclass) {
+ assert subclass != null;
+ assert superclass != null;
+ assert !subclass.isInterface();
+ assert !superclass.isInterface();
+ if (subclass == superclass) {
+ return false;
+ }
+ // Treat object special: it is always the superclass even for broken hierarchies.
+ if (subclass.getType() == dexItemFactory().objectType) {
+ return false;
+ }
+ if (superclass.getType() == dexItemFactory().objectType) {
+ return true;
+ }
+ BooleanBox result = new BooleanBox();
+ traverseSuperClasses(
+ subclass,
+ (currentType, currentClass, immediateSubclass) -> {
+ if (currentType == superclass.getType()) {
+ result.set();
+ return BREAK;
+ }
+ if (currentClass == null) {
+ return BREAK;
+ }
+ if (superclass.isProgramClass() && !currentClass.isProgramClass()) {
+ return BREAK;
+ }
+ return CONTINUE;
+ });
+ return result.isTrue();
+ }
+
public boolean inSameHierarchy(DexType type, DexType other) {
assert type.isClassType();
assert other.isClassType();
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index d276e9b..b3e8f74 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -351,6 +351,10 @@
return definitionSupplier.definitionFor(this).isInterface();
}
+ public DexProgramClass asProgramClass(DexDefinitionSupplier definitions) {
+ return asProgramClassOrNull(definitions.definitionFor(this));
+ }
+
public boolean isProgramType(DexDefinitionSupplier definitions) {
DexClass clazz = definitions.definitionFor(this);
return clazz != null && clazz.isProgramClass();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index fb5cfa3..b536669 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoCheckDiscard;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
+import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerCycles;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
import com.android.tools.r8.horizontalclassmerging.policies.NoConstructorCollisions;
import com.android.tools.r8.horizontalclassmerging.policies.NoDeadEnumLiteMaps;
@@ -217,7 +218,7 @@
private static void addMultiClassPoliciesForMergingNonSyntheticClasses(
AppView<AppInfoWithLiveness> appView,
ImmutableList.Builder<Policy> builder) {
- builder.add(new NoDeadLocks(appView));
+ builder.add(new NoClassInitializerCycles(appView), new NoDeadLocks(appView));
}
private static void addMultiClassPoliciesForInterfaceMerging(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
new file mode 100644
index 0000000..f8faefd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -0,0 +1,506 @@
+// Copyright (c) 2021, 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.horizontalclassmerging.policies;
+
+import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Disallows merging of classes when the merging could introduce class initialization deadlocks.
+ *
+ * <p>Example: In the below example, if thread t0 triggers the class initialization of A, and thread
+ * t1 triggers the class initialization of C, then the program will never deadlock. However, if
+ * classes B and C are merged, then the program may all of a sudden deadlock, since thread t0 may
+ * hold the lock for A and wait for BC's lock, meanwhile thread t1 holds the lock for BC while
+ * waiting for A's lock.
+ *
+ * <pre>
+ * class A {
+ * static {
+ * new B();
+ * }
+ * }
+ * class B extends A {}
+ * class C extends A {}
+ * </pre>
+ *
+ * <p>To identify the above situation, we perform a tracing from {@code A.<clinit>} to check if
+ * there is an execution path that triggers the class initialization of B or C. In that case, the
+ * reached subclass is ineligible for class merging.
+ *
+ * <p>Example: In the below example, if thread t0 triggers the class initialization of A, and thread
+ * t1 triggers the class initialization of B, then the program will never deadlock. However, if
+ * classes B and C are merged, then the program may all of a sudden deadlock, since thread 0 may
+ * hold the lock for A and wait for BC's lock, meanwhile thread t1 holds the lock for BC while
+ * waiting for A's lock.
+ *
+ * <pre>
+ * class A {
+ * static {
+ * new C();
+ * }
+ * }
+ * class B {
+ * static {
+ * new A();
+ * }
+ * }
+ * class C {}
+ * </pre>
+ *
+ * <p>To identify the above situation, we perform a tracing for each {@code <clinit>} in the merge
+ * group. If we find an execution path from the class initializer of one class in the merge group to
+ * the class initializer of another class in the merge group, then after merging there is a cycle in
+ * the class initialization that could lead to a deadlock.
+ */
+public class NoClassInitializerCycles extends MultiClassPolicyWithPreprocessing<Void> {
+
+ final AppView<AppInfoWithLiveness> appView;
+
+ // Mapping from each merge candidate to its merge group.
+ final Map<DexProgramClass, MergeGroup> allGroups = new IdentityHashMap<>();
+
+ public NoClassInitializerCycles(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group, Void nothing) {
+ Tracer tracer = new Tracer(group);
+ removeClassesWithPossibleClassInitializerDeadlock(group, tracer);
+
+ List<MergeGroup> newGroups = new LinkedList<>();
+ for (DexProgramClass clazz : group) {
+ MergeGroup newGroup = getOrCreateGroupFor(clazz, newGroups, tracer);
+ if (newGroup != null) {
+ newGroup.add(clazz);
+ } else {
+ // Ineligible for merging.
+ }
+ }
+ return removeTrivialGroups(newGroups);
+ }
+
+ private MergeGroup getOrCreateGroupFor(
+ DexProgramClass clazz, List<MergeGroup> groups, Tracer tracer) {
+ assert !tracer.hasPossibleClassInitializerDeadlock(clazz);
+
+ ProgramMethod classInitializer = clazz.getProgramClassInitializer();
+ if (classInitializer != null) {
+ assert tracer.verifySeenSetIsEmpty();
+ assert tracer.verifyWorklistIsEmpty();
+ tracer.setTracingRoot(clazz);
+ tracer.enqueueMethod(classInitializer);
+ tracer.trace();
+ if (tracer.hasPossibleClassInitializerDeadlock(clazz)) {
+ // Ineligible for merging.
+ return null;
+ }
+ }
+
+ for (MergeGroup group : groups) {
+ if (canMerge(clazz, group, tracer)) {
+ return group;
+ }
+ }
+
+ MergeGroup newGroup = new MergeGroup();
+ groups.add(newGroup);
+ return newGroup;
+ }
+
+ private boolean canMerge(DexProgramClass clazz, MergeGroup group, Tracer tracer) {
+ for (DexProgramClass member : group) {
+ // Check that the class initialization of the given class cannot reach the class initializer
+ // of the current group member.
+ if (tracer.isClassInitializedByClassInitializationOf(member, clazz)) {
+ return false;
+ }
+ // Check that the class initialization of the current group member cannot reach the class
+ // initializer of the given class.
+ if (tracer.isClassInitializedByClassInitializationOf(clazz, member)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Runs the tracer from the parent class initializers, using the entire group as tracing context.
+ * If the class initializer of one of the classes in the merge group is reached, then that class
+ * is not eligible for merging.
+ */
+ private void removeClassesWithPossibleClassInitializerDeadlock(MergeGroup group, Tracer tracer) {
+ tracer.setTracingRoots(group);
+ tracer.enqueueParentClassInitializers(group);
+ tracer.trace();
+ group.removeIf(tracer::hasPossibleClassInitializerDeadlock);
+ }
+
+ @Override
+ public void clear() {
+ allGroups.clear();
+ }
+
+ @Override
+ public String getName() {
+ return "NoClassInitializerCycles";
+ }
+
+ @Override
+ public Void preprocess(Collection<MergeGroup> groups) {
+ for (MergeGroup group : groups) {
+ for (DexProgramClass clazz : group) {
+ allGroups.put(clazz, group);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ HorizontalClassMergerOptions options = appView.options().horizontalClassMergerOptions();
+ return !options.isClassInitializerDeadlockDetectionEnabled();
+ }
+
+ private class Tracer {
+
+ final Set<DexProgramClass> group;
+
+ private final Set<DexProgramClass> seenClassInitializers = Sets.newIdentityHashSet();
+ private final ProgramMethodSet seenMethods = ProgramMethodSet.create();
+ private final Deque<ProgramMethod> worklist = new ArrayDeque<>();
+
+ // Mapping from each merge grop member to the set of merge group members whose class
+ // initializers may trigger the class initialization of the group member.
+ private final Map<DexProgramClass, Set<DexProgramClass>> classInitializerReachableFromClasses =
+ new IdentityHashMap<>();
+
+ // The current tracing roots (either the entire merge group or one of the classes in the merge
+ // group).
+ private Collection<DexProgramClass> tracingRoots;
+
+ Tracer(MergeGroup group) {
+ this.group = SetUtils.newIdentityHashSet(group);
+ }
+
+ void clearSeen() {
+ seenClassInitializers.clear();
+ seenMethods.clear();
+ }
+
+ boolean markClassInitializerAsSeen(DexProgramClass clazz) {
+ return seenClassInitializers.add(clazz);
+ }
+
+ boolean enqueueMethod(ProgramMethod method) {
+ if (seenMethods.add(method)) {
+ worklist.add(method);
+ return true;
+ }
+ return false;
+ }
+
+ void enqueueParentClassInitializers(MergeGroup group) {
+ DexProgramClass member = group.iterator().next();
+ enqueueParentClassInitializers(member);
+ }
+
+ void enqueueParentClassInitializers(DexProgramClass clazz) {
+ DexProgramClass superClass =
+ asProgramClassOrNull(appView.definitionFor(clazz.getSuperType()));
+ if (superClass == null) {
+ return;
+ }
+ ProgramMethod classInitializer = superClass.getProgramClassInitializer();
+ if (classInitializer != null) {
+ enqueueMethod(classInitializer);
+ }
+ enqueueParentClassInitializers(superClass);
+ }
+
+ void recordClassInitializerReachableFromTracingRoots(DexProgramClass clazz) {
+ assert group.contains(clazz);
+ classInitializerReachableFromClasses
+ .computeIfAbsent(clazz, ignoreKey(Sets::newIdentityHashSet))
+ .addAll(tracingRoots);
+ }
+
+ void recordTracingRootsIneligibleForClassMerging() {
+ for (DexProgramClass tracingRoot : tracingRoots) {
+ classInitializerReachableFromClasses
+ .computeIfAbsent(tracingRoot, ignoreKey(Sets::newIdentityHashSet))
+ .add(tracingRoot);
+ }
+ }
+
+ boolean hasSingleTracingRoot(DexProgramClass clazz) {
+ return tracingRoots.size() == 1 && tracingRoots.contains(clazz);
+ }
+
+ boolean hasPossibleClassInitializerDeadlock(DexProgramClass clazz) {
+ return classInitializerReachableFromClasses
+ .getOrDefault(clazz, Collections.emptySet())
+ .contains(clazz);
+ }
+
+ boolean isClassInitializedByClassInitializationOf(
+ DexProgramClass classToBeInitialized, DexProgramClass classBeingInitialized) {
+ return classInitializerReachableFromClasses
+ .getOrDefault(classToBeInitialized, Collections.emptySet())
+ .contains(classBeingInitialized);
+ }
+
+ void setTracingRoot(DexProgramClass tracingRoot) {
+ setTracingRoots(ImmutableList.of(tracingRoot));
+ }
+
+ void setTracingRoots(Collection<DexProgramClass> tracingRoots) {
+ this.tracingRoots = tracingRoots;
+ }
+
+ void trace() {
+ // TODO(b/205611444): Avoid redundant tracing of the same methods.
+ while (!worklist.isEmpty()) {
+ ProgramMethod method = worklist.removeLast();
+ method.registerCodeReferences(new TracerUseRegistry(method));
+ }
+ clearSeen();
+ }
+
+ boolean verifySeenSetIsEmpty() {
+ assert seenClassInitializers.isEmpty();
+ assert seenMethods.isEmpty();
+ return true;
+ }
+
+ boolean verifyWorklistIsEmpty() {
+ assert worklist.isEmpty();
+ return true;
+ }
+
+ class TracerUseRegistry extends UseRegistry<ProgramMethod> {
+
+ TracerUseRegistry(ProgramMethod context) {
+ super(appView, context);
+ }
+
+ private void fail() {
+ // Ensures that hasPossibleClassInitializerDeadlock() returns true for each tracing root.
+ recordTracingRootsIneligibleForClassMerging();
+ doBreak();
+ }
+
+ private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexType type) {
+ DexProgramClass clazz = type.asProgramClass(appView);
+ if (clazz != null) {
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(clazz);
+ }
+ }
+
+ private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexProgramClass clazz) {
+ if (!isClassAlreadyInitializedInCurrentContext(clazz)) {
+ triggerClassInitializer(clazz);
+ }
+ }
+
+ private boolean isClassAlreadyInitializedInCurrentContext(DexProgramClass clazz) {
+ // TODO(b/205611444): There is only a risk of a deadlock if the execution path comes from
+ // outside the merge group. We could address this by updating this check.
+ return appView.appInfo().isSubtype(getContext().getHolder(), clazz);
+ }
+
+ private void triggerClassInitializer(DexType type) {
+ DexProgramClass clazz = type.asProgramClass(appView);
+ if (clazz != null) {
+ triggerClassInitializer(clazz);
+ }
+ }
+
+ // TODO(b/205611444): This needs to account for pending merging. If the given class is in a
+ // merge group, then this should trigger the class initializers of all of the classes in the
+ // merge group.
+ private void triggerClassInitializer(DexProgramClass clazz) {
+ if (!markClassInitializerAsSeen(clazz)) {
+ return;
+ }
+
+ if (group.contains(clazz)) {
+ if (hasSingleTracingRoot(clazz)) {
+ // We found an execution path from the class initializer of the given class back to its
+ // own class initializer. Therefore this class is not eligible for merging.
+ fail();
+ } else {
+ // Record that this class initializer is reachable from the tracing roots.
+ recordClassInitializerReachableFromTracingRoots(clazz);
+ }
+ }
+
+ ProgramMethod classInitializer = clazz.getProgramClassInitializer();
+ if (classInitializer != null) {
+ if (!enqueueMethod(classInitializer)) {
+ // This class initializer is already seen in the current context, thus all of the parent
+ // class initializers are also seen in the current context.
+ return;
+ }
+ }
+
+ triggerClassInitializer(clazz.getSuperType());
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(type);
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ DexMethod rewrittenMethod =
+ appView.graphLens().lookupInvokeDirect(method, getContext()).getReference();
+ MethodResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOnClass(rewrittenMethod);
+ if (resolutionResult.isSingleResolution()
+ && resolutionResult.getResolvedHolder().isProgramClass()) {
+ enqueueMethod(resolutionResult.getResolvedProgramMethod());
+ }
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ fail();
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ DexMethod rewrittenMethod =
+ appView.graphLens().lookupInvokeStatic(method, getContext()).getReference();
+ ProgramMethod resolvedMethod =
+ appView
+ .appInfo()
+ .unsafeResolveMethodDueToDexFormat(rewrittenMethod)
+ .getResolvedProgramMethod();
+ if (resolvedMethod != null) {
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(resolvedMethod.getHolder());
+ enqueueMethod(resolvedMethod);
+ }
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ DexMethod rewrittenMethod =
+ appView.graphLens().lookupInvokeSuper(method, getContext()).getReference();
+ ProgramMethod superTarget =
+ asProgramMethodOrNull(
+ appView.appInfo().lookupSuperTarget(rewrittenMethod, getContext()));
+ if (superTarget != null) {
+ enqueueMethod(superTarget);
+ }
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ fail();
+ }
+
+ @Override
+ public void registerNewInstance(DexType type) {
+ DexType rewrittenType = appView.graphLens().lookupType(type);
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenType);
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ DexField rewrittenField = appView.graphLens().lookupField(field);
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType());
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ DexField rewrittenField = appView.graphLens().lookupField(field);
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType());
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ // Intentionally empty, new-array etc. does not trigger any class initialization.
+ }
+
+ @Override
+ public void registerCallSite(DexCallSite callSite) {
+ LambdaDescriptor descriptor =
+ LambdaDescriptor.tryInfer(callSite, appView.appInfo(), getContext());
+ if (descriptor != null) {
+ // Use of lambda metafactory does not trigger any class initialization.
+ } else {
+ fail();
+ }
+ }
+
+ @Override
+ public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
+ // Intentionally empty, does not trigger any class initialization.
+ }
+
+ @Override
+ public void registerConstClass(
+ DexType type,
+ ListIterator<? extends CfOrDexInstruction> iterator,
+ boolean ignoreCompatRules) {
+ // Intentionally empty, does not trigger any class initialization.
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ // Intentionally empty, does not trigger any class initialization.
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ // Intentionally empty, does not trigger any class initialization.
+ }
+
+ @Override
+ public void registerInstanceOf(DexType type) {
+ // Intentionally empty, does not trigger any class initialization.
+ }
+
+ @Override
+ public void registerExceptionGuard(DexType guard) {
+ // Intentionally empty, does not trigger any class initialization.
+ }
+ }
+ }
+}
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 1e69a6b..40c2ce3 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
@@ -113,7 +113,11 @@
this.extraNeverInlineMethods =
appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
? ImmutableSet.of()
- : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
+ : ImmutableSet.of(
+ intrinsics.throwNpe,
+ intrinsics.throwParameterIsNullException,
+ intrinsics.throwParameterIsNullNPE,
+ intrinsics.throwParameterIsNullIAE);
this.lensCodeRewriter = lensCodeRewriter;
this.mainDexInfo = appView.appInfo().getMainDexInfo();
this.singleInlineCallers =
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 b5ced8c..d994add 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
@@ -101,6 +101,7 @@
import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.typechecks.CheckCastAndInstanceOfMethodSpecialization;
import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.kotlin.Kotlin.Intrinsics;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -744,11 +745,13 @@
}
// We need to ignore the holder, since Kotlin adds different versions of null-check machinery,
// e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
- DexMethod checkParameterIsNotNullMethod =
- appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull;
+ Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
DexMethod originalInvokedMethod =
appView.graphLens().getOriginalMethodSignature(invoke.getInvokedMethod());
- return originalInvokedMethod.match(checkParameterIsNotNullMethod)
+ boolean isCheckNotNullMethod =
+ originalInvokedMethod.match(intrinsics.checkParameterIsNotNull)
+ || originalInvokedMethod.match(intrinsics.checkNotNullParameter);
+ return isCheckNotNullMethod
&& invoke.getFirstArgument() == value
&& originalInvokedMethod.getHolderType().getPackageDescriptor().startsWith(Kotlin.NAME);
}
@@ -763,11 +766,11 @@
}
// We need to ignore the holder, since Kotlin adds different versions of null-check machinery,
// e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
- DexMethod throwParameterIsNullExceptionMethod =
- appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException;
+ Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
DexMethod originalInvokedMethod =
appView.graphLens().getOriginalMethodSignature(invoke.getInvokedMethod());
- return originalInvokedMethod.match(throwParameterIsNullExceptionMethod)
+ return (originalInvokedMethod.match(intrinsics.throwParameterIsNullException)
+ || originalInvokedMethod.match(intrinsics.throwParameterIsNullNPE))
&& originalInvokedMethod.getHolderType().getPackageDescriptor().startsWith(Kotlin.NAME);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 9470367..eb3af79 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -164,9 +164,24 @@
public final DexType type = factory.createType(PACKAGE_PREFIX + "jvm/internal/Intrinsics;");
public final DexMethod throwParameterIsNullException = factory.createMethod(type,
factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
+ public final DexMethod throwParameterIsNullNPE =
+ factory.createMethod(
+ type,
+ factory.createProto(factory.voidType, factory.stringType),
+ "throwParameterIsNullNPE");
+ public final DexMethod throwParameterIsNullIAE =
+ factory.createMethod(
+ type,
+ factory.createProto(factory.voidType, factory.stringType),
+ "throwParameterIsNullIAE");
public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
factory.createProto(factory.voidType, factory.objectType, factory.stringType),
"checkParameterIsNotNull");
+ public final DexMethod checkNotNullParameter =
+ factory.createMethod(
+ type,
+ factory.createProto(factory.voidType, factory.objectType, factory.stringType),
+ "checkNotNullParameter");
public final DexMethod throwNpe = factory.createMethod(
type, factory.createProto(factory.voidType), "throwNpe");
}
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 57ce006..72b0295 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -148,7 +148,7 @@
private boolean isPresentSinceMinApi(LibraryMethod method) {
AndroidApiLevel apiLevel =
androidApiLevelCompute.computeApiLevelForLibraryReference(method.getReference());
- return apiLevel.isLessThanOrEqualTo(options.minApiLevel);
+ return apiLevel != AndroidApiLevel.UNKNOWN && apiLevel.isLessThanOrEqualTo(options.minApiLevel);
}
public static DexField validMemberRebindingTargetFor(
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 5cd2455..acdf404 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1357,6 +1357,8 @@
private boolean enable =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
+ // TODO(b/205611444): Enable by default.
+ private boolean enableClassInitializerDeadlockDetection = false;
private boolean enableInterfaceMerging =
System.getProperty("com.android.tools.r8.disableHorizontalInterfaceMerging") == null;
private boolean enableInterfaceMergingInInitial = false;
@@ -1390,6 +1392,10 @@
return true;
}
+ public boolean isClassInitializerDeadlockDetectionEnabled() {
+ return enableClassInitializerDeadlockDetection;
+ }
+
public boolean isEnabled(HorizontalClassMerger.Mode mode) {
if (!enable || debug || intermediate) {
return false;
@@ -1424,6 +1430,10 @@
return restrictToSynthetics || !isOptimizing() || !isShrinking();
}
+ public void setEnableClassInitializerDeadlockDetection() {
+ enableClassInitializerDeadlockDetection = true;
+ }
+
public void setEnableInterfaceMergingInInitial() {
enableInterfaceMergingInInitial = true;
}
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index b39af91..b2bf2e8 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -91,10 +91,7 @@
AndroidApiLevel minApiLevel,
KeepRuleConsumer keepRuleConsumer,
StringResource desugaredLibraryConfiguration) {
- if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
- super.enableCoreLibraryDesugaring(
- minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
- }
+ super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index dc4a247..cd0036b 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -8,7 +8,7 @@
import static junit.framework.TestCase.assertTrue;
import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryJDK11Undesugarer;
+import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidAppConsumers;
diff --git a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
index 111b040..d79f4af 100644
--- a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
+++ b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
@@ -260,14 +260,7 @@
}
private static KeepRuleConsumer createKeepRuleConsumer(AndroidApiLevel apiLevel) {
- if (requiresAnyCoreLibDesugaring(apiLevel)) {
return new PresentKeepRuleConsumer();
- }
- return new AbsentKeepRuleConsumer();
- }
-
- private static boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel) {
- return apiLevel.isLessThan(AndroidApiLevel.O);
}
public static class PresentKeepRuleConsumer implements KeepRuleConsumer {
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 0f72c5a..6c8ac51 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -654,10 +654,7 @@
AndroidApiLevel minApiLevel,
KeepRuleConsumer keepRuleConsumer,
StringResource desugaredLibraryConfiguration) {
- if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
- super.enableCoreLibraryDesugaring(
- minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
- }
+ super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 69bf834..9ab97e7 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -258,9 +258,7 @@
public CR addDesugaredCoreLibraryRunClassPath(
Function<AndroidApiLevel, Path> classPathSupplier, AndroidApiLevel minAPILevel) {
- if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
- addRunClasspathFiles(classPathSupplier.apply(minAPILevel));
- }
+ addRunClasspathFiles(classPathSupplier.apply(minAPILevel));
return self();
}
@@ -269,9 +267,7 @@
AndroidApiLevel minAPILevel,
String keepRules,
boolean shrink) {
- if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
- addRunClasspathFiles(classPathSupplier.apply(minAPILevel, keepRules, shrink));
- }
+ addRunClasspathFiles(classPathSupplier.apply(minAPILevel, keepRules, shrink));
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 2fe8ae0..c4e86e3 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -484,7 +484,6 @@
AndroidApiLevel minApiLevel,
KeepRuleConsumer keepRuleConsumer,
StringResource desugaredLibraryConfiguration) {
- assert minApiLevel.getLevel() < AndroidApiLevel.O.getLevel();
return enableLibraryDesugaring(
LibraryDesugaringTestConfiguration.builder()
.setMinApi(minApiLevel)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ImplicitClassInitializationSynchronizationTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ImplicitClassInitializationSynchronizationTest.java
index aa0d663..e1e3c7b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ImplicitClassInitializationSynchronizationTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ImplicitClassInitializationSynchronizationTest.java
@@ -4,14 +4,14 @@
package com.android.tools.r8.classmerging.horizontal;
-import static org.hamcrest.core.IsEqual.equalTo;
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.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
import java.lang.Thread.State;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -34,25 +34,15 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/205611444): Should not be merged.
.addHorizontallyMergedClassesInspector(
- inspector -> inspector.assertIsCompleteMergeGroup(B.class, C.class))
+ inspector -> inspector.assertClassesNotMerged(B.class, C.class))
+ .addOptionsModification(
+ options ->
+ options.horizontalClassMergerOptions().setEnableClassInitializerDeadlockDetection())
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
- // TODO(b/205611444): Should succeed.
- .assertFailure()
- .assertStdoutMatches(
- equalTo(
- StringUtils.lines(
- "Main: fork",
- "Main: wait",
- "Worker: notify",
- "Worker: wait",
- "Main: notified",
- "Main: lock C",
- "Worker: notified",
- "Worker: lock B")));
+ .assertSuccessWithOutputLines(getExpectedOutput());
}
@Test
@@ -61,19 +51,23 @@
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines(
- "Main: fork",
- "Main: wait",
- "Worker: notify",
- "Worker: wait",
- "Main: notified",
- "Main: lock C",
- "Worker: notified",
- "Worker: lock B",
- "B",
- "Worker: unlock B",
- "C",
- "Main: unlock C");
+ .assertSuccessWithOutputLines(getExpectedOutput());
+ }
+
+ private static List<String> getExpectedOutput() {
+ return ImmutableList.of(
+ "Main: fork",
+ "Main: wait",
+ "Worker: notify",
+ "Worker: wait",
+ "Main: notified",
+ "Main: lock C",
+ "Worker: notified",
+ "Worker: lock B",
+ "B",
+ "Worker: unlock B",
+ "C",
+ "Main: unlock C");
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConcurrentHashMapJDK11Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConcurrentHashMapJDK11Test.java
new file mode 100644
index 0000000..434f2b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConcurrentHashMapJDK11Test.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2021, 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.jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentHashMap.KeySetView;
+import org.junit.Assume;
+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 ConcurrentHashMapJDK11Test extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT = StringUtils.lines("1", "one=ONE, two=TWO");
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public ConcurrentHashMapJDK11Test(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ Assume.assumeTrue(isJDK11DesugaredLibrary());
+ Assume.assumeFalse(
+ "TODO(b/171682016): reduce methods are backported but rely on non backported ForkJoinPool",
+ parameters.getDexRuntimeVersion().isEqualTo(Version.V4_0_4));
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addInnerClasses(ConcurrentHashMapJDK11Test.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Assume.assumeTrue(isJDK11DesugaredLibrary());
+ Assume.assumeFalse(
+ "TODO(b/171682016): reduce methods are backported but rely on non backported ForkJoinPool",
+ parameters.getDexRuntimeVersion().isEqualTo(Version.V4_0_4));
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(Backend.DEX)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addInnerClasses(ConcurrentHashMapJDK11Test.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ KeySetView<Object, Boolean> keySet = ConcurrentHashMap.newKeySet();
+ keySet.add(new Object());
+ System.out.println(keySet.size());
+
+ ConcurrentHashMap<String, String> chm = new ConcurrentHashMap<>();
+ chm.put("one", "ONE");
+ chm.put("two", "TWO");
+ String reduced = chm.reduce(1, (key, value) -> key + "=" + value, (s1, s2) -> s1 + ", " + s2);
+ System.out.println(reduced);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java
rename to src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
index f79db70..bdda327 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
@@ -2,9 +2,10 @@
// 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;
+package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.transformers.MethodTransformer;
import com.android.tools.r8.utils.StreamUtils;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java
new file mode 100644
index 0000000..368d169
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2021, 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.jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assume;
+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 StandardCharsetTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("%E3%81%8B", "%82%A0%82%A9%97%43%24%E3%81%8B", "true");
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public StandardCharsetTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ Assume.assumeTrue(isJDK11DesugaredLibrary());
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramClassFileData(getProgramClassFileData())
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Assume.assumeTrue(isJDK11DesugaredLibrary());
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(Backend.DEX)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramClassFileData(getProgramClassFileData())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private Collection<byte[]> getProgramClassFileData() throws IOException {
+ return ImmutableList.of(
+ transformer(TestClass.class)
+ .addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitMethodInsn(
+ int opcode,
+ String owner,
+ String name,
+ String descriptor,
+ boolean isInterface) {
+ if (opcode == Opcodes.INVOKESTATIC && name.equals("encode")) {
+ super.visitMethodInsn(
+ opcode, "java/net/URLEncoder", name, descriptor, isInterface);
+ return;
+ }
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform());
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(encode("か", StandardCharsets.UTF_8));
+ System.out.println(
+ encode("あ", "Shift_JIS")
+ + encode("か", "Shift_JIS")
+ + encode("佑", "Shift_JIS")
+ + encode("$", "Shift_JIS")
+ + encode("か", StandardCharsets.UTF_8));
+ System.out.println(Character.isBmpCodePoint('か'));
+ }
+
+ // Replaced in the transformer by JDK 11 URLEncoder#encode.
+ public static String encode(String s, Charset set) {
+ return null;
+ }
+
+ // Replaced in the transformer by JDK 11 URLEncoder#encode.
+ public static String encode(String s, String set) {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index c245f5a..4aaea25 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -256,12 +256,12 @@
clazz,
"main",
String[].class.getCanonicalName()));
+ String kotlinIntrinsics = "void kotlin.jvm.internal.Intrinsics";
assertEquals(
Lists.newArrayList(
kotlinc.is(KOTLINC_1_3_72)
- ? "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"
- : "void kotlin.jvm.internal.Intrinsics.checkNotNullParameter(java.lang.Object,"
- + " java.lang.String)"),
+ ? kotlinIntrinsics + ".throwParameterIsNullException(java.lang.String)"
+ : kotlinIntrinsics + ".throwParameterIsNullNPE(java.lang.String)"),
collectStaticCalls(clazz, "main", String[].class.getCanonicalName()));
});
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
new file mode 100644
index 0000000..802be75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+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.utils.codeinspector.MethodSubject;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinIntrinsicsInlineChainTest extends KotlinTestBase {
+
+ private static final String FOLDER = "intrinsics";
+ private static final String MAIN = FOLDER + ".InlineChainParameterCheckKt";
+
+ @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+ BooleanUtils.values());
+ }
+
+ private final TestParameters parameters;
+ private final boolean allowAccessModification;
+
+ public KotlinIntrinsicsInlineChainTest(
+ TestParameters parameters,
+ KotlinTestParameters kotlinParameters,
+ boolean allowAccessModification) {
+ super(kotlinParameters);
+ this.parameters = parameters;
+ this.allowAccessModification = allowAccessModification;
+ }
+
+ private static final KotlinCompileMemoizer compiledJars =
+ getCompileMemoizer(getKotlinFilesInResource(FOLDER), FOLDER)
+ .configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect());
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(
+ compiledJars.getForConfiguration(kotlinc, targetVersion),
+ kotlinc.getKotlinAnnotationJar())
+ .addKeepMainRule(MAIN)
+ .allowAccessModification(allowAccessModification)
+ .allowDiagnosticWarningMessages()
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .compile()
+ .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .run(parameters.getRuntime(), MAIN, "foobar")
+ .assertSuccessWithOutputLines("foobar", "field is foobar")
+ .inspect(
+ inspector -> {
+ ClassSubject mainClass = inspector.clazz(MAIN);
+ assertThat(mainClass, isPresent());
+
+ // Check that we have inlined all methods into main method.
+ assertEquals(1, mainClass.allMethods().size());
+
+ // Count the number of check parameter is not null.
+ MethodSubject main = mainClass.mainMethod();
+ long checkParameterIsNotNull = countCall(main, "checkParameterIsNotNull");
+ long checkNotNullParameter = countCall(main, "checkNotNullParameter");
+ if (kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72)) {
+ assertEquals(allowAccessModification ? 0 : 1, checkParameterIsNotNull);
+ assertEquals(0, checkNotNullParameter);
+ } else {
+ assertEquals(allowAccessModification ? 0 : 1, checkNotNullParameter);
+ assertEquals(0, checkParameterIsNotNull);
+ }
+ });
+ }
+}
diff --git a/src/test/kotlinR8TestResources/intrinsics/inlineChainParameterCheck.kt b/src/test/kotlinR8TestResources/intrinsics/inlineChainParameterCheck.kt
new file mode 100644
index 0000000..e77fab7
--- /dev/null
+++ b/src/test/kotlinR8TestResources/intrinsics/inlineChainParameterCheck.kt
@@ -0,0 +1,17 @@
+// Copyright (c) 2021, 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 intrinsics
+
+fun foo(field: String) {
+ println("field is " + field)
+}
+
+fun bar(field : String) {
+ println(field)
+ foo(field)
+}
+
+fun main(args : Array<String>) {
+ bar((if (args.size > 0) args.get(0) else null) as String)
+}
\ No newline at end of file