Merge commit 'd1141c1c27d3e5169a525d97b14cf2f943840555' into dev-release
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 8db4392..e359150 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -1154,6 +1154,10 @@
/** Returns kotlin class info if the class is synthesized by kotlin compiler. */
public abstract KotlinClassLevelInfo getKotlinInfo();
+ public final String getSimpleName() {
+ return getType().getSimpleName();
+ }
+
public final String getTypeName() {
return getType().getTypeName();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index 227270d..63c3261 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -323,7 +323,7 @@
.withConditionalItem(DexMethodHandle::isFieldHandle, DexMethodHandle::asField)
.withConditionalItem(DexMethodHandle::isMethodHandle, DexMethodHandle::asMethod)
.withBool(m -> m.isInterface)
- .withItem(m -> m.rewrittenTarget);
+ .withNullableItem(m -> m.rewrittenTarget);
}
public Handle toAsmHandle() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index e16a15f..202f78c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -83,7 +83,8 @@
// Run the policies on all program classes to produce a final grouping.
List<Policy> policies =
PolicyScheduler.getPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo);
- Collection<MergeGroup> groups = new PolicyExecutor().run(getInitialGroups(), policies, timing);
+ Collection<MergeGroup> groups =
+ new PolicyExecutor().run(getInitialGroups(), policies, executorService, timing);
// If there are no groups, then end horizontal class merging.
if (groups.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
index 067fcd5..d634479 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
@@ -5,6 +5,8 @@
package com.android.tools.r8.horizontalclassmerging;
import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
public abstract class MultiClassPolicyWithPreprocessing<T> extends Policy {
@@ -12,14 +14,15 @@
* Apply the multi class policy to a group of program classes.
*
* @param group This is a group of program classes which can currently still be merged.
- * @param data The result of calling {@link #preprocess(Collection)}.
+ * @param data The result of calling {@link #preprocess(Collection, ExecutorService)}.
* @return The same collection of program classes split into new groups of candidates which can be
* merged. If the policy detects no issues then `group` will be returned unchanged. If classes
* cannot be merged with any other classes they are returned as singleton lists.
*/
public abstract Collection<MergeGroup> apply(MergeGroup group, T data);
- public abstract T preprocess(Collection<MergeGroup> groups);
+ public abstract T preprocess(Collection<MergeGroup> groups, ExecutorService executorService)
+ throws ExecutionException;
@Override
public boolean isMultiClassPolicyWithPreprocessing() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
index c1c4f64..c206e75 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
@@ -9,6 +9,8 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
/**
* This is a simple policy executor that ensures regular sequential execution of policies. It should
@@ -50,9 +52,12 @@
}
private <T> LinkedList<MergeGroup> applyMultiClassPolicyWithPreprocessing(
- MultiClassPolicyWithPreprocessing<T> policy, LinkedList<MergeGroup> groups) {
+ MultiClassPolicyWithPreprocessing<T> policy,
+ LinkedList<MergeGroup> groups,
+ ExecutorService executorService)
+ throws ExecutionException {
// For each group apply the multi class policy and add all the new groups together.
- T data = policy.preprocess(groups);
+ T data = policy.preprocess(groups, executorService);
LinkedList<MergeGroup> newGroups = new LinkedList<>();
groups.forEach(
group -> {
@@ -73,7 +78,11 @@
* class groups.
*/
public Collection<MergeGroup> run(
- Collection<MergeGroup> inputGroups, Collection<Policy> policies, Timing timing) {
+ Collection<MergeGroup> inputGroups,
+ Collection<Policy> policies,
+ ExecutorService executorService,
+ Timing timing)
+ throws ExecutionException {
LinkedList<MergeGroup> linkedGroups;
if (inputGroups instanceof LinkedList) {
@@ -96,7 +105,7 @@
assert policy.isMultiClassPolicyWithPreprocessing();
linkedGroups =
applyMultiClassPolicyWithPreprocessing(
- policy.asMultiClassPolicyWithPreprocessing(), linkedGroups);
+ policy.asMultiClassPolicyWithPreprocessing(), linkedGroups, executorService);
}
timing.end();
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 b536669..fbbe362 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -71,6 +71,7 @@
.addAll(getSingleClassPolicies(appView, mode, runtimeTypeCheckInfo))
.addAll(getMultiClassPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo))
.build();
+ policies = appView.options().testing.horizontalClassMergingPolicyRewriter.apply(policies);
assert verifyPolicyOrderingConstraints(policies);
return policies;
}
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
index f8faefd..e8eb14e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -5,12 +5,13 @@
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.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
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.DexClassAndMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -20,10 +21,11 @@
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.horizontalclassmerging.policies.deadlock.SingleCallerInformation;
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.TraversalContinuation;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
@@ -32,11 +34,14 @@
import java.util.Collections;
import java.util.Deque;
import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
/**
* Disallows merging of classes when the merging could introduce class initialization deadlocks.
@@ -93,37 +98,68 @@
// Mapping from each merge candidate to its merge group.
final Map<DexProgramClass, MergeGroup> allGroups = new IdentityHashMap<>();
+ private SingleCallerInformation singleCallerInformation;
+
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);
-
+ // Partition the merge group into smaller groups that may be merged. If the class initialization
+ // of a parent class may initialize a member of the merge group, then this member is not
+ // eligible for class merging, unless the only way to class initialize this member is from the
+ // class initialization of the parent class. In this case, the member may be merged with other
+ // group members that are also guaranteed to only be class initialized from the class
+ // initialization of the parent class.
+ List<MergeGroup> partitioning = partitionClassesWithPossibleClassInitializerDeadlock(group);
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.
+
+ // Revisit each partition. If the class initialization of a group member may initialize another
+ // class (not necessarily a group member), and vice versa, then class initialization could
+ // deadlock if the group member is merged with another class that is initialized concurrently.
+ for (MergeGroup partition : partitioning) {
+ List<MergeGroup> newGroupsFromPartition = new LinkedList<>();
+ Tracer tracer = new Tracer(partition);
+ for (DexProgramClass clazz : partition) {
+ MergeGroup newGroup = getOrCreateGroupFor(clazz, newGroupsFromPartition, tracer);
+ if (newGroup != null) {
+ newGroup.add(clazz);
+ } else {
+ // Ineligible for merging.
+ }
+ }
+ newGroups.addAll(newGroupsFromPartition);
+ }
+ removeTrivialGroups(newGroups);
+ commit(group, newGroups);
+ return newGroups;
+ }
+
+ private void commit(MergeGroup oldGroup, List<MergeGroup> newGroups) {
+ for (MergeGroup newGroup : newGroups) {
+ for (DexProgramClass member : newGroup) {
+ allGroups.put(member, newGroup);
}
}
- return removeTrivialGroups(newGroups);
+ for (DexProgramClass member : oldGroup) {
+ MergeGroup newGroup = allGroups.get(member);
+ if (newGroup == oldGroup) {
+ allGroups.remove(member);
+ }
+ }
}
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();
+ if (clazz.hasClassInitializer()) {
+ // Trace from the class initializer of this group member. If an execution path is found that
+ // leads back to the class initializer then this class may be involved in a deadlock, and we
+ // should not merge any other classes into it.
tracer.setTracingRoot(clazz);
- tracer.enqueueMethod(classInitializer);
+ tracer.enqueueTracingRoot(clazz.getProgramClassInitializer());
tracer.trace();
if (tracer.hasPossibleClassInitializerDeadlock(clazz)) {
// Ineligible for merging.
@@ -163,11 +199,63 @@
* 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) {
+ private List<MergeGroup> partitionClassesWithPossibleClassInitializerDeadlock(MergeGroup group) {
+ Set<DexProgramClass> superclasses = Sets.newIdentityHashSet();
+ appView
+ .appInfo()
+ .traverseSuperClasses(
+ group.iterator().next(),
+ (supertype, superclass, immediateSubclass) -> {
+ if (superclass != null && superclass.isProgramClass()) {
+ superclasses.add(superclass.asProgramClass());
+ return TraversalContinuation.CONTINUE;
+ }
+ return TraversalContinuation.BREAK;
+ });
+
+ // Run the tracer from the class initializers of the superclasses.
+ Tracer tracer = new Tracer(group);
tracer.setTracingRoots(group);
- tracer.enqueueParentClassInitializers(group);
+ for (DexProgramClass superclass : superclasses) {
+ if (superclass.hasClassInitializer()) {
+ tracer.enqueueTracingRoot(superclass.getProgramClassInitializer());
+ }
+ }
tracer.trace();
- group.removeIf(tracer::hasPossibleClassInitializerDeadlock);
+
+ MergeGroup notInitializedByInitializationOfParent = new MergeGroup();
+ Map<DexProgramClass, MergeGroup> partitioning = new LinkedHashMap<>();
+ for (DexProgramClass member : group) {
+ if (tracer.hasPossibleClassInitializerDeadlock(member)) {
+ DexProgramClass nearestLock = getNearestLock(member, superclasses);
+ if (nearestLock != null) {
+ partitioning.computeIfAbsent(nearestLock, ignoreKey(MergeGroup::new)).add(member);
+ } else {
+ // Ineligible for merging.
+ }
+ } else {
+ notInitializedByInitializationOfParent.add(member);
+ }
+ }
+
+ return ImmutableList.<MergeGroup>builder()
+ .add(notInitializedByInitializationOfParent)
+ .addAll(partitioning.values())
+ .build();
+ }
+
+ private DexProgramClass getNearestLock(
+ DexProgramClass clazz, Set<DexProgramClass> candidateOwners) {
+ ProgramMethodSet seen = ProgramMethodSet.create();
+ ProgramMethod singleCaller = singleCallerInformation.getSingleClassInitializerCaller(clazz);
+ while (singleCaller != null && seen.add(singleCaller)) {
+ if (singleCaller.getDefinition().isClassInitializer()
+ && candidateOwners.contains(singleCaller.getHolder())) {
+ return singleCaller.getHolder();
+ }
+ singleCaller = singleCallerInformation.getSingleCaller(singleCaller);
+ }
+ return null;
}
@Override
@@ -181,12 +269,15 @@
}
@Override
- public Void preprocess(Collection<MergeGroup> groups) {
+ public Void preprocess(Collection<MergeGroup> groups, ExecutorService executorService)
+ throws ExecutionException {
for (MergeGroup group : groups) {
for (DexProgramClass clazz : group) {
allGroups.put(clazz, group);
}
}
+ singleCallerInformation =
+ SingleCallerInformation.builder(appView).analyze(executorService).build();
return null;
}
@@ -198,7 +289,10 @@
private class Tracer {
- final Set<DexProgramClass> group;
+ final MergeGroup group;
+
+ // The members of the existing merge group, for efficient membership querying.
+ final Set<DexProgramClass> groupMembers;
private final Set<DexProgramClass> seenClassInitializers = Sets.newIdentityHashSet();
private final ProgramMethodSet seenMethods = ProgramMethodSet.create();
@@ -214,7 +308,8 @@
private Collection<DexProgramClass> tracingRoots;
Tracer(MergeGroup group) {
- this.group = SetUtils.newIdentityHashSet(group);
+ this.group = group;
+ this.groupMembers = SetUtils.newIdentityHashSet(group);
}
void clearSeen() {
@@ -222,38 +317,30 @@
seenMethods.clear();
}
+ void clearWorklist() {
+ worklist.clear();
+ }
+
boolean markClassInitializerAsSeen(DexProgramClass clazz) {
return seenClassInitializers.add(clazz);
}
boolean enqueueMethod(ProgramMethod method) {
if (seenMethods.add(method)) {
- worklist.add(method);
+ worklist.addLast(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 enqueueTracingRoot(ProgramMethod tracingRoot) {
+ boolean added = seenMethods.add(tracingRoot);
+ assert added;
+ worklist.add(tracingRoot);
}
void recordClassInitializerReachableFromTracingRoots(DexProgramClass clazz) {
- assert group.contains(clazz);
+ assert groupMembers.contains(clazz);
classInitializerReachableFromClasses
.computeIfAbsent(clazz, ignoreKey(Sets::newIdentityHashSet))
.addAll(tracingRoots);
@@ -284,20 +371,25 @@
.contains(classBeingInitialized);
}
+ private void processWorklist() {
+ while (!worklist.isEmpty()) {
+ ProgramMethod method = worklist.removeLast();
+ method.registerCodeReferences(new TracerUseRegistry(method));
+ }
+ }
+
void setTracingRoot(DexProgramClass tracingRoot) {
setTracingRoots(ImmutableList.of(tracingRoot));
}
void setTracingRoots(Collection<DexProgramClass> tracingRoots) {
+ assert verifySeenSetIsEmpty();
+ assert verifyWorklistIsEmpty();
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));
- }
+ processWorklist();
clearSeen();
}
@@ -322,6 +414,7 @@
// Ensures that hasPossibleClassInitializerDeadlock() returns true for each tracing root.
recordTracingRootsIneligibleForClassMerging();
doBreak();
+ clearWorklist();
}
private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexType type) {
@@ -338,8 +431,6 @@
}
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);
}
@@ -350,15 +441,12 @@
}
}
- // 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 (groupMembers.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.
@@ -379,11 +467,19 @@
}
triggerClassInitializer(clazz.getSuperType());
+
+ MergeGroup other = allGroups.get(clazz);
+ if (other != null && other != group) {
+ for (DexProgramClass member : other) {
+ triggerClassInitializer(member);
+ }
+ }
}
@Override
public void registerInitClass(DexType type) {
- triggerClassInitializerIfNotAlreadyTriggeredInContext(type);
+ DexType rewrittenType = appView.graphLens().lookupType(type);
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenType);
}
@Override
@@ -400,7 +496,13 @@
@Override
public void registerInvokeInterface(DexMethod method) {
- fail();
+ DexMethod rewrittenMethod =
+ appView.graphLens().lookupInvokeInterface(method, getContext()).getReference();
+ DexClassAndMethod resolvedMethod =
+ appView.appInfo().resolveMethodOnInterface(rewrittenMethod).getResolutionPair();
+ if (resolvedMethod != null) {
+ fail();
+ }
}
@Override
@@ -432,7 +534,17 @@
@Override
public void registerInvokeVirtual(DexMethod method) {
- fail();
+ DexMethod rewrittenMethod =
+ appView.graphLens().lookupInvokeVirtual(method, getContext()).getReference();
+ DexClassAndMethod resolvedMethod =
+ appView.appInfo().resolveMethodOnClass(rewrittenMethod).getResolutionPair();
+ if (resolvedMethod != null) {
+ if (!resolvedMethod.getHolder().isEffectivelyFinal(appView)) {
+ fail();
+ } else if (resolvedMethod.isProgramMethod()) {
+ enqueueMethod(resolvedMethod.asProgramMethod());
+ }
+ }
}
@Override
@@ -460,9 +572,7 @@
@Override
public void registerCallSite(DexCallSite callSite) {
- LambdaDescriptor descriptor =
- LambdaDescriptor.tryInfer(callSite, appView.appInfo(), getContext());
- if (descriptor != null) {
+ if (isLambdaMetafactoryMethod(callSite, appView.appInfo())) {
// Use of lambda metafactory does not trigger any class initialization.
} else {
fail();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
index 191adc4..e77b31a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
@@ -25,6 +25,7 @@
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
/**
* In the final round, we're not allowed to resolve constructor collisions by appending null
@@ -66,7 +67,7 @@
* lead to constructor collisions.
*/
@Override
- public Set<DexType> preprocess(Collection<MergeGroup> groups) {
+ public Set<DexType> preprocess(Collection<MergeGroup> groups, ExecutorService executorService) {
// Build a mapping from types to groups.
Map<DexType, MergeGroup> groupsByType = new IdentityHashMap<>();
for (MergeGroup group : groups) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
index a2362bd..5ebe1a6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
@@ -36,6 +36,7 @@
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
import java.util.function.Function;
/**
@@ -143,7 +144,8 @@
}
@Override
- public Map<DexType, InterfaceInfo> preprocess(Collection<MergeGroup> groups) {
+ public Map<DexType, InterfaceInfo> preprocess(
+ Collection<MergeGroup> groups, ExecutorService executorService) {
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
Collection<DexProgramClass> classesOfInterest = computeClassesOfInterest(subtypingInfo);
Map<DexType, DexMethodSignatureSet> inheritedClassMethodsPerClass =
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
index c53395c..f86a398 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
@@ -27,6 +27,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
import java.util.function.Function;
/**
@@ -160,7 +161,7 @@
}
@Override
- public SubtypingInfo preprocess(Collection<MergeGroup> groups) {
+ public SubtypingInfo preprocess(Collection<MergeGroup> groups, ExecutorService executorService) {
return new SubtypingInfo(appView);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
new file mode 100644
index 0000000..0e35bf3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
@@ -0,0 +1,262 @@
+// 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.deadlock;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+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.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Stores the single caller (if any) for each non-virtual method. Virtual methods are not considered
+ * since computing single caller information for such methods is expensive (it involves computing
+ * the possible dispatch targets for each virtual invoke).
+ *
+ * <p>Unlike the {@link com.android.tools.r8.ir.conversion.CallGraph} that is used to determine if a
+ * method can be single caller inlined, this considers a method that is called from multiple call
+ * sites in the same method to have a single caller.
+ */
+// TODO(b/205611444): account for -keep rules.
+public class SingleCallerInformation {
+
+ private final ProgramMethodMap<ProgramMethod> singleCallers;
+ private final Map<DexProgramClass, ProgramMethod> singleClinitCallers;
+
+ SingleCallerInformation(
+ ProgramMethodMap<ProgramMethod> singleCallers,
+ Map<DexProgramClass, ProgramMethod> singleClinitCallers) {
+ this.singleCallers = singleCallers;
+ this.singleClinitCallers = singleClinitCallers;
+ }
+
+ public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return new Builder(appView);
+ }
+
+ public ProgramMethod getSingleCaller(ProgramMethod method) {
+ return singleCallers.get(method);
+ }
+
+ public ProgramMethod getSingleClassInitializerCaller(DexProgramClass clazz) {
+ return singleClinitCallers.get(clazz);
+ }
+
+ public static class Builder {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+ // The single callers for each method and class initializer.
+ // If a method is not in the map, then a call to that method has never been seen.
+ // If a method is mapped to Optional.empty(), then the method has multiple calling contexts.
+ // If a method is mapped to Optional.of(m), then the method is only called from method m.
+ final ProgramMethodMap<Optional<ProgramMethod>> callers = ProgramMethodMap.createConcurrent();
+ final Map<DexProgramClass, Optional<ProgramMethod>> clinitCallers = new ConcurrentHashMap<>();
+
+ Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ this.appView = appView;
+ }
+
+ public Builder analyze(ExecutorService executorService) throws ExecutionException {
+ ThreadUtils.processItems(
+ appView.appInfo()::forEachMethod, this::processMethod, executorService);
+ return this;
+ }
+
+ public SingleCallerInformation build() {
+ ProgramMethodMap<ProgramMethod> singleCallers = ProgramMethodMap.create();
+ callers.forEach(
+ (method, callers) -> callers.ifPresent(caller -> singleCallers.put(method, caller)));
+ Map<DexProgramClass, ProgramMethod> singleClinitCallers = new IdentityHashMap<>();
+ clinitCallers.forEach(
+ (clazz, callers) -> callers.ifPresent(caller -> singleClinitCallers.put(clazz, caller)));
+ return new SingleCallerInformation(singleCallers, singleClinitCallers);
+ }
+
+ private void processMethod(ProgramMethod method) {
+ method.registerCodeReferences(new InvokeExtractor(appView, method));
+ }
+
+ private class InvokeExtractor extends UseRegistry<ProgramMethod> {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+ InvokeExtractor(AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
+ super(appView, context);
+ this.appView = appView;
+ }
+
+ private void recordDispatchTarget(ProgramMethod target) {
+ callers.compute(
+ target,
+ (key, value) -> {
+ if (value == null) {
+ // This target is now called from the current context (only).
+ return Optional.of(getContext());
+ }
+ // If the target is only called from the current context, then that is still the
+ // case.
+ if (value.orElse(null) == getContext()) {
+ return value;
+ }
+ // The target is now called from more than one place.
+ return Optional.empty();
+ });
+ }
+
+ 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) {
+ return appView.appInfo().isSubtype(getContext().getHolder(), clazz);
+ }
+
+ private void triggerClassInitializer(DexType type) {
+ DexProgramClass clazz = type.asProgramClass(appView);
+ if (clazz != null) {
+ triggerClassInitializer(clazz);
+ }
+ }
+
+ private void triggerClassInitializer(DexProgramClass clazz) {
+ Optional<ProgramMethod> callers = clinitCallers.get(clazz);
+ if (callers != null) {
+ if (!callers.isPresent()) {
+ // Optional.empty() represents that this class initializer has multiple (unknown)
+ // callers. Since this <clinit> and all of the parent <clinit>s are already triggered
+ // from multiple places, there is no need to record it is also triggered from the
+ // current context.
+ return;
+ }
+ if (callers.get() == getContext()) {
+ // This <clinit> is already triggered from the current context. No need to record this
+ // again.
+ return;
+ }
+ }
+
+ // Record that the given class is now initialized from the current context.
+ clinitCallers.compute(
+ clazz,
+ (key, value) -> {
+ if (value == null) {
+ // This <clinit> was not triggered before.
+ return Optional.of(getContext());
+ }
+ // This <clinit> was triggered from another context than the current.
+ assert value.orElse(null) != getContext();
+ return Optional.empty();
+ });
+
+ // Repeat for the parent classes.
+ triggerClassInitializer(clazz.getSuperType());
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {
+ DexType rewrittenType = appView.graphLens().lookupType(type);
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenType);
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ DexMethod rewrittenMethod =
+ appView.graphLens().lookupInvokeDirect(method, getContext()).getReference();
+ DexProgramClass holder = rewrittenMethod.getHolderType().asProgramClass(appView);
+ ProgramMethod target = rewrittenMethod.lookupOnProgramClass(holder);
+ if (target != null) {
+ recordDispatchTarget(target);
+ }
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ // Intentionally empty, as we don't aim to collect single caller information for virtual
+ // methods.
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ DexMethod rewrittenMethod =
+ appView.graphLens().lookupInvokeDirect(method, getContext()).getReference();
+ ProgramMethod target =
+ appView
+ .appInfo()
+ .unsafeResolveMethodDueToDexFormat(rewrittenMethod)
+ .getResolvedProgramMethod();
+ if (target != null) {
+ recordDispatchTarget(target);
+ triggerClassInitializerIfNotAlreadyTriggeredInContext(target.getHolder());
+ }
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ // Intentionally empty, as we don't aim to collect single caller information for virtual
+ // methods.
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ // Intentionally empty, as we don't aim to collect single caller information for virtual
+ // methods.
+ }
+
+ @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.
+ }
+ }
+ }
+}
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 40c2ce3..674493f 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
@@ -59,7 +59,6 @@
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
-import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.utils.InternalOptions;
@@ -71,7 +70,6 @@
import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -86,7 +84,6 @@
protected final AppView<AppInfoWithLiveness> appView;
private final IRConverter converter;
- private final Set<DexMethod> extraNeverInlineMethods;
private final LensCodeRewriter lensCodeRewriter;
final MainDexInfo mainDexInfo;
@@ -107,17 +104,8 @@
AppView<AppInfoWithLiveness> appView,
IRConverter converter,
LensCodeRewriter lensCodeRewriter) {
- Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
this.appView = appView;
this.converter = converter;
- this.extraNeverInlineMethods =
- appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
- ? ImmutableSet.of()
- : ImmutableSet.of(
- intrinsics.throwNpe,
- intrinsics.throwParameterIsNullException,
- intrinsics.throwParameterIsNullNPE,
- intrinsics.throwParameterIsNullIAE);
this.lensCodeRewriter = lensCodeRewriter;
this.mainDexInfo = appView.appInfo().getMainDexInfo();
this.singleInlineCallers =
@@ -140,12 +128,6 @@
return true;
}
- if (extraNeverInlineMethods.contains(
- appView.graphLens().getOriginalMethodSignature(singleTargetReference))) {
- whyAreYouNotInliningReporter.reportExtraNeverInline();
- return true;
- }
-
if (appInfo.isNeverInlineMethod(singleTargetReference)) {
whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 218a8fc..cc7d4ec 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -22,9 +22,6 @@
}
@Override
- public void reportExtraNeverInline() {}
-
- @Override
public void reportCallerNotSameClass() {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 8063674..66d6108 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -45,8 +45,6 @@
}
}
- public abstract void reportExtraNeverInline();
-
public abstract void reportCallerNotSameClass();
public abstract void reportCallerNotSameNest();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index f9418b4..94632b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -48,11 +48,6 @@
}
@Override
- public void reportExtraNeverInline() {
- print("method is marked as an additional never inline method.");
- }
-
- @Override
public void reportCallerNotSameClass() {
print("inlinee can only be inlined into methods in the same class.");
}
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 c012686..fca35a8 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.Policy;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
@@ -1372,7 +1373,7 @@
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
// TODO(b/205611444): Enable by default.
- private boolean enableClassInitializerDeadlockDetection = false;
+ private boolean enableClassInitializerDeadlockDetection = true;
private boolean enableInterfaceMerging =
System.getProperty("com.android.tools.r8.disableHorizontalInterfaceMerging") == null;
private boolean enableInterfaceMergingInInitial = false;
@@ -1592,6 +1593,8 @@
public BiConsumer<DexItemFactory, HorizontallyMergedClasses> horizontallyMergedClassesConsumer =
ConsumerUtils.emptyBiConsumer();
+ public Function<List<Policy>, List<Policy>> horizontalClassMergingPolicyRewriter =
+ Function.identity();
public TriFunction<AppView<?>, Iterable<DexProgramClass>, DexProgramClass, DexProgramClass>
horizontalClassMergingTarget = (appView, candidates, target) -> target;
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
index 3a648fe..9c1c5af 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
@@ -8,6 +8,7 @@
import com.google.common.base.Equivalence.Wrapper;
import java.util.Map;
import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -28,6 +29,10 @@
backing.clear();
}
+ public V compute(K member, BiFunction<K, V, V> fn) {
+ return backing.compute(wrap(member), (key, value) -> fn.apply(member, value));
+ }
+
public V computeIfAbsent(K member, Function<K, V> fn) {
return backing.computeIfAbsent(wrap(member), key -> fn.apply(key.get()));
}
@@ -44,6 +49,10 @@
return backing.get(wrap(member));
}
+ public V getOrDefault(K member, V defaultValue) {
+ return backing.getOrDefault(wrap(member), defaultValue);
+ }
+
public boolean isEmpty() {
return backing.isEmpty();
}
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
index cb33702..145a48c 100644
--- a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -32,4 +32,14 @@
matcher);
return self();
}
+
+ public Dex2OatTestRunResult assertSoftVerificationErrors() {
+ assertSuccess();
+ Matcher<? super String> matcher = CoreMatchers.containsString("Soft verification failures");
+ assertThat(
+ errorMessage("Run dex2oat did not produce soft verification errors.", matcher.toString()),
+ getStdErr(),
+ matcher);
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 033e512..52b4ac9 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1924,6 +1924,8 @@
command.add(getDex2OatPath(vm).toString());
command.add("--android-root=" + getProductPath(vm) + "/system");
command.add("--runtime-arg");
+ command.add("-verbose:verifier");
+ command.add("--runtime-arg");
command.add("-Xnorelocate");
command.add("--dex-file=" + file.toAbsolutePath());
command.add("--oat-file=" + outFile.toAbsolutePath());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingMultipleGroupsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingMultipleGroupsTest.java
new file mode 100644
index 0000000..3d07644
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingMultipleGroupsTest.java
@@ -0,0 +1,143 @@
+// 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.classmerging.horizontal;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.horizontalclassmerging.Policy;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClinitDeadlockAfterMergingMultipleGroupsTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepClassAndMembersRules(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertClassesNotMerged(A1.class, A2.class)
+ .assertIsCompleteMergeGroup(B1.class, B2.class)
+ .assertIsCompleteMergeGroup(C1.class, C2.class)
+ .assertIsCompleteMergeGroup(D1.class, D2.class)
+ .assertNoOtherClassesMerged())
+ .addOptionsModification(
+ options -> {
+ options.horizontalClassMergerOptions().setEnableClassInitializerDeadlockDetection();
+ options.testing.horizontalClassMergingPolicyRewriter =
+ policies ->
+ ImmutableList.<Policy>builder()
+ .add(getPolicyForTesting())
+ .addAll(policies)
+ .build();
+ })
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ }
+
+ // A custom policy for splitting the merge group {A1, A2, B1, B2, C1, C2, D1, D2} into {A1, A2},
+ // {B1, B2}, {C1, C2}, {D1, D2}.
+ private Policy getPolicyForTesting() {
+ return new MultiClassSameReferencePolicy<String>() {
+
+ @Override
+ public String getMergeKey(DexProgramClass clazz) {
+ String simpleName = clazz.getSimpleName();
+ String simpleNameExcludingIndex = simpleName.substring(0, simpleName.length() - 1);
+ return simpleNameExcludingIndex;
+ }
+
+ @Override
+ public String getName() {
+ return ClinitDeadlockAfterMergingMultipleGroupsTest.class.getTypeName();
+ }
+ };
+ }
+
+ static class Main {
+
+ // @Keep
+ public static void thread0() {
+ // Will take the followings locks in the specified order: A1, B1.
+ System.out.println(A1.b1);
+ }
+
+ // @Keep
+ public static void thread1() {
+ // Will take the following locks in the specified order: B2, C2.
+ System.out.println(B2.c2);
+ }
+
+ // @Keep
+ public static void thread2() {
+ // Will take the following locks in the specified order: C1, D1.
+ System.out.println(C1.d1);
+ }
+
+ // @Keep
+ public static void thread3() {
+ // Will take the following locks in the specified order: D2, A2.
+ System.out.println(D2.a2);
+ }
+
+ // @Keep
+ public static void thread4() {
+ System.out.println(new A1());
+ System.out.println(new A2());
+ System.out.println(new B1());
+ System.out.println(new B2());
+ System.out.println(new C1());
+ System.out.println(new C2());
+ System.out.println(new D1());
+ System.out.println(new D2());
+ }
+ }
+
+ static class A1 {
+
+ static B1 b1 = new B1();
+ }
+
+ static class A2 {}
+
+ static class B1 {}
+
+ static class B2 {
+
+ static C2 c2 = new C2();
+ }
+
+ static class C1 {
+
+ static D1 d1 = new D1();
+ }
+
+ static class C2 {}
+
+ static class D1 {}
+
+ static class D2 {
+
+ static A2 a2 = new A2();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest.java
index 2ea472e..f7ca4c1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.classmerging.horizontal.ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest.Host.Companion.HostA;
import com.android.tools.r8.classmerging.horizontal.ClinitDeadlockAfterMergingSingletonClassesInstantiatedByCompanionTest.Host.Companion.HostB;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
@@ -43,9 +42,12 @@
" public static void thread0();",
" public static void thread" + thread + "();",
"}")
- // TODO(b/205611444): HostA and HostB should be merged when thread is 1.
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector ->
+ inspector
+ .applyIf(
+ thread == 1, i -> i.assertIsCompleteMergeGroup(HostA.class, HostB.class))
+ .assertNoOtherClassesMerged())
.addOptionsModification(
options ->
options.horizontalClassMergerOptions().setEnableClassInitializerDeadlockDetection())
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 4aaea25..2d2611f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -259,9 +259,11 @@
String kotlinIntrinsics = "void kotlin.jvm.internal.Intrinsics";
assertEquals(
Lists.newArrayList(
- kotlinc.is(KOTLINC_1_3_72)
- ? kotlinIntrinsics + ".throwParameterIsNullException(java.lang.String)"
- : kotlinIntrinsics + ".throwParameterIsNullNPE(java.lang.String)"),
+ kotlinIntrinsics
+ + (kotlinc.is(KOTLINC_1_3_72)
+ ? ".checkParameterIsNotNull"
+ : ".checkNotNullParameter")
+ + "(java.lang.Object, 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
index 802be75..da2f568 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
@@ -78,10 +78,10 @@
long checkParameterIsNotNull = countCall(main, "checkParameterIsNotNull");
long checkNotNullParameter = countCall(main, "checkNotNullParameter");
if (kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72)) {
- assertEquals(allowAccessModification ? 0 : 1, checkParameterIsNotNull);
+ assertEquals(1, checkParameterIsNotNull);
assertEquals(0, checkNotNullParameter);
} else {
- assertEquals(allowAccessModification ? 0 : 1, checkNotNullParameter);
+ assertEquals(1, checkNotNullParameter);
assertEquals(0, checkParameterIsNotNull);
}
});
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
index c407a5b..a03ebb2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -76,9 +76,7 @@
MethodSubject isSupported = main.uniqueMethodWithName("isSupported");
assertThat(isSupported, isPresent());
assertEquals(
- !kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) || allowAccessModification
- ? 0
- : 1,
+ kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) ? 1 : 0,
countCall(isSupported, "checkParameterIsNotNull"));
// In general cases, null check won't be invoked only once or twice, hence no subtle
@@ -92,9 +90,7 @@
@Test
public void b139432507_isSupported() throws Exception {
assumeTrue("Different inlining behavior on CF backend", parameters.isDexRuntime());
- testSingle(
- "isSupported",
- kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) && !allowAccessModification);
+ testSingle("isSupported", kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index 142e48d..d8a7e1c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -62,15 +62,25 @@
"throwParameterIsNullException",
"void",
Collections.singletonList("java.lang.String")),
- // throwParameterIsNullException is not added for test starting from 1.4
- kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72))
+ false)
+ .put(
+ new MethodSignature(
+ "throwParameterIsNullNPE",
+ "void",
+ Collections.singletonList("java.lang.String")),
+ false)
.put(
new MethodSignature(
"checkParameterIsNotNull",
"void",
Lists.newArrayList("java.lang.Object", "java.lang.String")),
- kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72)
- && !allowAccessModification)
+ kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72))
+ .put(
+ new MethodSignature(
+ "checkNotNullParameter",
+ "void",
+ Lists.newArrayList("java.lang.Object", "java.lang.String")),
+ !kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72))
.build());
});
}
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index e6f1c6d..63cacfc 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -118,7 +118,7 @@
boolean metKotlinIntrinsicsNullChecks = false;
while (it.hasNext()) {
DexMethod invokedMethod = it.next().getMethod();
- if (invokedMethod.holder.toSourceString().contains("java.net")) {
+ if (invokedMethod.holder.getTypeName().contains("java.net")) {
continue;
}
ClassSubject invokedMethodHolderSubject =
@@ -165,7 +165,11 @@
"-neverinline class **." + targetClassName + " { <methods>; }",
"-keepconstantarguments class kotlin.jvm.internal.Intrinsics {",
" *** checkParameterIsNotNull(...);",
+ "}",
+ "-neversinglecallerinline class kotlin.jvm.internal.Intrinsics {",
+ " *** checkParameterIsNotNull(...);",
"}"))
+ .addNeverSingleCallerInlineAnnotations()
.allowDiagnosticWarningMessages()
.minification(minification)
.compile()
diff --git a/src/test/java/com/android/tools/r8/softverification/FoundClass.java b/src/test/java/com/android/tools/r8/softverification/FoundClass.java
new file mode 100644
index 0000000..cc25389
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/FoundClass.java
@@ -0,0 +1,20 @@
+// 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.softverification;
+
+public class FoundClass {
+
+ public static int staticField = 42;
+
+ public int instanceField = 42;
+
+ public static void staticMethod() {
+ System.out.println("FoundClass::staticMethod");
+ }
+
+ public void instanceMethod() {
+ System.out.println("FoundClass::instanceMethod");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/MissingClass.java b/src/test/java/com/android/tools/r8/softverification/MissingClass.java
new file mode 100644
index 0000000..d653af7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/MissingClass.java
@@ -0,0 +1,20 @@
+// 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.softverification;
+
+public class MissingClass {
+
+ public static int staticField = 42;
+
+ public int instanceField = 42;
+
+ public static void staticMethod() {
+ System.out.println("MissingClass::staticMethod");
+ }
+
+ public void instanceMethod() {
+ System.out.println("MissingClass::instanceMethod");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/MissingMember.java b/src/test/java/com/android/tools/r8/softverification/MissingMember.java
new file mode 100644
index 0000000..9fee087
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/MissingMember.java
@@ -0,0 +1,20 @@
+// 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.softverification;
+
+public class MissingMember {
+
+ public static int staticField = 42;
+
+ public int instanceField = 42;
+
+ public static void staticMethod() {
+ System.out.println("MissingMember::staticMethod");
+ }
+
+ public void instanceMethod() {
+ System.out.println("MissingMember::instanceMethod");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestCheckCast.java b/src/test/java/com/android/tools/r8/softverification/TestCheckCast.java
new file mode 100644
index 0000000..bee31ea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestCheckCast.java
@@ -0,0 +1,26 @@
+// 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.softverification;
+
+public class TestCheckCast {
+
+ public static Object getObject() {
+ return new Object();
+ }
+
+ public static String run() {
+ if (System.currentTimeMillis() == 0) {
+ MissingClass foo = (MissingClass) getObject();
+ }
+ if (System.currentTimeMillis() == 0) {
+ MissingClass foo = (MissingClass) getObject();
+ }
+ String currentString = "foobar";
+ for (int i = 0; i < 10; i++) {
+ currentString = "foobar" + (i + currentString.length());
+ }
+ return currentString;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestInstanceField.java b/src/test/java/com/android/tools/r8/softverification/TestInstanceField.java
new file mode 100644
index 0000000..e5cf5f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestInstanceField.java
@@ -0,0 +1,26 @@
+// 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.softverification;
+
+public class TestInstanceField {
+
+ public static String run() {
+ return run(null);
+ }
+
+ public static String run(MissingClass missingClass) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(missingClass.instanceField);
+ }
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(missingClass.instanceField);
+ }
+ String currentString = "foobar";
+ for (int i = 0; i < 10; i++) {
+ currentString = "foobar" + (i + currentString.length());
+ }
+ return currentString;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestInstanceMethod.java b/src/test/java/com/android/tools/r8/softverification/TestInstanceMethod.java
new file mode 100644
index 0000000..2a8ee20
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestInstanceMethod.java
@@ -0,0 +1,26 @@
+// 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.softverification;
+
+public class TestInstanceMethod {
+
+ public static String run() {
+ return run(null);
+ }
+
+ public static String run(MissingClass missingClass) {
+ if (System.currentTimeMillis() == 0) {
+ missingClass.instanceMethod();
+ }
+ if (System.currentTimeMillis() == 0) {
+ missingClass.instanceMethod();
+ }
+ String currentString = "foobar";
+ for (int i = 0; i < 10; i++) {
+ currentString = "foobar" + (i + currentString.length());
+ }
+ return currentString;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestInstanceOf.java b/src/test/java/com/android/tools/r8/softverification/TestInstanceOf.java
new file mode 100644
index 0000000..ee0c2db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestInstanceOf.java
@@ -0,0 +1,30 @@
+// 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.softverification;
+
+public class TestInstanceOf {
+
+ public static Object getObject() {
+ return new Object();
+ }
+
+ public static String run() {
+ if (System.currentTimeMillis() == 0) {
+ if (getObject() instanceof MissingClass) {
+ throw new RuntimeException("Foo");
+ }
+ }
+ if (System.currentTimeMillis() == 0) {
+ if (getObject() instanceof MissingClass) {
+ throw new RuntimeException("Foo");
+ }
+ }
+ String currentString = "foobar";
+ for (int i = 0; i < 10; i++) {
+ currentString = "foobar" + (i + currentString.length());
+ }
+ return currentString;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestNewInstance.java b/src/test/java/com/android/tools/r8/softverification/TestNewInstance.java
new file mode 100644
index 0000000..7b67609
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestNewInstance.java
@@ -0,0 +1,22 @@
+// 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.softverification;
+
+public class TestNewInstance {
+
+ public static String run() {
+ if (System.currentTimeMillis() == 0) {
+ new MissingClass();
+ }
+ if (System.currentTimeMillis() == 0) {
+ new MissingClass();
+ }
+ String currentString = "foobar";
+ for (int i = 0; i < 10; i++) {
+ currentString = "foobar" + (i + currentString.length());
+ }
+ return currentString;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestRunner.java b/src/test/java/com/android/tools/r8/softverification/TestRunner.java
new file mode 100644
index 0000000..6baef2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestRunner.java
@@ -0,0 +1,59 @@
+// 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.softverification;
+
+public class TestRunner {
+
+ public static class Measure {
+ private long start;
+ private String description;
+
+ public Measure() {}
+
+ public void start(String description) {
+ start = System.currentTimeMillis();
+ this.description = description;
+ }
+
+ public String stop() {
+ long end = System.currentTimeMillis();
+ return "Time for " + description + " took: " + (end - start) + "\n";
+ }
+ }
+
+ public static String run() {
+ StringBuilder sb = new StringBuilder();
+ Measure measure = new Measure();
+ measure.start("InstanceSourceObject");
+ TestInstanceOf.run();
+ sb.append(measure.stop());
+ measure.start("CheckCastSourceObject");
+ TestCheckCast.run();
+ sb.append(measure.stop());
+ measure.start("TypeReference");
+ TestTypeReference.run();
+ sb.append(measure.stop());
+ measure.start("NewInstance");
+ TestNewInstance.run();
+ sb.append(measure.stop());
+ measure.start("StaticField");
+ TestStaticField.run();
+ sb.append(measure.stop());
+ measure.start("StaticMethod");
+ TestStaticMethod.run();
+ sb.append(measure.stop());
+ measure.start("InstanceField");
+ TestInstanceField.run();
+ sb.append(measure.stop());
+ measure.start("InstanceMethod");
+ TestInstanceMethod.run();
+ sb.append(measure.stop());
+ return sb.toString();
+ }
+
+ public static void main(String[] args) {
+ System.out.println(run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestRunnerBuilder.java b/src/test/java/com/android/tools/r8/softverification/TestRunnerBuilder.java
new file mode 100644
index 0000000..7a19f09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestRunnerBuilder.java
@@ -0,0 +1,192 @@
+// 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.softverification;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.Dex2OatTestRunResult;
+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.softverification.TestRunner.Measure;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This runner produces a benchmark jar that can be used to investigate the time penalty that soft-
+ * verification errors have on the runtime of an app. It will build a set of different tests:
+ *
+ * <pre>
+ * - CheckCast
+ * - InstanceOf
+ * - TypeReference
+ * - NewInstance
+ * - StaticField
+ * - StaticMethod
+ * - InstanceField
+ * - InstanceMethod
+ * </pre>
+ *
+ * where for each test, there is a reference to either a missing class, existing class with missing
+ * members or a full class with all definitions. Each test column can be run by invoking the
+ * corresponding test runner:
+ *
+ * <p>TestRunner_MissingClass, TestRunner_MissingMember, TestRunner_FoundClass
+ *
+ * <p>To test in a setting with a studio project, modify ANDROID_STUDIO_LIB_PATH to point to an
+ * android project. A reference android project can be found at:
+ * /google/data/ro/teams/r8/deps/DexVerificationSample.tar.gz
+ */
+@RunWith(Parameterized.class)
+public class TestRunnerBuilder extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntime(Version.V9_0_0)
+ .withApiLevel(AndroidApiLevel.M)
+ .build();
+ }
+
+ private static final Path ANDROID_STUDIO_LIB_PATH = Paths.get("PATH_TO_PROJECT/libs/library.jar");
+
+ private static final int COUNT = 1100;
+
+ private static final Set<String> testClasses =
+ ImmutableSet.of(
+ binaryName(TestCheckCast.class),
+ binaryName(TestInstanceOf.class),
+ binaryName(TestTypeReference.class),
+ binaryName(TestNewInstance.class),
+ binaryName(TestStaticField.class),
+ binaryName(TestStaticMethod.class),
+ binaryName(TestInstanceField.class),
+ binaryName(TestInstanceMethod.class));
+
+ private static void buildJar(Path path) throws Exception {
+ ZipBuilder builder = ZipBuilder.builder(path);
+ builder.addFilesRelative(
+ ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(Measure.class));
+ for (Class<?> clazz :
+ ImmutableList.of(MissingClass.class, MissingMember.class, FoundClass.class)) {
+ String postFix = clazz.getSimpleName();
+ int classCounter = 0;
+ for (int i = 0; i < COUNT; i++) {
+ addClass(builder, TestCheckCast.class, clazz, postFix, i, classCounter++);
+ addClass(builder, TestInstanceOf.class, clazz, postFix, i, classCounter++);
+ addClass(builder, TestTypeReference.class, clazz, postFix, i, classCounter++);
+ addClass(builder, TestNewInstance.class, clazz, postFix, i, classCounter++);
+ addClass(builder, TestStaticField.class, clazz, postFix, i, classCounter++);
+ addClass(builder, TestStaticMethod.class, clazz, postFix, i, classCounter++);
+ addClass(builder, TestInstanceField.class, clazz, postFix, i, classCounter++);
+ addClass(builder, TestInstanceMethod.class, clazz, postFix, i, classCounter++);
+ }
+ if (clazz != MissingClass.class) {
+ for (int i = 0; i < classCounter; i++) {
+ String binaryName = binaryName(clazz) + "_" + i;
+ ClassFileTransformer transformer =
+ transformer(clazz).setClassDescriptor(getDescriptorFromClassBinaryName(binaryName));
+ if (clazz == MissingMember.class) {
+ transformer.removeMethods(MethodPredicate.all()).removeFields(FieldPredicate.all());
+ }
+ builder.addBytes(binaryName + ".class", transformer.transform());
+ }
+ }
+ String runnerClass = binaryName(TestRunner.class) + "_" + postFix;
+ builder.addBytes(
+ runnerClass + ".class",
+ transformer(TestRunner.class)
+ .setClassDescriptor(getDescriptorFromClassBinaryName(runnerClass))
+ .addMethodTransformer(
+ new MethodTransformer() {
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ super.visitMaxs(-1, maxLocals);
+ }
+
+ @Override
+ public void visitMethodInsn(
+ int opcode,
+ String owner,
+ String name,
+ String descriptor,
+ boolean isInterface) {
+ if (!testClasses.contains(owner)) {
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ return;
+ }
+ for (int i = 0; i < COUNT; i++) {
+ super.visitMethodInsn(
+ opcode, owner + "_" + postFix + "_" + i, name, descriptor, isInterface);
+ }
+ }
+ })
+ .transform());
+ }
+ builder.build();
+ }
+
+ private static void addClass(
+ ZipBuilder builder,
+ Class<?> clazz,
+ Class<?> classReference,
+ String postFix,
+ int index,
+ int referenceIndex)
+ throws IOException {
+ String binaryName = binaryName(clazz) + "_" + postFix + "_" + index;
+ String referenceBinaryName = binaryName(classReference) + "_" + referenceIndex;
+ builder.addBytes(
+ binaryName + ".class",
+ transformer(clazz)
+ .setClassDescriptor(getDescriptorFromClassBinaryName(binaryName))
+ .replaceClassDescriptorInMembers(
+ descriptor(MissingClass.class),
+ getDescriptorFromClassBinaryName(referenceBinaryName))
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MissingClass.class),
+ getDescriptorFromClassBinaryName(referenceBinaryName))
+ .transform());
+ }
+
+ @Test
+ public void buildTest() throws Exception {
+ Path benchmarkJar = temp.newFile("library.jar").toPath();
+ buildJar(benchmarkJar);
+ D8TestCompileResult compileResult =
+ testForD8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramFiles(benchmarkJar)
+ .compile();
+ Dex2OatTestRunResult dex2OatTestRunResult = compileResult.runDex2Oat(parameters.getRuntime());
+ dex2OatTestRunResult.assertSoftVerificationErrors();
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Building jar and placing in " + ANDROID_STUDIO_LIB_PATH);
+ buildJar(ANDROID_STUDIO_LIB_PATH);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestStaticField.java b/src/test/java/com/android/tools/r8/softverification/TestStaticField.java
new file mode 100644
index 0000000..3d0b711
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestStaticField.java
@@ -0,0 +1,22 @@
+// 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.softverification;
+
+public class TestStaticField {
+
+ public static String run() {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(MissingClass.staticField);
+ }
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(MissingClass.staticField);
+ }
+ String currentString = "foobar";
+ for (int i = 0; i < 10; i++) {
+ currentString = "foobar" + (i + currentString.length());
+ }
+ return currentString;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestStaticMethod.java b/src/test/java/com/android/tools/r8/softverification/TestStaticMethod.java
new file mode 100644
index 0000000..8e6fe5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestStaticMethod.java
@@ -0,0 +1,22 @@
+// 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.softverification;
+
+public class TestStaticMethod {
+
+ public static String run() {
+ if (System.currentTimeMillis() == 0) {
+ MissingClass.staticMethod();
+ }
+ if (System.currentTimeMillis() == 0) {
+ MissingClass.staticMethod();
+ }
+ String currentString = "foobar";
+ for (int i = 0; i < 10; i++) {
+ currentString = "foobar" + (i + currentString.length());
+ }
+ return currentString;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/softverification/TestTypeReference.java b/src/test/java/com/android/tools/r8/softverification/TestTypeReference.java
new file mode 100644
index 0000000..0d064f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/softverification/TestTypeReference.java
@@ -0,0 +1,25 @@
+// 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.softverification;
+
+public class TestTypeReference {
+
+ public static String run() {
+ return run(null);
+ }
+
+ public static String test(MissingClass missingClass) {
+ return missingClass == null ? "nobar" : null;
+ }
+
+ public static String run(MissingClass missingClass) {
+ String currentString = missingClass == null ? "foobar" : test(missingClass);
+ currentString = missingClass == null ? currentString : test(missingClass);
+ for (int i = 0; i < 10; i++) {
+ currentString = "foobar" + (i + currentString.length());
+ }
+ return currentString;
+ }
+}
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 5e37647..296004d 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -524,6 +524,10 @@
public interface MethodPredicate {
boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
+ static MethodPredicate all() {
+ return (access, name, descriptor, signature, exceptions) -> true;
+ }
+
static MethodPredicate onName(String name) {
return (access, otherName, descriptor, signature, exceptions) -> name.equals(otherName);
}
@@ -543,6 +547,10 @@
public interface FieldPredicate {
boolean test(int access, String name, String descriptor, String signature, Object value);
+ static FieldPredicate all() {
+ return (access, name, descriptor, signature, value) -> true;
+ }
+
static FieldPredicate onNameAndSignature(String name, String descriptor) {
return (access, otherName, otherDescriptor, signature, value) ->
name.equals(otherName) && descriptor.equals(otherDescriptor);
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 2572610..733e59b 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -20,6 +20,7 @@
import archive
import git_utils
+import jdk
import optparse
import os
import re
@@ -89,6 +90,13 @@
'https://github.com/'
+ github_account + '/' + LIBRARY_NAME, checkout_dir)
+def GetJavaEnv():
+ java_env = dict(os.environ, JAVA_HOME = jdk.GetJdk11Home())
+ java_env['PATH'] = java_env['PATH'] + os.pathsep + os.path.join(jdk.GetJdk11Home(), 'bin')
+ java_env['GRADLE_OPTS'] = '-Xmx1g'
+ return java_env
+
+
def BuildDesugaredLibrary(checkout_dir, variant):
if (variant != 'jdk8' and variant != 'jdk11'):
raise Exception('Variant ' + variant + 'is not supported')
@@ -98,15 +106,12 @@
bazel,
'--bazelrc=/dev/null',
'build',
- 'maven_release' + ('_jdk11' if variant == 'jdk11' else ''),
- '--java_language_version=' + ('11' if variant == 'jdk11' else '8')]
- if variant == 'jdk11':
- cmd.append('--java_runtime_version=remotejdk_11')
+ 'maven_release' + ('_jdk11' if variant == 'jdk11' else '')]
utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ subprocess.check_call(cmd, env=GetJavaEnv())
cmd = [bazel, 'shutdown']
utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ subprocess.check_call(cmd, env=GetJavaEnv())
# Locate the library jar and the maven zip with the jar from the
# bazel build.
diff --git a/tools/jdk.py b/tools/jdk.py
index 7138fda..b3af7a6 100755
--- a/tools/jdk.py
+++ b/tools/jdk.py
@@ -20,6 +20,17 @@
else:
return os.environ['JAVA_HOME']
+def GetJdk11Home():
+ root = os.path.join(JDK_DIR, 'jdk-11')
+ if defines.IsLinux():
+ return os.path.join(root, 'linux')
+ elif defines.IsOsX():
+ return os.path.join(root, 'osx')
+ elif defines.IsWindows():
+ return os.path.join(root, 'windows')
+ else:
+ return os.environ['JAVA_HOME']
+
def GetJdk8Home():
root = os.path.join(JDK_DIR, 'jdk8')
if defines.IsLinux():