Merge commit '99d0625e6e21db571fe2b9482e0b89d21724c8f5' into dev-release
diff --git a/.gitignore b/.gitignore
index 147cf9a..26634e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
+!third_party/chrome/*.sha1
!third_party/gmail/*.sha1
!third_party/gmscore/*.sha1
!third_party/internal/*.sha1
@@ -30,6 +31,8 @@
tests/2017-10-04/art.tar.gz
third_party/android_cts_baseline
third_party/android_cts_baseline.tar.gz
+third_party/clank/clank_google3_prebuilt
+third_party/clank/clank_google3.tar.gz
third_party/android_jar/lib
third_party/android_jar/lib-v[0-9][0-9]
third_party/android_jar/lib-v[0-9][0-9].tar.gz
diff --git a/build.gradle b/build.gradle
index d7c83e7..121279f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -420,6 +420,7 @@
"chrome/chrome_180917_ffbaa8",
"chrome/chrome_200430",
"chrome/monochrome_public_minimal_apks/chrome_200520",
+ "chrome/clank_google3_prebuilt",
"classlib",
"cf_segments",
"desugar/desugar_20180308",
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index b762557..7310ecf 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -266,15 +266,7 @@
InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
if (options.isGeneratingClassFiles()) {
// TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
- SyntheticFinalization.Result result =
- appView.getSyntheticItems().computeFinalSynthetics(appView);
- if (result != null) {
- appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
- appView.pruneItems(result.prunedItems);
- if (result.lens != null) {
- appView.setGraphLens(result.lens);
- }
- }
+ SyntheticFinalization.finalize(appView);
new CfApplicationWriter(
appView,
marker,
@@ -313,15 +305,7 @@
}
// TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
- SyntheticFinalization.Result result =
- appView.getSyntheticItems().computeFinalSynthetics(appView);
- if (result != null) {
- appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
- appView.pruneItems(result.prunedItems);
- if (result.lens != null) {
- appView.setGraphLens(result.lens);
- }
- }
+ SyntheticFinalization.finalize(appView);
new ApplicationWriter(
appView,
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b12a940..da20df4 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -471,7 +471,6 @@
assert !internal.enableInlining;
assert !internal.enableClassInlining;
assert internal.horizontalClassMergerOptions().isDisabled();
- assert !internal.enableStaticClassMerging;
assert !internal.enableVerticalClassMerging;
assert !internal.enableClassStaticizer;
assert !internal.enableEnumValueOptimization;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index bf85148..f91a003 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -18,8 +18,7 @@
import com.android.tools.r8.shaking.EnqueuerFactory;
import com.android.tools.r8.shaking.MainDexListBuilder;
import com.android.tools.r8.shaking.MainDexTracingResult;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.Box;
@@ -86,7 +85,7 @@
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
RootSet mainDexRootSet =
- new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
+ RootSet.builder(appView, subtypingInfo, options.mainDexKeepRules).build(executor);
GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 64fc53e..f12d33c 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -138,15 +138,7 @@
new IRConverter(appView, timing).convert(appView, executor);
- SyntheticFinalization.Result result =
- appView.getSyntheticItems().computeFinalSynthetics(appView);
- if (result != null) {
- appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
- appView.pruneItems(result.prunedItems);
- if (result.lens != null) {
- appView.setGraphLens(result.lens);
- }
- }
+ SyntheticFinalization.finalize(appView);
NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
new GenericSignatureRewriter(appView, namingLens).run(appView.appInfo().classes(), executor);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index fff3341..8bf7cc6 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -169,7 +169,6 @@
assert !internal.enableInlining;
assert !internal.enableClassInlining;
assert internal.horizontalClassMergerOptions().isDisabled();
- assert !internal.enableStaticClassMerging;
assert !internal.enableVerticalClassMerging;
assert !internal.enableClassStaticizer;
assert !internal.enableEnumValueOptimization;
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 4be995a..99fc6db 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -14,8 +14,8 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerFactory;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -90,8 +90,8 @@
appView.setAppServices(AppServices.builder(appView).build());
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
RootSet rootSet =
- new RootSetBuilder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
- .run(executor);
+ RootSet.builder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
+ .build(executor);
Enqueuer enqueuer =
EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo);
AppInfoWithLiveness appInfo = enqueuer.traceApplication(rootSet, executor, timing);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5e31835..9c93e0e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -38,7 +38,6 @@
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerResult;
@@ -95,10 +94,9 @@
import com.android.tools.r8.shaking.MissingClasses;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardConfigurationUtils;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
-import com.android.tools.r8.shaking.StaticClassMerger;
import com.android.tools.r8.shaking.TreePruner;
import com.android.tools.r8.shaking.TreePrunerConfiguration;
import com.android.tools.r8.shaking.VerticalClassMerger;
@@ -343,12 +341,12 @@
}
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
appView.setRootSet(
- new RootSetBuilder(
+ RootSet.builder(
appView,
subtypingInfo,
Iterables.concat(
options.getProguardConfiguration().getRules(), synthesizedProguardRules))
- .run(executorService));
+ .build(executorService));
AnnotationRemover.Builder annotationRemoverBuilder =
options.isShrinking() ? AnnotationRemover.builder() : null;
@@ -430,8 +428,8 @@
// Find classes which may have code executed before secondary dex files installation.
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
mainDexRootSet =
- new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules)
- .run(executorService);
+ RootSet.builder(appView, subtypingInfo, options.mainDexKeepRules)
+ .build(executorService);
// Live types is the tracing result.
Set<DexProgramClass> mainDexBaseClasses =
EnqueuerFactory.createForInitialMainDexTracing(appView, executorService, subtypingInfo)
@@ -477,18 +475,6 @@
RuntimeTypeCheckInfo runtimeTypeCheckInfo = classMergingEnqueuerExtensionBuilder.build();
if (!isKotlinLibraryCompilationWithInlinePassThrough
&& options.getProguardConfiguration().isOptimizing()) {
- if (options.enableStaticClassMerging) {
- timing.begin("HorizontalStaticClassMerger");
- StaticClassMerger staticClassMerger =
- new StaticClassMerger(appViewWithLiveness, options, mainDexTracingResult);
- NestedGraphLens lens = staticClassMerger.run();
- appView.rewriteWithLens(lens);
- timing.end();
- } else {
- appView.setStaticallyMergedClasses(StaticallyMergedClasses.empty());
- }
- assert appView.staticallyMergedClasses() != null;
-
if (options.enableVerticalClassMerging) {
timing.begin("VerticalClassMerger");
VerticalClassMerger verticalClassMerger =
@@ -822,25 +808,10 @@
appView.appInfo().getMainDexClasses().addAll(mainDexTracingResult);
}
- SyntheticFinalization.Result result =
- appView.getSyntheticItems().computeFinalSynthetics(appView);
- if (result != null) {
- if (appView.appInfo().hasLiveness()) {
- if (result.lens == null) {
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
- } else {
- appViewWithLiveness.rewriteWithLensAndApplication(
- result.lens, result.commit.getApplication().asDirect());
- }
- appViewWithLiveness.pruneItems(result.prunedItems);
- } else {
- appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
- appView.pruneItems(result.prunedItems);
- if (result.lens != null) {
- appView.setGraphLens(result.lens);
- }
- }
+ if (appView.appInfo().hasLiveness()) {
+ SyntheticFinalization.finalizeWithLiveness(appView.withLiveness());
+ } else {
+ SyntheticFinalization.finalizeWithClassHierarchy(appView);
}
// Perform minification.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index d6ab3a6..0fa55a7 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -858,7 +858,6 @@
assert proguardConfiguration.isOptimizing()
|| internal.horizontalClassMergerOptions().isDisabled();
- assert internal.enableStaticClassMerging || !proguardConfiguration.isOptimizing();
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
if (internal.debug) {
@@ -868,7 +867,6 @@
internal.enableInlining = false;
internal.enableClassInlining = false;
internal.horizontalClassMergerOptions().disable();
- internal.enableStaticClassMerging = false;
internal.enableVerticalClassMerging = false;
internal.enableClassStaticizer = false;
internal.outline.enabled = false;
@@ -881,7 +879,6 @@
internal.enableEnumUnboxing = false;
internal.horizontalClassMergerOptions().disable();
internal.enableLambdaMerging = false;
- internal.enableStaticClassMerging = false;
internal.enableVerticalClassMerging = false;
}
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 656c8f8..92930b0 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -563,7 +563,7 @@
indent();
Kind kind = cfSwitch.getKind();
builder.append(kind == Kind.LOOKUP ? "lookup" : "table").append("switch");
- IntList keys = cfSwitch.getKeys();
+ IntList keys = (IntList) cfSwitch.getKeys();
List<CfLabel> targets = cfSwitch.getSwitchTargets();
for (int i = 0; i < targets.size(); i++) {
indent();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index cb915ff..f07011a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -21,7 +21,6 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntList;
import java.util.List;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
@@ -71,7 +70,7 @@
return defaultTarget;
}
- public IntList getKeys() {
+ public List<Integer> getKeys() {
return new IntArrayList(keys);
}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 7da0bc5..414031e 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -36,6 +36,7 @@
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import java.io.IOException;
@@ -471,6 +472,20 @@
classes.removeAll(featureClasses);
}
}
+ List<DexProgramClass> toRemove = new ArrayList<>();
+ for (DexProgramClass dexProgramClass : classes) {
+ Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom();
+ if (!synthesizedFrom.isEmpty()) {
+ DexProgramClass from = Iterables.getFirst(synthesizedFrom, null);
+ FeatureSplit featureSplit = classToFeatureSplitMap.getFeatureSplit(from);
+ if (!featureSplit.isBase()) {
+ Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit);
+ dexProgramClasses.add(dexProgramClass);
+ toRemove.add(dexProgramClass);
+ }
+ }
+ }
+ classes.removeAll(toRemove);
return featureSplitClasses;
}
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index ec95ade..1ea1595 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.Sets;
+import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
@@ -27,11 +28,18 @@
public class ClassToFeatureSplitMap {
private final Map<DexType, FeatureSplit> classToFeatureSplitMap = new IdentityHashMap<>();
+ private final Map<FeatureSplit, String> representativeStringsForFeatureSplit;
- private ClassToFeatureSplitMap() {}
+ private ClassToFeatureSplitMap() {
+ this(new HashMap<>());
+ }
+
+ private ClassToFeatureSplitMap(Map<FeatureSplit, String> representativeStringsForFeatureSplit) {
+ this.representativeStringsForFeatureSplit = representativeStringsForFeatureSplit;
+ }
public static ClassToFeatureSplitMap createEmptyClassToFeatureSplitMap() {
- return new ClassToFeatureSplitMap();
+ return new ClassToFeatureSplitMap(null);
}
public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap(
@@ -56,6 +64,7 @@
}
for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+ String representativeType = null;
for (ProgramResourceProvider programResourceProvider :
featureSplit.getProgramResourceProviders()) {
try {
@@ -63,16 +72,42 @@
for (String classDescriptor : programResource.getClassDescriptors()) {
DexType type = dexItemFactory.createType(classDescriptor);
result.classToFeatureSplitMap.put(type, featureSplit);
+ if (representativeType == null || classDescriptor.compareTo(representativeType) > 0) {
+ representativeType = classDescriptor;
+ }
}
}
} catch (ResourceException e) {
throw reporter.fatalError(e.getMessage());
}
}
+ if (representativeType != null) {
+ result.representativeStringsForFeatureSplit.put(featureSplit, representativeType);
+ }
}
return result;
}
+ public int compareFeatureSplitsForDexTypes(DexType a, DexType b) {
+ FeatureSplit featureSplitA = getFeatureSplit(a);
+ FeatureSplit featureSplitB = getFeatureSplit(b);
+ assert featureSplitA != null;
+ assert featureSplitB != null;
+ if (featureSplitA == featureSplitB) {
+ return 0;
+ }
+ // Base bigger than any other feature
+ if (featureSplitA.isBase()) {
+ return 1;
+ }
+ if (featureSplitB.isBase()) {
+ return -1;
+ }
+ return representativeStringsForFeatureSplit
+ .get(featureSplitA)
+ .compareTo(representativeStringsForFeatureSplit.get(featureSplitB));
+ }
+
public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
Set<DexProgramClass> classes) {
Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
@@ -118,8 +153,13 @@
return getFeatureSplit(a) == getFeatureSplit(b);
}
+ public boolean isInSameFeatureOrBothInBase(DexType a, DexType b) {
+ return getFeatureSplit(a) == getFeatureSplit(b);
+ }
+
public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) {
- ClassToFeatureSplitMap rewrittenClassToFeatureSplitMap = new ClassToFeatureSplitMap();
+ ClassToFeatureSplitMap rewrittenClassToFeatureSplitMap =
+ new ClassToFeatureSplitMap(representativeStringsForFeatureSplit);
classToFeatureSplitMap.forEach(
(type, featureSplit) -> {
DexType rewrittenType = lens.lookupType(type);
@@ -137,7 +177,8 @@
}
public ClassToFeatureSplitMap withoutPrunedItems(PrunedItems prunedItems) {
- ClassToFeatureSplitMap classToFeatureSplitMapAfterPruning = new ClassToFeatureSplitMap();
+ ClassToFeatureSplitMap classToFeatureSplitMapAfterPruning =
+ new ClassToFeatureSplitMap(representativeStringsForFeatureSplit);
classToFeatureSplitMap.forEach(
(type, featureSplit) -> {
if (!prunedItems.getRemovedClasses().contains(type)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
index 8767555..f8c4f3b 100644
--- a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
+++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
@@ -293,7 +293,7 @@
return accessesWithContexts.isEmpty();
}
- boolean recordAccess(DexField access, ProgramMethod context) {
+ public boolean recordAccess(DexField access, ProgramMethod context) {
return accessesWithContexts
.computeIfAbsent(access, ignore -> ProgramMethodSet.create())
.add(context);
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 2d91d71..7d574dd 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintFactory;
@@ -34,7 +33,8 @@
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.LibraryModeledPredicate;
import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.ProguardCompatibilityActions;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.TestingOptions;
@@ -62,10 +62,12 @@
private final WholeProgramOptimizations wholeProgramOptimizations;
private GraphLens graphLens;
private InitClassLens initClassLens;
+ private ProguardCompatibilityActions proguardCompatibilityActions;
private RootSet rootSet;
// This should perferably always be obtained via AppInfoWithLiveness.
// Currently however the liveness may be downgraded thus loosing the computed keep info.
private KeepInfoCollection keepInfo = null;
+
private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
new InstanceFieldInitializationInfoFactory();
@@ -91,7 +93,6 @@
private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
private HorizontallyMergedClasses horizontallyMergedClasses;
- private StaticallyMergedClasses staticallyMergedClasses;
private VerticallyMergedClasses verticallyMergedClasses;
private EnumDataMap unboxedEnums = EnumDataMap.empty();
// TODO(b/169115389): Remove
@@ -201,6 +202,10 @@
return appInfo;
}
+ public AppInfoWithLiveness appInfoWithLiveness() {
+ return appInfo.hasLiveness() ? appInfo.withLiveness() : null;
+ }
+
public AppInfoWithClassHierarchy appInfoForDesugaring() {
if (enableWholeProgramOptimizations()) {
assert appInfo.hasClassHierarchy();
@@ -459,6 +464,20 @@
return keepInfo;
}
+ public boolean hasProguardCompatibilityActions() {
+ return proguardCompatibilityActions != null;
+ }
+
+ public ProguardCompatibilityActions getProguardCompatibilityActions() {
+ return proguardCompatibilityActions;
+ }
+
+ public void setProguardCompatibilityActions(
+ ProguardCompatibilityActions proguardCompatibilityActions) {
+ assert options().forceProguardCompatibility;
+ this.proguardCompatibilityActions = proguardCompatibilityActions;
+ }
+
public MergedClassesCollection allMergedClasses() {
MergedClassesCollection collection = new MergedClassesCollection();
if (horizontallyMergedClasses != null) {
@@ -505,19 +524,6 @@
}
/**
- * Get the result of static class merging. Returns null if static class merging has not been run.
- */
- public StaticallyMergedClasses staticallyMergedClasses() {
- return staticallyMergedClasses;
- }
-
- public void setStaticallyMergedClasses(StaticallyMergedClasses staticallyMergedClasses) {
- assert this.staticallyMergedClasses == null;
- this.staticallyMergedClasses = staticallyMergedClasses;
- testing().staticallyMergedClassesConsumer.accept(dexItemFactory(), staticallyMergedClasses);
- }
-
- /**
* Get the result of vertical class merging. Returns null if vertical class merging has not been
* run.
*/
@@ -558,6 +564,10 @@
: null;
}
+ public boolean hasLiveness() {
+ return appInfo().hasLiveness();
+ }
+
public AppView<AppInfoWithLiveness> withLiveness() {
@SuppressWarnings("unchecked")
AppView<AppInfoWithLiveness> appViewWithLiveness = (AppView<AppInfoWithLiveness>) this;
@@ -605,6 +615,10 @@
if (appServices() != null) {
setAppServices(appServices().prunedCopy(prunedItems));
}
+ if (hasProguardCompatibilityActions()) {
+ setProguardCompatibilityActions(
+ getProguardCompatibilityActions().withoutPrunedItems(prunedItems));
+ }
}
@SuppressWarnings("unchecked")
@@ -670,6 +684,10 @@
if (appView.hasInitClassLens()) {
appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
}
+ if (appView.hasProguardCompatibilityActions()) {
+ appView.setProguardCompatibilityActions(
+ appView.getProguardCompatibilityActions().rewrittenWithLens(lens));
+ }
});
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 37b8d28..d5ea16e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -98,8 +98,6 @@
abstract List<DexProgramClass> programClasses();
- abstract List<DexClasspathClass> classpathClasses();
-
public List<DexProgramClass> classes() {
ReorderBox<DexProgramClass> box = new ReorderBox<>(programClasses());
assert box.reorderClasses();
@@ -134,7 +132,6 @@
public abstract static class Builder<T extends Builder<T>> {
private final List<DexProgramClass> programClasses = new ArrayList<>();
- private final List<DexClasspathClass> classpathClasses = new ArrayList<>();
final List<DataResourceProvider> dataResourceProviders = new ArrayList<>();
@@ -157,7 +154,6 @@
public Builder(DexApplication application) {
programClasses.addAll(application.programClasses());
- classpathClasses.addAll(application.classpathClasses());
dataResourceProviders.addAll(application.dataResourceProviders);
proguardMap = application.getProguardMap();
timing = application.timing;
@@ -167,6 +163,14 @@
synthesizedClasses = new ArrayList<>();
}
+ public boolean isDirect() {
+ return false;
+ }
+
+ public DirectMappedDexApplication.Builder asDirect() {
+ return null;
+ }
+
public synchronized T setProguardMap(ClassNameMapper proguardMap) {
assert this.proguardMap == null;
this.proguardMap = proguardMap;
@@ -180,14 +184,6 @@
return self();
}
- public synchronized T replaceClasspathClasses(
- Collection<DexClasspathClass> newClasspathClasses) {
- assert newClasspathClasses != null;
- classpathClasses.clear();
- classpathClasses.addAll(newClasspathClasses);
- return self();
- }
-
public synchronized T addDataResourceProvider(DataResourceProvider provider) {
dataResourceProviders.add(provider);
return self();
@@ -208,16 +204,6 @@
return self();
}
- public synchronized T addClasspathClass(DexClasspathClass clazz) {
- classpathClasses.add(clazz);
- return self();
- }
-
- public synchronized T addClasspathClasses(Collection<DexClasspathClass> classes) {
- classpathClasses.addAll(classes);
- return self();
- }
-
public synchronized T addSynthesizedClass(DexProgramClass synthesizedClass) {
assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
addProgramClass(synthesizedClass);
@@ -229,10 +215,6 @@
return programClasses;
}
- public List<DexClasspathClass> getClasspathClasses() {
- return classpathClasses;
- }
-
public Collection<DexProgramClass> getSynthesizedClasses() {
return synthesizedClasses;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 30d5cfd..f933754 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -20,9 +20,11 @@
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.structural.StructuralItem;
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.util.function.Consumer;
public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField>
implements StructuralItem<DexEncodedField> {
@@ -65,6 +67,10 @@
assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations);
}
+ public DexEncodedField(DexField field, FieldAccessFlags accessFlags) {
+ this(field, accessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
+ }
+
public DexEncodedField(
DexField field,
FieldAccessFlags accessFlags,
@@ -162,6 +168,10 @@
return getReference().getType();
}
+ public TypeElement getTypeElement(AppView<?> appView) {
+ return getReference().getTypeElement(appView);
+ }
+
@Override
public boolean isDexEncodedField() {
return true;
@@ -295,17 +305,14 @@
}
public DexEncodedField toTypeSubstitutedField(DexField field) {
+ return toTypeSubstitutedField(field, ConsumerUtils.emptyConsumer());
+ }
+
+ public DexEncodedField toTypeSubstitutedField(DexField field, Consumer<Builder> consumer) {
if (this.field == field) {
return this;
}
- // TODO(b/169923358): Consider removing the fieldSignature here.
- DexEncodedField result =
- new DexEncodedField(field, accessFlags, genericSignature, annotations(), staticValue);
- result.optimizationInfo =
- optimizationInfo.isMutableFieldOptimizationInfo()
- ? optimizationInfo.asMutableFieldOptimizationInfo().mutableCopy()
- : DefaultFieldOptimizationInfo.getInstance();
- return result;
+ return builder(this).setField(field).apply(consumer).build();
}
public boolean validateDexValue(DexItemFactory factory) {
@@ -335,4 +342,58 @@
public void clearGenericSignature() {
this.genericSignature = FieldTypeSignature.noSignature();
}
+
+ private static Builder builder(DexEncodedField from) {
+ return new Builder(from);
+ }
+
+ public static class Builder {
+
+ private DexField field;
+ private DexAnnotationSet annotations;
+ private FieldAccessFlags accessFlags;
+ private FieldTypeSignature genericSignature;
+ private DexValue staticValue;
+ private FieldOptimizationInfo optimizationInfo;
+
+ Builder(DexEncodedField from) {
+ // Copy all the mutable state of a DexEncodedField here.
+ field = from.field;
+ accessFlags = from.accessFlags.copy();
+ // TODO(b/169923358): Consider removing the fieldSignature here.
+ genericSignature = from.getGenericSignature();
+ annotations = from.annotations();
+ staticValue = from.staticValue;
+ optimizationInfo =
+ from.optimizationInfo.isDefaultFieldOptimizationInfo()
+ ? DefaultFieldOptimizationInfo.getInstance()
+ : from.optimizationInfo.mutableCopy();
+ }
+
+ public Builder fixupOptimizationInfo(Consumer<MutableFieldOptimizationInfo> consumer) {
+ if (optimizationInfo.isMutableFieldOptimizationInfo()) {
+ consumer.accept(optimizationInfo.asMutableFieldOptimizationInfo());
+ }
+ return this;
+ }
+
+ public Builder apply(Consumer<Builder> consumer) {
+ consumer.accept(this);
+ return this;
+ }
+
+ public Builder setField(DexField field) {
+ this.field = field;
+ return this;
+ }
+
+ DexEncodedField build() {
+ DexEncodedField dexEncodedField =
+ new DexEncodedField(field, accessFlags, genericSignature, annotations, staticValue);
+ if (optimizationInfo.isMutableFieldOptimizationInfo()) {
+ dexEncodedField.setOptimizationInfo(optimizationInfo.asMutableFieldOptimizationInfo());
+ }
+ return dexEncodedField;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 8060cbe..c736831 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1293,15 +1293,13 @@
}
})
.build())
- .modifyAccessFlags(
- accessFlags -> {
- accessFlags.setSynthetic();
- accessFlags.setStatic();
- accessFlags.unsetPrivate();
- if (holder.isInterface()) {
- accessFlags.setPublic();
- }
- })
+ .setAccessFlags(
+ MethodAccessFlags.builder()
+ .setBridge()
+ .setPublic(holder.isInterface())
+ .setStatic()
+ .setSynthetic()
+ .build())
.build());
}
@@ -1581,8 +1579,9 @@
return this;
}
- public void setAccessFlags(MethodAccessFlags accessFlags) {
- this.accessFlags = accessFlags.copy();
+ public Builder setAccessFlags(MethodAccessFlags accessFlags) {
+ this.accessFlags = accessFlags;
+ return this;
}
public Builder setMethod(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index a925fcc..5ebf3ae 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -3,8 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -46,6 +49,10 @@
return type;
}
+ public TypeElement getTypeElement(AppView<?> appView) {
+ return TypeElement.fromDexType(getType(), maybeNull(), appView);
+ }
+
@Override
public DexEncodedField lookupOnClass(DexClass clazz) {
return clazz != null ? clazz.lookupField(this) : null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e775ac6..30ac84b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -293,6 +293,11 @@
createString(Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX);
public final DexString thisName = createString("this");
+
+ // As much as possible, R8 should rely on the content of the static enum field, using
+ // enumMembers.isValuesFieldCandidate or checking the object state in the optimization info.
+ // The field name is unrealiable since the filed can be minified prior to this compilation.
+ // We keep enumValuesFieldName as a heuristic only.
public final DexString enumValuesFieldName = createString("$VALUES");
public final DexString enabledFieldName = createString("ENABLED");
@@ -1398,21 +1403,17 @@
return field == nameField || field == ordinalField;
}
- public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
- assert enumClass.isEnum();
- return method.holder == enumClass.type
- && method.proto.returnType == enumClass.type.toArrayType(1, DexItemFactory.this)
- && method.proto.parameters.size() == 0
- && method.name == valuesMethodName;
+ public boolean isEnumField(DexEncodedField staticField, DexType enumType) {
+ assert staticField.isStatic();
+ return staticField.getType() == enumType && staticField.isEnum() && staticField.isFinal();
}
- public boolean isValueOfMethod(DexMethod method, DexClass enumClass) {
- assert enumClass.isEnum();
- return method.holder == enumClass.type
- && method.proto.returnType == enumClass.type
- && method.proto.parameters.size() == 1
- && method.proto.parameters.values[0] == stringType
- && method.name == valueOfMethodName;
+ public boolean isValuesFieldCandidate(DexEncodedField staticField, DexType enumType) {
+ assert staticField.isStatic();
+ return staticField.getType().isArrayType()
+ && staticField.getType().toArrayElementType(DexItemFactory.this) == enumType
+ && staticField.isSynthetic()
+ && staticField.isFinal();
}
}
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 c488caf..76d2ea4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -4,20 +4,12 @@
package com.android.tools.r8.graph;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
-import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
-import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -308,27 +300,6 @@
return isDoubleType() || isLongType();
}
- // TODO(b/158159959): Remove usage of name-based identification.
- public boolean isD8R8SynthesizedClassType() {
- String name = toSourceString();
- // The synthesized classes listed here must always be unique to a program context and thus
- // never duplicated for distinct inputs.
- return false
- // Hygienic suffix.
- || name.contains(COMPANION_CLASS_NAME_SUFFIX)
- || name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
- || name.contains(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)
- // New and hygienic synthesis infrastructure.
- || SyntheticNaming.isSyntheticName(name)
- // Only generated in core lib.
- || name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
- || name.contains(TYPE_WRAPPER_SUFFIX)
- || name.contains(VIVIFIED_TYPE_WRAPPER_SUFFIX)
- || name.contains(DesugaredLibraryRetargeter.DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX)
- // Non-hygienic types.
- || isSynthesizedTypeThatCouldBeDuplicated(name);
- }
-
public boolean isLegacySynthesizedTypeAllowedDuplication() {
String name = toSourceString();
return isSynthesizedTypeThatCouldBeDuplicated(name) || oldSynthesizedName(name);
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index f355030..3c17c60 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -1358,9 +1358,7 @@
@Override
public AbstractValue toAbstractValue(AbstractValueFactory factory) {
- // TODO(b/150835624): Update once there is an abstract value to represent dex item based
- // strings.
- return UnknownValue.getInstance();
+ return factory.createSingleDexItemBasedStringValue(value, nameComputationInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 4f38fb2..86377b3 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
@@ -58,6 +59,10 @@
return allClasses.values();
}
+ public List<DexClasspathClass> classpathClasses() {
+ return classpathClasses;
+ }
+
@Override
List<DexProgramClass> programClasses() {
return programClasses;
@@ -68,11 +73,6 @@
}
@Override
- public List<DexClasspathClass> classpathClasses() {
- return classpathClasses;
- }
-
- @Override
public DexClass definitionFor(DexType type) {
assert type.isClassType() : "Cannot lookup definition for type: " + type;
return allClasses.get(type);
@@ -181,12 +181,16 @@
public static class Builder extends DexApplication.Builder<Builder> {
+ private ImmutableList<DexClasspathClass> classpathClasses;
private ImmutableList<DexLibraryClass> libraryClasses;
+ private final List<DexClasspathClass> pendingClasspathClasses = new ArrayList<>();
+
Builder(LazyLoadedDexApplication application) {
super(application);
// As a side-effect, this will force-load all classes.
AllClasses allClasses = application.loadAllClasses();
+ classpathClasses = allClasses.getClasspathClasses();
libraryClasses = allClasses.getLibraryClasses();
replaceProgramClasses(allClasses.getProgramClasses());
replaceClasspathClasses(allClasses.getClasspathClasses());
@@ -194,14 +198,58 @@
private Builder(DirectMappedDexApplication application) {
super(application);
+ classpathClasses = application.classpathClasses;
libraryClasses = application.libraryClasses;
}
@Override
+ public boolean isDirect() {
+ return true;
+ }
+
+ @Override
+ public Builder asDirect() {
+ return this;
+ }
+
+ @Override
Builder self() {
return this;
}
+ public Builder addClasspathClass(DexClasspathClass clazz) {
+ pendingClasspathClasses.add(clazz);
+ return self();
+ }
+
+ public Builder addClasspathClasses(Collection<DexClasspathClass> classes) {
+ pendingClasspathClasses.addAll(classes);
+ return self();
+ }
+
+ private void commitPendingClasspathClasses() {
+ if (!pendingClasspathClasses.isEmpty()) {
+ classpathClasses =
+ ImmutableList.<DexClasspathClass>builder()
+ .addAll(classpathClasses)
+ .addAll(pendingClasspathClasses)
+ .build();
+ pendingClasspathClasses.clear();
+ }
+ }
+
+ public List<DexClasspathClass> getClasspathClasses() {
+ commitPendingClasspathClasses();
+ return classpathClasses;
+ }
+
+ public Builder replaceClasspathClasses(Collection<DexClasspathClass> newClasspathClasses) {
+ assert newClasspathClasses != null;
+ classpathClasses = ImmutableList.copyOf(newClasspathClasses);
+ pendingClasspathClasses.clear();
+ return self();
+ }
+
public Builder replaceLibraryClasses(Collection<DexLibraryClass> libraryClasses) {
this.libraryClasses = ImmutableList.copyOf(libraryClasses);
return self();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 554df05..4f07887 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -58,6 +58,11 @@
return this;
}
+ public static FieldAccessFlags createPublicStaticSynthetic() {
+ return fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
+ }
+
public static FieldAccessFlags fromSharedAccessFlags(int access) {
assert (access & FLAGS) == access;
return new FieldAccessFlags(access & FLAGS);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index c5f516b..871ed69 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -79,6 +79,10 @@
this.readsWithContexts = readsWithContexts;
}
+ public void setWritesWithContexts(AbstractAccessContexts writesWithContexts) {
+ this.writesWithContexts = writesWithContexts;
+ }
+
@Override
public int getNumberOfReadContexts() {
return readsWithContexts.getNumberOfAccessContexts();
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index ca9a57a..2f6c224 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -627,7 +627,7 @@
// that they can be mapped back to the original program.
DexField originalField = getOriginalFieldSignature(field.getReference());
assert originalFields.contains(originalField)
- || isD8R8SynthesizedField(originalField, appView)
+ || isD8R8SynthesizedField(field.getReference(), appView)
: "Unable to map field `"
+ field.getReference().toSourceString()
+ "` back to original program";
@@ -654,7 +654,7 @@
if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) {
return true;
}
- if (appView.getSyntheticItems().isSyntheticClass(field.getHolderType())
+ if (appView.getSyntheticItems().isNonLegacySynthetic(field.getHolderType())
&& field.getName().toSourceString().equals(LAMBDA_INSTANCE_FIELD_NAME)) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index ba240f6..7005e06 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -54,12 +54,6 @@
}
@Override
- List<DexClasspathClass> classpathClasses() {
- classpathClasses.forceLoad(t -> true);
- return classpathClasses.getAllClasses();
- }
-
- @Override
public DexClass definitionFor(DexType type) {
assert type.isClassType() : "Cannot lookup definition for type: " + type;
DexClass clazz = null;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 7d6eac0..4a5c689 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -163,6 +163,10 @@
set(Constants.ACC_NATIVE);
}
+ public void unsetNative() {
+ unset(Constants.ACC_NATIVE);
+ }
+
public boolean isAbstract() {
return isSet(Constants.ACC_ABSTRACT);
}
@@ -225,6 +229,11 @@
super(MethodAccessFlags.fromSharedAccessFlags(0, false));
}
+ public Builder setBridge() {
+ flags.setBridge();
+ return this;
+ }
+
public Builder setConstructor() {
flags.setConstructor();
return this;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
index 5d53ac0..b58a7b8 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
@@ -35,6 +35,11 @@
.add(clazz);
}
+ public boolean contains(DexProgramClass clazz) {
+ ProgramPackage pkg = packages.get(clazz.getType().getPackageDescriptor());
+ return pkg != null && pkg.contains(clazz);
+ }
+
public boolean isEmpty() {
return packages.isEmpty();
}
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index ed42622..7441412 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -38,6 +38,10 @@
return removedClasses.isEmpty() && additionalPinnedItems.isEmpty();
}
+ public boolean isRemoved(DexType type) {
+ return removedClasses.contains(type);
+ }
+
public DexApplication getPrunedApp() {
return prunedApp;
}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java
deleted file mode 100644
index 9aabf18..0000000
--- a/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph.classmerging;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
-import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
-import java.util.Set;
-import java.util.function.BiConsumer;
-
-public class StaticallyMergedClasses implements MergedClasses {
-
- private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses;
-
- public StaticallyMergedClasses(BidirectionalManyToOneMap<DexType, DexType> mergedClasses) {
- this.mergedClasses = mergedClasses;
- }
-
- public static StaticallyMergedClasses empty() {
- return new StaticallyMergedClasses(new EmptyBidirectionalOneToOneMap<>());
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- @Override
- public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) {
- mergedClasses.forEachManyToOneMapping(consumer);
- }
-
- @Override
- public boolean hasBeenMergedIntoDifferentType(DexType type) {
- return false;
- }
-
- @Override
- public boolean isMergeTarget(DexType type) {
- // Intentionally returns false since static class merging technically doesn't merge any classes,
- // it only moves static members.
- return false;
- }
-
- @Override
- public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
- return true;
- }
-
- public static class Builder {
-
- private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses =
- new BidirectionalManyToOneHashMap<>();
-
- private Builder() {}
-
- public void recordMerge(DexProgramClass source, DexProgramClass target) {
- for (DexType previousSource : mergedClasses.removeValue(source.getType())) {
- mergedClasses.put(previousSource, target.getType());
- }
- mergedClasses.put(source.getType(), target.getType());
- }
-
- public StaticallyMergedClasses build() {
- return new StaticallyMergedClasses(mergedClasses);
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index 3adb35c..fcafba0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -89,7 +89,7 @@
public boolean tryAdd(AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
Map<DexMethodSignature, MethodCharacteristics> newMethods = new HashMap<>();
- for (DexEncodedMethod method : clazz.methods()) {
+ for (DexEncodedMethod method : clazz.methods(this::isSubjectToMethodMerging)) {
DexMethodSignature signature = method.getSignature();
MethodCharacteristics existingCharacteristics = methodMap.get(signature);
MethodCharacteristics methodCharacteristics = MethodCharacteristics.create(appView, method);
@@ -105,6 +105,14 @@
group.add(clazz);
return true;
}
+
+ private boolean isSubjectToMethodMerging(DexEncodedMethod method) {
+ if (method.isStatic() || (method.isPrivate() && !method.isInstanceInitializer())) {
+ // Static and private instance methods can easily be renamed and do not require merging.
+ return false;
+ }
+ return true;
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
index dcbd291..c7c57c8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
@@ -79,7 +79,7 @@
DexMethodSignatureSet getOrComputeSignatures(DexType type) {
DexClass clazz = appView.definitionFor(type);
- return clazz != null ? getOrComputeSignatures(clazz) : null;
+ return clazz != null ? getOrComputeSignatures(clazz) : DexMethodSignatureSet.create();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 1e93d1b..899fa2b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -4,8 +4,10 @@
package com.android.tools.r8.ir.analysis.fieldaccess;
-
+import com.android.tools.r8.graph.AbstractAccessContexts;
+import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -14,8 +16,11 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -27,7 +32,9 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -37,7 +44,14 @@
private final PostMethodProcessor.Builder postMethodProcessorBuilder;
/** Updated concurrently from {@link #processClass(DexProgramClass)}. */
- private final Set<DexEncodedField> fieldsOfInterest = Sets.newConcurrentHashSet();
+ private final Map<DexEncodedField, AbstractAccessContexts> readFields = new ConcurrentHashMap<>();
+
+ /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+ private final Map<DexEncodedField, AbstractAccessContexts> writtenFields =
+ new ConcurrentHashMap<>();
+
+ /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+ private final Set<DexEncodedField> constantFields = Sets.newConcurrentHashSet();
/** Updated concurrently from {@link #processClass(DexProgramClass)}. */
private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
@@ -58,38 +72,34 @@
assert feedback.noUpdatesLeft();
timing.begin("Compute fields of interest");
- computeFieldsOfInterest();
+ computeConstantFields();
timing.end(); // Compute fields of interest
- if (fieldsOfInterest.isEmpty()) {
- timing.end(); // Trivial field accesses analysis
- return;
- }
-
timing.begin("Enqueue methods for reprocessing");
enqueueMethodsForReprocessing(appInfo, executorService);
timing.end(); // Enqueue methods for reprocessing
- timing.begin("Clear reads from fields of interest");
- clearReadsFromFieldsOfInterest(appInfo);
+ timing.begin("Clear reads and writes from fields of interest");
+ clearReadsAndWritesFromFieldsOfInterest(appInfo);
timing.end(); // Clear reads from fields of interest
-
timing.end(); // Trivial field accesses analysis
- fieldsOfInterest.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
+ constantFields.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
+ readFields.keySet().forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
+ writtenFields.keySet().forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
}
- private void computeFieldsOfInterest() {
+ private void computeConstantFields() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
for (DexEncodedField field : clazz.instanceFields()) {
if (canOptimizeField(field, appView)) {
- fieldsOfInterest.add(field);
+ constantFields.add(field);
}
}
if (appView.canUseInitClass() || !clazz.classInitializationMayHaveSideEffects(appView)) {
for (DexEncodedField field : clazz.staticFields()) {
if (canOptimizeField(field, appView)) {
- fieldsOfInterest.add(field);
+ constantFields.add(field);
}
}
}
@@ -97,11 +107,17 @@
assert verifyNoConstantFieldsOnSynthesizedClasses(appView);
}
- private void clearReadsFromFieldsOfInterest(AppInfoWithLiveness appInfo) {
+ private void clearReadsAndWritesFromFieldsOfInterest(AppInfoWithLiveness appInfo) {
FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
- for (DexEncodedField field : fieldsOfInterest) {
+ for (DexEncodedField field : constantFields) {
fieldAccessInfoCollection.get(field.field).asMutable().clearReads();
}
+ for (DexEncodedField field : readFields.keySet()) {
+ fieldAccessInfoCollection.get(field.getReference()).asMutable().clearWrites();
+ }
+ for (DexEncodedField field : writtenFields.keySet()) {
+ fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
+ }
}
private void enqueueMethodsForReprocessing(
@@ -111,6 +127,8 @@
appInfo.getSyntheticItems().getPendingSyntheticClasses(),
this::processClass,
executorService);
+ processFieldsNeverRead(appInfo);
+ processFieldsNeverWritten(appInfo);
postMethodProcessorBuilder.put(methodsToReprocess);
}
@@ -146,6 +164,74 @@
return false;
}
+ private void processFieldsNeverRead(AppInfoWithLiveness appInfo) {
+ FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+ writtenFields
+ .entrySet()
+ .removeIf(
+ entry ->
+ !entry.getValue().isConcrete()
+ || !canOptimizeOnlyReadOrWrittenField(
+ entry.getKey(), true, fieldAccessInfoCollection));
+ writtenFields.forEach(
+ (field, contexts) -> {
+ assert !readFields.containsKey(field);
+ fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
+ methodsToReprocess.addAll(
+ contexts.asConcrete().getAccessesWithContexts().values().iterator().next());
+ });
+ }
+
+ private void processFieldsNeverWritten(AppInfoWithLiveness appInfo) {
+ FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+ readFields
+ .entrySet()
+ .removeIf(
+ entry ->
+ !entry.getValue().isConcrete()
+ || !canOptimizeOnlyReadOrWrittenField(
+ entry.getKey(), false, fieldAccessInfoCollection));
+ readFields.forEach(
+ (field, contexts) -> {
+ assert !writtenFields.containsKey(field);
+ methodsToReprocess.addAll(
+ contexts.asConcrete().getAccessesWithContexts().values().iterator().next());
+ });
+ }
+
+ private boolean canOptimizeOnlyReadOrWrittenField(
+ DexEncodedField field,
+ boolean isWrite,
+ FieldAccessInfoCollection<?> fieldAccessInfoCollection) {
+ assert !appView.appInfo().isPinned(field);
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.getReference());
+ if (fieldAccessInfo == null) {
+ assert false
+ : "Expected program field with concrete accesses to be present in field access "
+ + "collection";
+ return false;
+ }
+
+ if (fieldAccessInfo.hasReflectiveAccess()
+ || fieldAccessInfo.isAccessedFromMethodHandle()
+ || fieldAccessInfo.isReadFromAnnotation()) {
+ return false;
+ }
+
+ if (isWrite && field.getType().isReferenceType()) {
+ ReferenceTypeElement fieldType = field.getTypeElement(appView).asReferenceType();
+ ClassTypeElement classType =
+ (fieldType.isArrayType() ? fieldType.asArrayType().getBaseType() : fieldType)
+ .asClassType();
+ if (classType != null
+ && appView.appInfo().mayHaveFinalizeMethodDirectlyOrIndirectly(classType)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private static boolean verifyNoConstantFieldsOnSynthesizedClasses(
AppView<AppInfoWithLiveness> appView) {
for (DexProgramClass clazz :
@@ -166,44 +252,95 @@
this.method = method;
}
- private boolean registerFieldAccess(DexField field, boolean isStatic) {
- DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
- if (encodedField != null) {
- // We cannot remove references from pass through functions.
- if (appView.isCfByteCodePassThrough(method.getDefinition())) {
- fieldsOfInterest.remove(encodedField);
- return true;
- }
- if (encodedField.isStatic() == isStatic) {
- if (fieldsOfInterest.contains(encodedField)) {
- methodsToReprocess.add(method);
+ private void registerFieldAccess(DexField reference, boolean isStatic, boolean isWrite) {
+ SuccessfulFieldResolutionResult resolutionResult =
+ appView.appInfo().resolveField(reference).asSuccessfulResolution();
+ if (resolutionResult == null) {
+ return;
+ }
+
+ DexClassAndField field = resolutionResult.getResolutionPair();
+ DexEncodedField definition = field.getDefinition();
+
+ // Record access.
+ if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
+ if (field.getAccessFlags().isStatic() == isStatic) {
+ if (isWrite) {
+ recordFieldAccessContext(definition, writtenFields, readFields);
+ } else {
+ recordFieldAccessContext(definition, readFields, writtenFields);
}
} else {
- // Should generally not happen.
- fieldsOfInterest.remove(encodedField);
+ destroyFieldAccessContexts(definition);
}
}
- return true;
+
+ // We cannot remove references from pass through functions.
+ if (appView.isCfByteCodePassThrough(method.getDefinition())) {
+ constantFields.remove(definition);
+ return;
+ }
+
+ if (definition.isStatic() == isStatic) {
+ if (constantFields.contains(definition)) {
+ methodsToReprocess.add(method);
+ }
+ } else {
+ // Should generally not happen.
+ constantFields.remove(definition);
+ }
}
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- registerFieldAccess(field, false);
+ private void recordFieldAccessContext(
+ DexEncodedField field,
+ Map<DexEncodedField, AbstractAccessContexts> fieldAccesses,
+ Map<DexEncodedField, AbstractAccessContexts> otherFieldAccesses) {
+ synchronized (field) {
+ AbstractAccessContexts otherAccessContexts =
+ otherFieldAccesses.getOrDefault(field, AbstractAccessContexts.empty());
+ if (otherAccessContexts.isBottom()) {
+ // Only read or written.
+ AbstractAccessContexts accessContexts =
+ fieldAccesses.computeIfAbsent(field, ignore -> new ConcreteAccessContexts());
+ assert accessContexts.isConcrete();
+ accessContexts.asConcrete().recordAccess(field.getReference(), method);
+ } else if (!otherAccessContexts.isTop()) {
+ // Now both read and written.
+ fieldAccesses.put(field, AbstractAccessContexts.unknown());
+ otherFieldAccesses.put(field, AbstractAccessContexts.unknown());
+ } else {
+ // Already read and written.
+ assert fieldAccesses.getOrDefault(field, AbstractAccessContexts.empty()).isTop();
+ assert otherFieldAccesses.getOrDefault(field, AbstractAccessContexts.empty()).isTop();
+ }
+ }
+ }
+
+ private void destroyFieldAccessContexts(DexEncodedField field) {
+ synchronized (field) {
+ readFields.put(field, AbstractAccessContexts.unknown());
+ writtenFields.put(field, AbstractAccessContexts.unknown());
+ }
}
@Override
public void registerInstanceFieldRead(DexField field) {
- registerFieldAccess(field, false);
+ registerFieldAccess(field, false, false);
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ registerFieldAccess(field, false, true);
}
@Override
public void registerStaticFieldRead(DexField field) {
- registerFieldAccess(field, true);
+ registerFieldAccess(field, true, false);
}
@Override
public void registerStaticFieldWrite(DexField field) {
- registerFieldAccess(field, true);
+ registerFieldAccess(field, true, true);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 281215f..fc01602 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,9 +12,6 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -26,6 +23,7 @@
import com.android.tools.r8.ir.analysis.value.NullOrAbstractValue;
import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
@@ -40,10 +38,13 @@
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
+import java.util.IdentityHashMap;
+import java.util.Map;
public class StaticFieldValueAnalysis extends FieldValueAnalysis {
private final StaticFieldValues.Builder builder;
+ private final Map<Value, AbstractValue> computedValues = new IdentityHashMap<>();
private StaticFieldValueAnalysis(
AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
@@ -63,7 +64,7 @@
timing.begin("Analyze class initializer");
StaticFieldValues result =
new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
- .analyze(classInitializerDefaultsResult, code.context().getHolderType());
+ .analyze(classInitializerDefaultsResult);
timing.end();
return result;
}
@@ -78,8 +79,7 @@
return this;
}
- StaticFieldValues analyze(
- ClassInitializerDefaultsResult classInitializerDefaultsResult, DexType holderType) {
+ StaticFieldValues analyze(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
computeFieldOptimizationInfo(classInitializerDefaultsResult);
return builder.build();
}
@@ -218,16 +218,41 @@
return null;
}
assert !value.hasAliasedValue();
- if (isEnumValuesArray(value)) {
+ if (value.isPhi()) {
+ return null;
+ }
+ if (value.definition.isNewArrayEmpty()) {
return computeSingleEnumFieldValueForValuesArray(value);
}
- return computeSingleEnumFieldValueForInstance(value);
+ if (value.definition.isNewInstance()) {
+ return computeSingleEnumFieldValueForInstance(value);
+ }
+ return null;
}
private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
- if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)) {
+ if (!value.definition.isNewArrayEmpty()) {
return null;
}
+ AbstractValue valuesValue = computedValues.get(value);
+ if (valuesValue != null) {
+ // This implicitely answers null if the value could not get computed.
+ if (valuesValue.isSingleFieldValue()) {
+ SingleFieldValue fieldValue = valuesValue.asSingleFieldValue();
+ if (fieldValue.getState().isEnumValuesObjectState()) {
+ return fieldValue;
+ }
+ }
+ return null;
+ }
+ SingleFieldValue singleFieldValue = internalComputeSingleEnumFieldValueForValuesArray(value);
+ computedValues.put(
+ value, singleFieldValue == null ? UnknownValue.getInstance() : singleFieldValue);
+ return singleFieldValue;
+ }
+
+ private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
+ assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
@@ -267,7 +292,9 @@
// We need the state of all fields for the analysis to be valuable.
return null;
}
- assert verifyValuesArrayIndexMatchesOrdinal(index, objectState);
+ if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+ return null;
+ }
if (valuesState[index] != null) {
return null;
}
@@ -326,24 +353,25 @@
return ObjectState.empty();
}
- private boolean verifyValuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
+ private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
DexEncodedField ordinalField =
appView
.appInfo()
.resolveField(appView.dexItemFactory().enumMembers.ordinalField, context)
.getResolvedField();
- assert ordinalField != null;
+ if (ordinalField == null) {
+ return false;
+ }
AbstractValue ordinalState = objectState.getAbstractFieldValue(ordinalField);
- assert ordinalState != null;
- assert ordinalState.isSingleNumberValue();
- assert ordinalState.asSingleNumberValue().getIntValue() == ordinal;
- return true;
+ if (ordinalState == null || !ordinalState.isSingleNumberValue()) {
+ return false;
+ }
+ int intValue = ordinalState.asSingleNumberValue().getIntValue();
+ return intValue == ordinal;
}
private SingleFieldValue computeSingleEnumFieldValueForInstance(Value value) {
- if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
- return null;
- }
+ assert value.isDefinedByInstructionSatisfying(Instruction::isNewInstance);
NewInstance newInstance = value.definition.asNewInstance();
// Some enums have direct subclasses, and the subclass is instantiated here.
@@ -459,30 +487,7 @@
}
private boolean isEnumValuesArray(Value value) {
- assert context.getHolder().isEnum();
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- DexField valuesField =
- dexItemFactory.createField(
- context.getHolderType(),
- context.getHolderType().toArrayType(1, dexItemFactory),
- dexItemFactory.enumValuesFieldName);
-
- Value root = value.getAliasedValue();
- if (root.isPhi()) {
- return false;
- }
-
- Instruction definition = root.definition;
- if (definition.isNewArrayEmpty()) {
- for (Instruction user : root.aliasedUsers()) {
- if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
- return true;
- }
- }
- } else if (definition.isStaticGet()) {
- return definition.asStaticGet().getField() == valuesField;
- }
-
- return false;
+ SingleFieldValue singleFieldValue = computeSingleEnumFieldValueForValuesArray(value);
+ return singleFieldValue != null && singleFieldValue.getState().isEnumValuesObjectState();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index b2dcdcd..65e1ea3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -37,17 +37,10 @@
// All the abstract values stored here may match a pinned field, using them requires therefore
// to check the field is not pinned or prove it is no longer pinned.
public static class EnumStaticFieldValues extends StaticFieldValues {
- private final ImmutableMap<DexField, AbstractValue> enumAbstractValues;
- private final DexField valuesField;
- private final AbstractValue valuesAbstractValue;
+ private final ImmutableMap<DexField, ObjectState> enumAbstractValues;
- public EnumStaticFieldValues(
- ImmutableMap<DexField, AbstractValue> enumAbstractValues,
- DexField valuesField,
- AbstractValue valuesAbstractValue) {
+ public EnumStaticFieldValues(ImmutableMap<DexField, ObjectState> enumAbstractValues) {
this.enumAbstractValues = enumAbstractValues;
- this.valuesField = valuesField;
- this.valuesAbstractValue = valuesAbstractValue;
}
static StaticFieldValues.Builder builder() {
@@ -55,33 +48,38 @@
}
public static class Builder extends StaticFieldValues.Builder {
- private final ImmutableMap.Builder<DexField, AbstractValue> enumAbstractValuesBuilder =
+ private final ImmutableMap.Builder<DexField, ObjectState> enumObjectStateBuilder =
ImmutableMap.builder();
- private DexField valuesFields;
- private AbstractValue valuesAbstractValue;
+ private AbstractValue valuesCandidateAbstractValue;
Builder() {}
@Override
public void recordStaticField(
DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
- // TODO(b/166532388): Stop relying on the values name.
- if (staticField.getName() == factory.enumValuesFieldName) {
- valuesFields = staticField.field;
- valuesAbstractValue = value;
- } else if (staticField.isEnum()) {
- enumAbstractValuesBuilder.put(staticField.field, value);
+ if (factory.enumMembers.isValuesFieldCandidate(staticField, staticField.getHolderType())) {
+ if (value.isSingleFieldValue()
+ && value.asSingleFieldValue().getState().isEnumValuesObjectState()) {
+ assert valuesCandidateAbstractValue == null
+ || valuesCandidateAbstractValue.equals(value);
+ valuesCandidateAbstractValue = value;
+ enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+ }
+ } else if (factory.enumMembers.isEnumField(staticField, staticField.getHolderType())) {
+ if (value.isSingleFieldValue() && !value.asSingleFieldValue().getState().isEmpty()) {
+ enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+ }
}
}
@Override
public StaticFieldValues build() {
- ImmutableMap<DexField, AbstractValue> enumAbstractValues =
- enumAbstractValuesBuilder.build();
- if (valuesAbstractValue == null && enumAbstractValues.isEmpty()) {
+ ImmutableMap<DexField, ObjectState> enumAbstractValues = enumObjectStateBuilder.build();
+ if (enumAbstractValues.isEmpty()) {
return EmptyStaticValues.getInstance();
}
- return new EnumStaticFieldValues(enumAbstractValues, valuesFields, valuesAbstractValue);
+ assert enumAbstractValues.values().stream().noneMatch(ObjectState::isEmpty);
+ return new EnumStaticFieldValues(enumAbstractValues);
}
}
@@ -96,20 +94,7 @@
}
public ObjectState getObjectStateForPossiblyPinnedField(DexField field) {
- AbstractValue fieldValue = enumAbstractValues.get(field);
- if (fieldValue == null || fieldValue.isZero()) {
- return null;
- }
- if (fieldValue.isSingleFieldValue()) {
- return fieldValue.asSingleFieldValue().getState();
- }
- assert fieldValue.isUnknown();
- return ObjectState.empty();
- }
-
- public AbstractValue getValuesAbstractValueForPossiblyPinnedField(DexField field) {
- assert valuesField == field || valuesAbstractValue == null;
- return valuesAbstractValue;
+ return enumAbstractValues.get(field);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index c374875..c5f72a3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -266,7 +266,6 @@
PredicateSet<DexType> alwaysClassInline,
Set<DexType> neverMergeClassVertically,
Set<DexType> neverMergeClassHorizontally,
- Set<DexType> neverMergeStaticClassHorizontally,
Set<DexMethod> alwaysInline,
Set<DexMethod> bypassClinitforInlining) {
new RootSetExtension(
@@ -274,7 +273,6 @@
alwaysClassInline,
neverMergeClassVertically,
neverMergeClassHorizontally,
- neverMergeStaticClassHorizontally,
alwaysInline,
bypassClinitforInlining)
.extend(subtypingInfo);
@@ -393,7 +391,6 @@
private final PredicateSet<DexType> alwaysClassInline;
private final Set<DexType> neverMergeClassVertically;
private final Set<DexType> neverMergeClassHorizontally;
- private final Set<DexType> neverMergeStaticClassHorizontally;
private final Set<DexMethod> alwaysInline;
private final Set<DexMethod> bypassClinitforInlining;
@@ -403,7 +400,6 @@
PredicateSet<DexType> alwaysClassInline,
Set<DexType> neverMergeClassVertically,
Set<DexType> neverMergeClassHorizontally,
- Set<DexType> neverMergeStaticClassHorizontally,
Set<DexMethod> alwaysInline,
Set<DexMethod> bypassClinitforInlining) {
this.appView = appView;
@@ -411,7 +407,6 @@
this.alwaysClassInline = alwaysClassInline;
this.neverMergeClassVertically = neverMergeClassVertically;
this.neverMergeClassHorizontally = neverMergeClassHorizontally;
- this.neverMergeStaticClassHorizontally = neverMergeStaticClassHorizontally;
this.alwaysInline = alwaysInline;
this.bypassClinitforInlining = bypassClinitforInlining;
}
@@ -478,7 +473,6 @@
private void neverMergeClass(DexType type) {
neverMergeClassVertically.add(type);
neverMergeClassHorizontally.add(type);
- neverMergeStaticClassHorizontally.add(type);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index e53f27b..d45bf07 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -84,6 +84,14 @@
return null;
}
+ public boolean isSingleDexItemBasedStringValue() {
+ return false;
+ }
+
+ public SingleDexItemBasedStringValue asSingleDexItemBasedStringValue() {
+ return null;
+ }
+
public boolean isUnknown() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index d242202..58a44a3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -5,8 +5,10 @@
package com.android.tools.r8.ir.analysis.value;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
import java.util.concurrent.ConcurrentHashMap;
public class AbstractValueFactory {
@@ -38,4 +40,9 @@
public SingleStringValue createSingleStringValue(DexString string) {
return singleStringValues.computeIfAbsent(string, SingleStringValue::new);
}
+
+ public SingleDexItemBasedStringValue createSingleDexItemBasedStringValue(
+ DexReference reference, NameComputationInfo<?> nameComputationInfo) {
+ return new SingleDexItemBasedStringValue(reference, nameComputationInfo);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
index db6be42..f8056dc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
@@ -6,8 +6,10 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.BiConsumer;
public class EmptyObjectState extends ObjectState {
@@ -20,6 +22,11 @@
}
@Override
+ public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {
+ // Intentionally empty.
+ }
+
+ @Override
public AbstractValue getAbstractFieldValue(DexEncodedField field) {
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
index 2d8ddc2..13686eb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
@@ -6,10 +6,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Arrays;
import java.util.Objects;
+import java.util.function.BiConsumer;
public class EnumValuesObjectState extends ObjectState {
@@ -22,6 +24,9 @@
}
@Override
+ public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {}
+
+ @Override
public AbstractValue getAbstractFieldValue(DexEncodedField field) {
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
index fad7303..6aacc3d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.function.BiConsumer;
public class NonEmptyObjectState extends ObjectState {
@@ -24,6 +25,11 @@
}
@Override
+ public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {
+ state.forEach(consumer);
+ }
+
+ @Override
public AbstractValue getAbstractFieldValue(DexEncodedField field) {
return state.getOrDefault(field.field, UnknownValue.getInstance());
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
index 4e6d970..f0af9b4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.function.BiConsumer;
public abstract class ObjectState {
@@ -22,6 +23,8 @@
return EmptyObjectState.getInstance();
}
+ public abstract void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer);
+
public abstract AbstractValue getAbstractFieldValue(DexEncodedField field);
public abstract boolean isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
new file mode 100644
index 0000000..88ea973
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.stringClassType;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
+import com.android.tools.r8.ir.code.DexItemBasedConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Objects;
+
+public class SingleDexItemBasedStringValue extends SingleConstValue {
+
+ private final DexReference item;
+ private final NameComputationInfo<?> nameComputationInfo;
+
+ /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+ SingleDexItemBasedStringValue(DexReference item, NameComputationInfo<?> nameComputationInfo) {
+ this.item = item;
+ this.nameComputationInfo = nameComputationInfo;
+ }
+
+ @Override
+ public boolean isSingleDexItemBasedStringValue() {
+ return true;
+ }
+
+ @Override
+ public SingleDexItemBasedStringValue asSingleDexItemBasedStringValue() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SingleDexItemBasedStringValue value = (SingleDexItemBasedStringValue) o;
+ return item == value.item && nameComputationInfo == value.nameComputationInfo;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(item, nameComputationInfo);
+ }
+
+ @Override
+ public String toString() {
+ return "DexItemBasedConstString(" + item.toSourceString() + ")";
+ }
+
+ @Override
+ public Instruction createMaterializingInstruction(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCode code,
+ TypeAndLocalInfoSupplier info) {
+ TypeElement typeLattice = info.getOutType();
+ DebugLocalInfo debugLocalInfo = info.getLocalInfo();
+ assert typeLattice.isClassType();
+ assert appView
+ .isSubtype(appView.dexItemFactory().stringType, typeLattice.asClassType().getClassType())
+ .isTrue();
+ Value returnedValue =
+ code.createValue(stringClassType(appView, definitelyNotNull()), debugLocalInfo);
+ DexItemBasedConstString instruction =
+ new DexItemBasedConstString(
+ returnedValue,
+ item,
+ nameComputationInfo,
+ ThrowingInfo.defaultForConstString(appView.options()));
+ assert !instruction.instructionInstanceCanThrow();
+ return instruction;
+ }
+
+ @Override
+ public boolean isMaterializableInContext(
+ AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+ return true;
+ }
+
+ @Override
+ public boolean isMaterializableInAllContexts(AppView<AppInfoWithLiveness> appView) {
+ return true;
+ }
+
+ @Override
+ public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+ return appView
+ .abstractValueFactory()
+ .createSingleDexItemBasedStringValue(lens.rewriteReference(item), nameComputationInfo);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 2c7313e..9c209c9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -20,6 +21,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public class DexItemBasedConstString extends ConstInstruction {
@@ -167,4 +169,12 @@
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
return false;
}
+
+ @Override
+ public AbstractValue getAbstractValue(
+ AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+ return appView
+ .abstractValueFactory()
+ .createSingleDexItemBasedStringValue(item, nameComputationInfo);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index efc52d1..36eeadf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -685,6 +685,7 @@
values.add(value);
assert value.uniquePhiUsers().contains(phi);
assert !phi.hasLocalInfo() || phi.getLocalInfo() == value.getLocalInfo();
+ assert value.isPhi() || value.definition.hasBlock();
}
}
for (Instruction instruction : block.getInstructions()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index e747891..7d32802 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -54,9 +54,9 @@
}
}
ThreadUtils.processItems(wave, this::convertClass, executorService);
+ methodProcessor.awaitMethodProcessing();
classes = deferred;
}
- methodProcessor.awaitMethodProcessing();
}
abstract void convertClass(DexProgramClass clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 11a9d00..4131111 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -748,17 +748,15 @@
if (inliner != null) {
postMethodProcessorBuilder.put(inliner);
}
+ if (!options.debug) {
+ new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
+ .run(executorService, feedback, timing);
+ }
if (enumUnboxer != null) {
enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
} else {
appView.setUnboxedEnums(EnumDataMap.empty());
}
-
- if (!options.debug) {
- new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
- .run(executorService, feedback, timing);
- }
-
timing.begin("IR conversion phase 2");
graphLensForIR = appView.graphLens();
PostMethodProcessor postMethodProcessor =
@@ -1759,6 +1757,8 @@
DexEncodedMethod method = code.method();
// Workaround massive dex2oat memory use for self-recursive methods.
CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
+ // Workaround MAX_INT switch issue.
+ codeRewriter.rewriteSwitchForMaxInt(code);
// Perform register allocation.
RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
timing.begin("Build DEX code");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index c44ce41..d91b447 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -4,12 +4,19 @@
package com.android.tools.r8.ir.conversion;
+import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
+import static com.android.tools.r8.ir.code.Invoke.Type.INTERFACE;
+import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
+import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
+import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
+
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.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
@@ -56,14 +63,14 @@
public void registerInvokeVirtual(DexMethod method) {
registerBackportedMethodRewriting(method);
registerLibraryRetargeting(method, false);
- registerInterfaceMethodRewriting(method, false);
+ registerInterfaceMethodRewriting(method, VIRTUAL);
registerDesugaredLibraryAPIConverter(method);
}
@Override
public void registerInvokeDirect(DexMethod method) {
registerLibraryRetargeting(method, false);
- registerInterfaceMethodRewriting(method, false);
+ registerInterfaceMethodRewriting(method, DIRECT);
registerDesugaredLibraryAPIConverter(method);
}
@@ -80,11 +87,11 @@
}
}
- private void registerInterfaceMethodRewriting(DexMethod method, boolean isInvokeSuper) {
+ private void registerInterfaceMethodRewriting(DexMethod method, Type invokeType) {
if (!needsDesugaring) {
needsDesugaring =
interfaceMethodRewriter != null
- && interfaceMethodRewriter.needsRewriting(method, isInvokeSuper, appView);
+ && interfaceMethodRewriter.needsRewriting(method, invokeType, appView);
}
}
@@ -109,14 +116,14 @@
registerTwrCloseResourceRewriting(method);
registerBackportedMethodRewriting(method);
registerLibraryRetargeting(method, false);
- registerInterfaceMethodRewriting(method, false);
+ registerInterfaceMethodRewriting(method, STATIC);
registerDesugaredLibraryAPIConverter(method);
}
@Override
public void registerInvokeInterface(DexMethod method) {
registerLibraryRetargeting(method, true);
- registerInterfaceMethodRewriting(method, false);
+ registerInterfaceMethodRewriting(method, INTERFACE);
registerDesugaredLibraryAPIConverter(method);
}
@@ -137,7 +144,7 @@
@Override
public void registerInvokeSuper(DexMethod method) {
registerLibraryRetargeting(method, false);
- registerInterfaceMethodRewriting(method, true);
+ registerInterfaceMethodRewriting(method, SUPER);
registerDesugaredLibraryAPIConverter(method);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index d2225e6..1b17172b5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -4,12 +4,18 @@
package com.android.tools.r8.ir.desugar;
+import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
+import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
+import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
+import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
+import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
import com.android.tools.r8.DesugarGraphConsumer;
import com.android.tools.r8.cf.CfVersion;
@@ -47,6 +53,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -226,13 +233,10 @@
return emulatedInterfaces.containsKey(itf);
}
- public boolean needsRewriting(DexMethod method, boolean isInvokeSuper, AppView<?> appView) {
- if (isInvokeSuper) {
+ public boolean needsRewriting(DexMethod method, Type invokeType, AppView<?> appView) {
+ if (invokeType == SUPER || invokeType == STATIC || invokeType == DIRECT) {
DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
- if (clazz != null
- && clazz.isLibraryClass()
- && clazz.isInterface()
- && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
+ if (clazz != null && clazz.isInterface()) {
return true;
}
}
@@ -364,6 +368,7 @@
// This can be a private instance method call. Note that the referenced
// method is expected to be in the current class since it is private, but desugaring
// may move some methods or their code into other classes.
+ assert needsRewriting(method, DIRECT, appView);
instructions.replaceCurrentInstruction(
new InvokeStatic(
directTarget.getDefinition().isPrivateMethod()
@@ -377,6 +382,7 @@
appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
if (virtualTarget != null) {
// This is a invoke-direct call to a virtual method.
+ assert needsRewriting(method, DIRECT, appView);
instructions.replaceCurrentInstruction(
new InvokeStatic(
defaultAsMethodOfCompanionClass(virtualTarget),
@@ -459,6 +465,7 @@
.setStaticTarget(invokedMethod, true)
.setStaticSource(m)
.build()));
+ assert needsRewriting(invokedMethod, STATIC, appView);
instructions.replaceCurrentInstruction(
new InvokeStatic(
newProgramMethod.getReference(), invoke.outValue(), invoke.arguments()));
@@ -481,6 +488,7 @@
.resolveMethodOnInterface(clazz, invokedMethod)
.asSingleResolution();
if (resolutionResult != null && resolutionResult.getResolvedMethod().isStatic()) {
+ assert needsRewriting(invokedMethod, STATIC, appView);
instructions.replaceCurrentInstruction(
new InvokeStatic(
staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
@@ -507,6 +515,7 @@
instructions.previous();
instructions.add(throwInvoke);
instructions.next();
+ assert needsRewriting(invokedMethod, STATIC, appView);
instructions.replaceCurrentInstructionWithThrow(
appView, code, blockIterator, throwInvoke.outValue(), blocksToRemove, affectedValues);
}
@@ -533,6 +542,7 @@
//
// WARNING: This may result in incorrect code on older platforms!
// Retarget call to an appropriate method of companion class.
+ assert needsRewriting(invokedMethod, SUPER, appView);
DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
instructions.replaceCurrentInstruction(
new InvokeStatic(
@@ -548,6 +558,7 @@
if (target != null && target.getDefinition().isDefaultMethod()) {
DexClass holder = target.getHolder();
if (holder.isLibraryClass() && holder.isInterface()) {
+ assert needsRewriting(invokedMethod, SUPER, appView);
instructions.replaceCurrentInstruction(
new InvokeStatic(
defaultAsMethodOfCompanionClass(target),
@@ -577,9 +588,11 @@
factory.protoWithDifferentFirstParameter(
originalCompanionMethod.proto, emulatedItf),
originalCompanionMethod.name);
+ assert needsRewriting(invokedMethod, SUPER, appView);
instructions.replaceCurrentInstruction(
new InvokeStatic(companionMethod, invoke.outValue(), invoke.arguments()));
} else {
+ assert needsRewriting(invokedMethod, SUPER, appView);
instructions.replaceCurrentInstruction(
new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
}
@@ -606,6 +619,7 @@
if (resolution != null
&& (resolution.getResolvedHolder().isLibraryClass()
|| appView.options().isDesugaredLibraryCompilation())) {
+ assert needsRewriting(invokedMethod, VIRTUAL, appView);
rewriteCurrentInstructionToEmulatedInterfaceCall(
emulatedItf, invokedMethod, invoke, instructions);
}
@@ -940,6 +954,11 @@
return type.descriptor.toString().endsWith(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
}
+ public static boolean isTypeWrapper(DexType type) {
+ String name = type.toBinaryName();
+ return name.endsWith(TYPE_WRAPPER_SUFFIX) || name.endsWith(VIVIFIED_TYPE_WRAPPER_SUFFIX);
+ }
+
// Gets the interface class for a companion class `type`.
private DexType getInterfaceClassType(DexType type) {
return getInterfaceClassType(type, factory);
@@ -1274,7 +1293,7 @@
private boolean shouldIgnoreFromReports(DexType missing) {
return appView.rewritePrefix.hasRewrittenType(missing, appView)
- || missing.isD8R8SynthesizedClassType()
+ || isTypeWrapper(missing)
|| isCompanionClassType(missing)
|| emulatedInterfaces.containsValue(missing)
|| options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
index 41f72cb..85ac5b8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Value;
import com.google.common.collect.Sets;
+import java.util.Collection;
import java.util.Set;
/**
@@ -62,6 +63,14 @@
}
}
+ public void markUnusedAssumeValuesForRemoval(Collection<Value> values) {
+ for (Value value : values) {
+ if (value.isDefinedByInstructionSatisfying(Instruction::isAssume) && !value.hasAnyUsers()) {
+ markForRemoval(value.getDefinition().asAssume());
+ }
+ }
+ }
+
private void markForRemoval(Assume assumeInstruction) {
assumeInstructionsToRemove.add(assumeInstruction);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5112116..3fa8be0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -961,7 +961,61 @@
if (!code.metadata().mayHaveSwitch()) {
return false;
}
+ return rewriteSwitchFull(code, switchCaseAnalyzer);
+ }
+ public void rewriteSwitchForMaxInt(IRCode code) {
+ if (options.canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) {
+ // Always rewrite for workaround switch bug.
+ rewriteSwitchForMaxIntOnly(code);
+ }
+ }
+
+ private void rewriteSwitchForMaxIntOnly(IRCode code) {
+ boolean needToSplitCriticalEdges = false;
+ ListIterator<BasicBlock> blocksIterator = code.listIterator();
+ while (blocksIterator.hasNext()) {
+ BasicBlock block = blocksIterator.next();
+ InstructionListIterator iterator = block.listIterator(code);
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ assert !instruction.isStringSwitch();
+ if (instruction.isIntSwitch()) {
+ IntSwitch intSwitch = instruction.asIntSwitch();
+ if (intSwitch.getKey(intSwitch.numberOfKeys() - 1) == Integer.MAX_VALUE) {
+ if (intSwitch.numberOfKeys() == 1) {
+ rewriteSingleKeySwitchToIf(code, block, iterator, intSwitch);
+ } else {
+ IntList newSwitchSequences = new IntArrayList(intSwitch.numberOfKeys() - 1);
+ for (int i = 0; i < intSwitch.numberOfKeys() - 1; i++) {
+ newSwitchSequences.add(intSwitch.getKey(i));
+ }
+ IntList outliers = new IntArrayList(1);
+ outliers.add(Integer.MAX_VALUE);
+ convertSwitchToSwitchAndIfs(
+ code,
+ blocksIterator,
+ block,
+ iterator,
+ intSwitch,
+ ImmutableList.of(newSwitchSequences),
+ outliers);
+ }
+ needToSplitCriticalEdges = true;
+ }
+ }
+ }
+ }
+
+ // Rewriting of switches introduces new branching structure. It relies on critical edges
+ // being split on the way in but does not maintain this property. We therefore split
+ // critical edges at exit.
+ if (needToSplitCriticalEdges) {
+ code.splitCriticalEdges();
+ }
+ }
+
+ private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
boolean needToRemoveUnreachableBlocks = false;
ListIterator<BasicBlock> blocksIterator = code.listIterator();
while (blocksIterator.hasNext()) {
@@ -1010,6 +1064,29 @@
return !affectedValues.isEmpty();
}
+ private void rewriteSingleKeySwitchToIf(
+ IRCode code, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) {
+ // Rewrite the switch to an if.
+ int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
+ int caseBlockIndex = theSwitch.targetBlockIndices()[0];
+ if (fallthroughBlockIndex < caseBlockIndex) {
+ block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
+ }
+ If replacement;
+ if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) {
+ replacement = new If(Type.EQ, theSwitch.value());
+ } else {
+ Instruction labelConst = theSwitch.materializeFirstKey(appView, code);
+ labelConst.setPosition(theSwitch.getPosition());
+ iterator.previous();
+ iterator.add(labelConst);
+ Instruction dummy = iterator.next();
+ assert dummy == theSwitch;
+ replacement = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue()));
+ }
+ iterator.replaceCurrentInstruction(replacement);
+ }
+
private void rewriteIntSwitch(
IRCode code,
ListIterator<BasicBlock> blockIterator,
@@ -1017,25 +1094,7 @@
InstructionListIterator iterator,
IntSwitch theSwitch) {
if (theSwitch.numberOfKeys() == 1) {
- // Rewrite the switch to an if.
- int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
- int caseBlockIndex = theSwitch.targetBlockIndices()[0];
- if (fallthroughBlockIndex < caseBlockIndex) {
- block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
- }
- If replacement;
- if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) {
- replacement = new If(Type.EQ, theSwitch.value());
- } else {
- Instruction labelConst = theSwitch.materializeFirstKey(appView, code);
- labelConst.setPosition(theSwitch.getPosition());
- iterator.previous();
- iterator.add(labelConst);
- Instruction dummy = iterator.next();
- assert dummy == theSwitch;
- replacement = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue()));
- }
- iterator.replaceCurrentInstruction(replacement);
+ rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch);
return;
}
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 38c4e6f..7eca624 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
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -745,6 +746,9 @@
if (lambdaMerger != null) {
lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
}
+ if (options.testing.inlineeIrModifier != null) {
+ options.testing.inlineeIrModifier.accept(code);
+ }
assert code.isConsistentSSA();
return new InlineeWithReason(code, reason);
}
@@ -982,13 +986,8 @@
continue;
}
- if (invoke.isInvokeMethodWithReceiver()) {
- if (iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context)) {
- continue;
- }
- } else if (invoke.isInvokeStatic()
- && iterator.replaceCurrentInstructionByInitClassIfPossible(
- appView, code, resolutionResult.getResolvedHolder().getType())) {
+ if (tryInlineMethodWithoutSideEffects(
+ code, iterator, invoke, resolutionResult.getResolutionPair(), assumeRemover)) {
continue;
}
@@ -1121,6 +1120,30 @@
assert code.isConsistentSSA();
}
+ private boolean tryInlineMethodWithoutSideEffects(
+ IRCode code,
+ InstructionListIterator iterator,
+ InvokeMethod invoke,
+ DexClassAndMethod resolvedMethod,
+ AssumeRemover assumeRemover) {
+ if (invoke.isInvokeMethodWithReceiver()) {
+ if (!iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context())) {
+ return false;
+ }
+ } else if (invoke.isInvokeStatic()) {
+ if (!iterator.replaceCurrentInstructionByInitClassIfPossible(
+ appView, code, resolvedMethod.getHolderType())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ // Succeeded.
+ assumeRemover.markUnusedAssumeValuesForRemoval(invoke.arguments());
+ return true;
+ }
+
private boolean containsPotentialCatchHandlerVerificationError(IRCode code) {
if (availableApiExceptions == null) {
assert !appView.options().canHaveDalvikCatchHandlerVerificationBug();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index d92d375..2d36f99 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -170,7 +170,9 @@
assert replacement.outValue() != null;
current.outValue().replaceUsers(replacement.outValue());
}
- if (current.isStaticGet()) {
+ if (current.isInstanceGet()) {
+ iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
+ } else if (current.isStaticGet()) {
StaticGet staticGet = current.asStaticGet();
iterator.replaceCurrentInstructionByInitClassIfPossible(
appView, code, staticGet.getField().holder);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 10b8b25..f802efe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.FieldInstruction;
@@ -235,24 +237,7 @@
activeState.putNonFinalInstanceField(fieldAndObject, value);
}
} else if (instruction.isStaticGet()) {
- StaticGet staticGet = instruction.asStaticGet();
- if (staticGet.outValue().hasLocalInfo()) {
- continue;
- }
- FieldValue replacement = activeState.getStaticFieldValue(reference);
- if (replacement != null) {
- replacement.eliminateRedundantRead(it, staticGet);
- } else {
- // A field get on a different class can cause <clinit> to run and change static
- // field values.
- killNonFinalActiveFields(staticGet);
- FieldValue value = new ExistingValue(staticGet.value());
- if (isFinal(field)) {
- activeState.putFinalStaticField(reference, value);
- } else {
- activeState.putNonFinalStaticField(reference, value);
- }
- }
+ handleStaticGet(it, instruction.asStaticGet(), field);
} else if (instruction.isStaticPut()) {
StaticPut staticPut = instruction.asStaticPut();
// A field put on a different class can cause <clinit> to run and change static
@@ -409,6 +394,52 @@
});
}
+ private void handleStaticGet(
+ InstructionListIterator instructionIterator, StaticGet staticGet, DexClassAndField field) {
+ if (staticGet.outValue().hasLocalInfo()) {
+ return;
+ }
+ FieldValue replacement = activeState.getStaticFieldValue(field.getReference());
+ if (replacement != null) {
+ replacement.eliminateRedundantRead(instructionIterator, staticGet);
+ return;
+ }
+
+ // A field get on a different class can cause <clinit> to run and change static field values.
+ if (staticGet.instructionMayHaveSideEffects(appView, method)) {
+ killNonFinalActiveFields(staticGet);
+ }
+
+ FieldValue value = new ExistingValue(staticGet.value());
+ if (isFinal(field)) {
+ activeState.putFinalStaticField(field.getReference(), value);
+ } else {
+ activeState.putNonFinalStaticField(field.getReference(), value);
+ }
+
+ if (appView.hasLiveness()) {
+ SingleFieldValue singleFieldValue =
+ field.getDefinition().getOptimizationInfo().getAbstractValue().asSingleFieldValue();
+ if (singleFieldValue != null) {
+ applyObjectState(staticGet.outValue(), singleFieldValue.getState());
+ }
+ }
+ }
+
+ private void applyObjectState(Value value, ObjectState objectState) {
+ objectState.forEachAbstractFieldValue(
+ (field, fieldValue) -> {
+ if (appView.appInfoWithLiveness().mayPropagateValueFor(field)
+ && fieldValue.isSingleValue()) {
+ SingleValue singleFieldValue = fieldValue.asSingleValue();
+ if (singleFieldValue.isMaterializableInContext(appView.withLiveness(), method)) {
+ activeState.putFinalInstanceField(
+ new FieldAndObject(field, value), new MaterializableValue(singleFieldValue));
+ }
+ }
+ });
+ }
+
private void killAllNonFinalActiveFields() {
activeState.clearNonFinalInstanceFields();
activeState.clearNonFinalStaticFields();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 0ce08a5..19a9da1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -62,6 +62,7 @@
import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.WorkList;
@@ -429,7 +430,8 @@
anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider);
- removeAliasIntroducingInstructionsLinkedToEligibleInstance();
+
+ rebindIndirectEligibleInstanceUsersFromPhis();
removeMiscUsages(code, affectedValues);
removeFieldReads(code);
removeFieldWrites();
@@ -594,47 +596,65 @@
return true;
}
- private void removeAliasIntroducingInstructionsLinkedToEligibleInstance() {
- Set<Instruction> currentUsers = eligibleInstance.uniqueUsers();
- while (!currentUsers.isEmpty()) {
- Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
- for (Instruction instruction : currentUsers) {
+ private void rebindIndirectEligibleInstanceUsersFromPhis() {
+ // Building the inlinee can cause some of the eligibleInstance users to be phi's. These phi's
+ // should be trivial.
+ // block X:
+ // vX <- NewInstance ...
+ // block Y:
+ // vZ : phi(vX, vY)
+ // block Z
+ // vY : phi(vX, vZ)
+ // These are not pruned by the trivial phi removal. We have to ensure that we rewrite all users
+ // also the indirect users directly using phi's, potentially through assumes and checkcast.
+ Set<Value> aliases = SetUtils.newIdentityHashSet(eligibleInstance);
+ Set<Phi> expectedDeadOrTrivialPhis = Sets.newIdentityHashSet();
+ WorkList<InstructionOrPhi> worklist = WorkList.newIdentityWorkList();
+ eligibleInstance.uniqueUsers().forEach(worklist::addIfNotSeen);
+ eligibleInstance.uniquePhiUsers().forEach(worklist::addIfNotSeen);
+ while (worklist.hasNext()) {
+ InstructionOrPhi instructionOrPhi = worklist.next();
+ if (instructionOrPhi.isPhi()) {
+ Phi phi = instructionOrPhi.asPhi();
+ expectedDeadOrTrivialPhis.add(phi);
+ phi.uniqueUsers().forEach(worklist::addIfNotSeen);
+ phi.uniquePhiUsers().forEach(worklist::addIfNotSeen);
+ } else {
+ Instruction instruction = instructionOrPhi.asInstruction();
if (aliasesThroughAssumeAndCheckCasts.isIntroducingAnAlias(instruction)) {
- Value src = aliasesThroughAssumeAndCheckCasts.getAliasForOutValue(instruction);
- Value dest = instruction.outValue();
- if (dest.hasPhiUsers()) {
- // It is possible that a trivial phi is constructed upon IR building for the eligible
- // value. It must actually be trivial so verify that it is indeed trivial and replace
- // all of the phis involved with the value.
- WorkList<Phi> worklist = WorkList.newIdentityWorkList(dest.uniquePhiUsers());
- while (worklist.hasNext()) {
- Phi phi = worklist.next();
- for (Value operand : phi.getOperands()) {
- operand = operand.getAliasedValue(aliasesThroughAssumeAndCheckCasts);
- if (operand.isPhi()) {
- worklist.addIfNotSeen(operand.asPhi());
- } else if (src != operand) {
- throw new InternalCompilerError(
- "Unexpected non-trivial phi in method eligible for class inlining");
- }
- }
- }
- // The only value flowing into any of the phis is src, so replace all phis by src.
- for (Phi phi : worklist.getSeenSet()) {
- indirectOutValueUsers.addAll(phi.uniqueUsers());
- phi.replaceUsers(src);
- phi.removeDeadPhi();
- }
- }
- assert !dest.hasPhiUsers();
- indirectOutValueUsers.addAll(dest.uniqueUsers());
- dest.replaceUsers(src);
- removeInstruction(instruction);
+ aliases.add(instruction.outValue());
+ instruction.outValue().uniqueUsers().forEach(worklist::addIfNotSeen);
+ instruction.outValue().uniquePhiUsers().forEach(worklist::addIfNotSeen);
}
}
- currentUsers = indirectOutValueUsers;
}
-
+ // Check that all phis are dead or trivial.
+ for (Phi deadTrivialPhi : expectedDeadOrTrivialPhis) {
+ for (Value operand : deadTrivialPhi.getOperands()) {
+ operand = operand.getAliasedValue(aliasesThroughAssumeAndCheckCasts);
+ // If the operand is a phi we should have found it in the search above.
+ if (operand.isPhi() && !expectedDeadOrTrivialPhis.contains(operand.asPhi())) {
+ throw new InternalCompilerError(
+ "Unexpected non-trivial phi in method eligible for class inlining");
+ }
+ // If the operand is not a phi, it should be an alias (or the eligibleInstance).
+ if (!operand.isPhi() && !aliases.contains(operand)) {
+ throw new InternalCompilerError(
+ "Unexpected non-trivial phi in method eligible for class inlining");
+ }
+ }
+ deadTrivialPhi.replaceUsers(eligibleInstance);
+ deadTrivialPhi.removeDeadPhi();
+ }
+ // We can now prune the aliases
+ for (Value alias : aliases) {
+ if (alias == eligibleInstance) {
+ continue;
+ }
+ assert alias.definition.isAssume() || alias.definition.isCheckCast();
+ alias.replaceUsers(eligibleInstance);
+ removeInstruction(alias.definition);
+ }
// Verify that no more assume or check-cast instructions are left as users.
assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isAssume);
assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isCheckCast);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 166022e..27deb32 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -63,6 +63,7 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.Reporter;
@@ -388,11 +389,17 @@
// Update keep info on any of the enum methods of the removed classes.
updateKeepInfo(enumsToUnbox);
DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
+ FieldAccessInfoCollectionModifier.builder();
UnboxedEnumMemberRelocator relocator =
UnboxedEnumMemberRelocator.builder(appView)
.synthesizeEnumUnboxingUtilityClasses(
- enumClassesToUnbox, enumsToUnboxWithPackageRequirement, appBuilder)
+ enumClassesToUnbox,
+ enumsToUnboxWithPackageRequirement,
+ appBuilder,
+ fieldAccessInfoCollectionModifierBuilder)
.build();
+ fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator);
EnumUnboxingLens enumUnboxingLens =
new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
@@ -491,34 +498,38 @@
// Step 1: We iterate over the field to find direct enum instance information and the values
// fields.
for (DexEncodedField staticField : enumClass.staticFields()) {
- if (EnumUnboxingCandidateAnalysis.isEnumField(staticField, enumClass.type)) {
+ if (factory.enumMembers.isEnumField(staticField, enumClass.type)) {
ObjectState enumState =
enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
- if (enumState != null) {
- OptionalInt optionalOrdinal = getOrdinal(enumState);
- if (!optionalOrdinal.isPresent()) {
- return null;
+ if (enumState == null) {
+ if (staticField.getOptimizationInfo().isDead()) {
+ // We don't care about unused field data.
+ continue;
}
- int ordinal = optionalOrdinal.getAsInt();
- unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
- ordinalToObjectState.put(ordinal, enumState);
- }
- } else if (EnumUnboxingCandidateAnalysis.matchesValuesField(
- staticField, enumClass.type, factory)) {
- AbstractValue valuesValue =
- enumStaticFieldValues.getValuesAbstractValueForPossiblyPinnedField(staticField.field);
- if (valuesValue == null || valuesValue.isZero()) {
- // Unused field
- continue;
- }
- if (valuesValue.isUnknown()) {
+ // We could not track the content of that field. We bail out.
return null;
}
- assert valuesValue.isSingleFieldValue();
- ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
- if (!valuesState.isEnumValuesObjectState()) {
+ OptionalInt optionalOrdinal = getOrdinal(enumState);
+ if (!optionalOrdinal.isPresent()) {
return null;
}
+ int ordinal = optionalOrdinal.getAsInt();
+ unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
+ ordinalToObjectState.put(ordinal, enumState);
+ } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
+ ObjectState valuesState =
+ enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
+ if (valuesState == null) {
+ if (staticField.getOptimizationInfo().isDead()) {
+ // We don't care about unused field data.
+ continue;
+ }
+ // We could not track the content of that field. We bail out.
+ // We could not track the content of that field, and the field could be a values field.
+ // We conservatively bail out.
+ return null;
+ }
+ assert valuesState.isEnumValuesObjectState();
assert valuesContents == null
|| valuesContents.equals(valuesState.asEnumValuesObjectState());
valuesContents = valuesState.asEnumValuesObjectState();
@@ -564,6 +575,13 @@
valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
}
+ private boolean isFinalFieldInitialized(DexEncodedField staticField, DexProgramClass enumClass) {
+ assert staticField.isFinal();
+ return appView
+ .appInfo()
+ .isFieldOnlyWrittenInMethodIgnoringPinning(staticField, enumClass.getClassInitializer());
+ }
+
private EnumInstanceFieldData computeEnumFieldData(
DexField instanceField,
DexProgramClass enumClass,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 6c20c4f..1602032 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -21,8 +20,6 @@
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.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.MethodAccessFlags;
@@ -78,7 +75,6 @@
private NestedGraphLens enumUnboxingLens;
private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
- private final Map<DexField, DexEncodedField> utilityFields = new ConcurrentHashMap<>();
private final DexMethod ordinalUtilityMethod;
private final DexMethod equalsUtilityMethod;
@@ -279,7 +275,6 @@
utilityMethods.computeIfAbsent(
valuesUtilityMethod, m -> synthesizeValuesUtilityMethod());
DexField fieldValues = createValuesField(holder);
- utilityFields.computeIfAbsent(fieldValues, this::computeValuesEncodedField);
DexMethod methodValues = createValuesMethod(holder);
utilityMethods.computeIfAbsent(
methodValues,
@@ -426,32 +421,27 @@
return unboxedEnumsData.isUnboxedEnum(enumType) ? enumType : null;
}
- public String compatibleName(DexType type) {
+ public static String compatibleName(DexType type) {
return type.toSourceString().replace('.', '$');
}
private DexField createValuesField(DexType enumType) {
- return factory.createField(
- relocator.getNewMemberLocationFor(enumType),
- factory.intArrayType,
- factory.enumValuesFieldName + "$field$" + compatibleName(enumType));
+ return createValuesField(enumType, relocator.getNewMemberLocationFor(enumType), factory);
}
- private DexEncodedField computeValuesEncodedField(DexField field) {
- return new DexEncodedField(
- field,
- FieldAccessFlags.fromSharedAccessFlags(
- Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC),
- FieldTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- null);
+ static DexField createValuesField(
+ DexType enumType, DexType enumUtilityClass, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createField(
+ enumUtilityClass,
+ dexItemFactory.intArrayType,
+ "$$values$$field$" + compatibleName(enumType));
}
private DexMethod createValuesMethod(DexType enumType) {
return factory.createMethod(
relocator.getNewMemberLocationFor(enumType),
factory.createProto(factory.intArrayType),
- factory.enumValuesFieldName + "$method$" + compatibleName(enumType));
+ "$$values$$method$" + compatibleName(enumType));
}
private DexEncodedMethod computeValuesEncodedMethod(
@@ -533,7 +523,6 @@
// fields required.
Map<DexType, List<DexEncodedMethod>> methodMap = triageEncodedMembers(utilityMethods.values());
if (methodMap.isEmpty()) {
- assert utilityFields.isEmpty();
return;
}
SortedProgramMethodSet wave = SortedProgramMethodSet.create();
@@ -546,13 +535,6 @@
wave.add(new ProgramMethod(utilityClass, dexEncodedMethod));
}
});
- Map<DexType, List<DexEncodedField>> fieldMap = triageEncodedMembers(utilityFields.values());
- fieldMap.forEach(
- (type, fieldsSorted) -> {
- DexProgramClass utilityClass = appView.definitionFor(type).asProgramClass();
- assert utilityClass != null;
- utilityClass.appendStaticFields(fieldsSorted);
- });
converter.processMethodsConcurrently(wave, executorService);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 10a50ca..bcd8458 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -193,7 +193,15 @@
if (newType != field.type) {
DexField newField = factory.createField(field.holder, newType, field.name);
lensBuilder.move(field, newField);
- DexEncodedField newEncodedField = encodedField.toTypeSubstitutedField(newField);
+ DexEncodedField newEncodedField =
+ encodedField.toTypeSubstitutedField(
+ newField,
+ builder ->
+ builder.fixupOptimizationInfo(
+ mutableFieldOptimizationInfo -> {
+ mutableFieldOptimizationInfo.setAbstractValue(
+ encodedField.getOptimizationInfo().getAbstractValue());
+ }));
setter.setField(i, newEncodedField);
if (encodedField.isStatic() && encodedField.hasExplicitStaticValue()) {
assert encodedField.getStaticValue() == DexValue.DexValueNull.NULL;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
index a0ea10c..5a8ae19 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
@@ -4,23 +4,32 @@
package com.android.tools.r8.ir.optimize.enums;
+import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
+
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.ProgramPackage;
import com.android.tools.r8.graph.ProgramPackageCollection;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -56,7 +65,7 @@
}
public static class Builder {
- private DexType defaultEnumUnboxingUtility;
+ private DexProgramClass defaultEnumUnboxingUtility;
private Map<DexType, DexType> relocationMap = new IdentityHashMap<>();
private final AppView<?> appView;
@@ -67,44 +76,84 @@
public Builder synthesizeEnumUnboxingUtilityClasses(
Set<DexProgramClass> enumsToUnbox,
ProgramPackageCollection enumsToUnboxWithPackageRequirement,
- DirectMappedDexApplication.Builder appBuilder) {
- defaultEnumUnboxingUtility = synthesizeUtilityClass(enumsToUnbox, appBuilder);
+ DirectMappedDexApplication.Builder appBuilder,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ Set<DexProgramClass> enumsToUnboxWithoutPackageRequirement =
+ SetUtils.newIdentityHashSet(enumsToUnbox);
+ enumsToUnboxWithoutPackageRequirement.removeIf(enumsToUnboxWithPackageRequirement::contains);
+ defaultEnumUnboxingUtility =
+ synthesizeUtilityClass(
+ enumsToUnbox,
+ enumsToUnboxWithoutPackageRequirement,
+ appBuilder,
+ fieldAccessInfoCollectionModifierBuilder);
if (!enumsToUnboxWithPackageRequirement.isEmpty()) {
- synthesizeRelocationMap(enumsToUnboxWithPackageRequirement, appBuilder);
+ synthesizeRelocationMap(
+ enumsToUnbox,
+ enumsToUnboxWithPackageRequirement,
+ appBuilder,
+ fieldAccessInfoCollectionModifierBuilder);
}
return this;
}
public UnboxedEnumMemberRelocator build() {
return new UnboxedEnumMemberRelocator(
- defaultEnumUnboxingUtility, ImmutableMap.copyOf(relocationMap));
+ defaultEnumUnboxingUtility.getType(), ImmutableMap.copyOf(relocationMap));
}
private void synthesizeRelocationMap(
+ Set<DexProgramClass> contexts,
ProgramPackageCollection enumsToUnboxWithPackageRequirement,
- DirectMappedDexApplication.Builder appBuilder) {
+ DirectMappedDexApplication.Builder appBuilder,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
for (ProgramPackage programPackage : enumsToUnboxWithPackageRequirement) {
Set<DexProgramClass> enumsToUnboxInPackage = programPackage.classesInPackage();
- DexType utilityType = synthesizeUtilityClass(enumsToUnboxInPackage, appBuilder);
- for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
- assert !relocationMap.containsKey(enumToUnbox.type);
- relocationMap.put(enumToUnbox.type, utilityType);
+ DexProgramClass enumUtilityClass =
+ synthesizeUtilityClass(
+ contexts,
+ enumsToUnboxInPackage,
+ appBuilder,
+ fieldAccessInfoCollectionModifierBuilder);
+ if (enumUtilityClass != defaultEnumUnboxingUtility) {
+ for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
+ assert !relocationMap.containsKey(enumToUnbox.type);
+ relocationMap.put(enumToUnbox.type, enumUtilityClass.getType());
+ }
}
}
}
- private DexType synthesizeUtilityClass(
- Set<DexProgramClass> contexts, DirectMappedDexApplication.Builder appBuilder) {
+ private DexProgramClass synthesizeUtilityClass(
+ Set<DexProgramClass> contexts,
+ Set<DexProgramClass> relocatedEnums,
+ DirectMappedDexApplication.Builder appBuilder,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
DexType deterministicContextType = findDeterministicContextType(contexts);
assert deterministicContextType.isClassType();
String descriptorString = deterministicContextType.toDescriptorString();
String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
DexType type = appView.dexItemFactory().createType(syntheticClassDescriptor);
+
+ // Required fields.
+ List<DexEncodedField> staticFields = new ArrayList<>(relocatedEnums.size());
+ for (DexProgramClass relocatedEnum : relocatedEnums) {
+ DexField reference =
+ createValuesField(relocatedEnum.getType(), type, appView.dexItemFactory());
+ staticFields.add(
+ new DexEncodedField(reference, FieldAccessFlags.createPublicStaticSynthetic()));
+ fieldAccessInfoCollectionModifierBuilder
+ .recordFieldReadInUnknownContext(reference)
+ .recordFieldWriteInUnknownContext(reference);
+ }
+ staticFields.sort(Comparator.comparing(DexEncodedField::getReference));
+
// The defaultEnumUnboxingUtility depends on all unboxable enums, and other synthetic types
// depend on a subset of the unboxable enums, the deterministicContextType can therefore
// be found twice, and in that case the same utility class can be used for both.
- if (type == defaultEnumUnboxingUtility) {
+ if (defaultEnumUnboxingUtility != null && type == defaultEnumUnboxingUtility.getType()) {
+ defaultEnumUnboxingUtility.appendStaticFields(staticFields);
return defaultEnumUnboxingUtility;
}
assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
@@ -124,7 +173,7 @@
Collections.emptyList(),
ClassSignature.noSignature(),
DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY,
+ staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
DexEncodedField.EMPTY_ARRAY,
DexEncodedMethod.EMPTY_ARRAY,
DexEncodedMethod.EMPTY_ARRAY,
@@ -135,7 +184,7 @@
.appInfo()
.addSynthesizedClass(
syntheticClass, appView.appInfo().getMainDexClasses().containsAnyOf(contexts));
- return syntheticClass.type;
+ return syntheticClass;
}
private DexType findDeterministicContextType(Set<DexProgramClass> contexts) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 41ee658..3bf8d88 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -65,7 +65,7 @@
return abstractValue;
}
- void setAbstractValue(AbstractValue abstractValue) {
+ public void setAbstractValue(AbstractValue abstractValue) {
this.abstractValue = abstractValue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index e9ff9e8..5bccf4d4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -50,6 +50,7 @@
import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -364,6 +365,9 @@
// Switch to APPLY strategy.
this.mode = new ApplyMode(lambdaGroupsClasses.inverse(), optimizationInfoFixer);
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
+ FieldAccessInfoCollectionModifier.builder();
+
// Add synthesized lambda group classes to the builder.
for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
DexProgramClass synthesizedClass = entry.getValue();
@@ -371,6 +375,13 @@
.appInfo()
.addSynthesizedClass(synthesizedClass, entry.getKey().shouldAddToMainDex(appView));
builder.addSynthesizedClass(synthesizedClass);
+
+ synthesizedClass.forEachField(
+ field ->
+ fieldAccessInfoCollectionModifierBuilder
+ .recordFieldReadInUnknownContext(field.getReference())
+ .recordFieldWriteInUnknownContext(field.getReference()));
+
// Eventually, we need to process synthesized methods in the lambda group.
// Otherwise, abstract SynthesizedCode will be flown to Enqueuer.
// But that process should not see the holder. Otherwise, lambda calls in the main dispatch
@@ -383,6 +394,9 @@
encodedMethod -> encodedMethod.markProcessed(ConstraintWithTarget.NEVER));
}
+ // Record field accesses for synthesized fields.
+ fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
+
converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
// Rewrite lambda class references into lambda group class
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 9d84c1f..bf9f986 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -454,7 +454,8 @@
}
}
- assert false : "Must always be able to find and remove the instantiation";
+ assert candidateInfo.singletonField.getOptimizationInfo().isDead()
+ : "Must always be able to find and remove the instantiation";
}
private void removeReferencesToThis(IRCode code, MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
index 2498026..5785d0a 100644
--- a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
@@ -20,7 +20,7 @@
}
@Override
- public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+ public boolean isTypeCompatInstantiatedFromUse(InternalOptions options) {
return options.isForceProguardCompatibilityEnabled();
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
index 0305995..431e27d 100644
--- a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
@@ -20,7 +20,7 @@
}
@Override
- public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+ public boolean isTypeCompatInstantiatedFromUse(InternalOptions options) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
index f2c1ec1..2cdc3c4 100644
--- a/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
@@ -20,7 +20,7 @@
}
@Override
- public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+ public boolean isTypeCompatInstantiatedFromUse(InternalOptions options) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
index 6707406..7f4d69d 100644
--- a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
@@ -16,7 +16,7 @@
public abstract boolean isTypeInitializedFromUse();
- public abstract boolean isTypeInstantiatedFromUse(InternalOptions options);
+ public abstract boolean isTypeCompatInstantiatedFromUse(InternalOptions options);
@Override
public boolean isTypeResult() {
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
new file mode 100644
index 0000000..7e66ed3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.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.retrace;
+
+import com.android.tools.r8.Keep;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/** Interface for producing a string format of a mapping file. */
+@Keep
+public interface ProguardMapProducer {
+
+ String get() throws IOException;
+
+ static ProguardMapProducer fromReader(Reader reader) {
+ return () -> {
+ try (BufferedReader br = new BufferedReader(reader)) {
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line).append('\n');
+ }
+ return sb.toString();
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 0d66a64..76fad7d 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.Keep;
import com.android.tools.r8.Version;
import com.android.tools.r8.retrace.RetraceCommand.Builder;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
import com.android.tools.r8.retrace.internal.PlainStackTraceVisitor;
import com.android.tools.r8.retrace.internal.RetraceAbortException;
import com.android.tools.r8.retrace.internal.RetraceRegularExpression;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index d9f67ef..e346e3c 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -145,7 +145,4 @@
}
}
- public interface ProguardMapProducer {
- String get();
- }
}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index ed1d338..015dac8 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
import com.android.tools.r8.retrace.internal.RetracerImpl;
/** This is the main api interface for retrace. */
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
index 07bf237..db73155 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
@@ -5,7 +5,7 @@
package com.android.tools.r8.retrace.internal;
import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMapProducer;
public interface DirectClassNameMapperProguardMapProducer extends ProguardMapProducer {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index c2a6749..d25afab 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.retrace.InvalidMappingFileException;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMapProducer;
import com.android.tools.r8.retrace.Retracer;
/** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 363cb30..baa1189 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
@@ -161,7 +162,6 @@
private final Set<DexType> noClassMerging;
private final Set<DexType> noHorizontalClassMerging;
private final Set<DexType> noVerticalClassMerging;
- private final Set<DexType> noStaticClassMerging;
/**
* Set of lock candidates (i.e., types whose class reference may flow to a monitor instruction).
@@ -228,7 +228,6 @@
Set<DexType> noClassMerging,
Set<DexType> noVerticalClassMerging,
Set<DexType> noHorizontalClassMerging,
- Set<DexType> noStaticClassMerging,
Set<DexReference> neverPropagateValue,
Object2BooleanMap<DexReference> identifierNameStrings,
Set<DexType> prunedTypes,
@@ -267,7 +266,6 @@
this.noClassMerging = noClassMerging;
this.noVerticalClassMerging = noVerticalClassMerging;
this.noHorizontalClassMerging = noHorizontalClassMerging;
- this.noStaticClassMerging = noStaticClassMerging;
this.neverPropagateValue = neverPropagateValue;
this.identifierNameStrings = identifierNameStrings;
this.prunedTypes = prunedTypes;
@@ -314,7 +312,6 @@
previous.noClassMerging,
previous.noVerticalClassMerging,
previous.noHorizontalClassMerging,
- previous.noStaticClassMerging,
previous.neverPropagateValue,
previous.identifierNameStrings,
previous.prunedTypes,
@@ -362,7 +359,6 @@
previous.noClassMerging,
previous.noVerticalClassMerging,
previous.noHorizontalClassMerging,
- previous.noStaticClassMerging,
previous.neverPropagateValue,
previous.identifierNameStrings,
prunedItems.hasRemovedClasses()
@@ -455,7 +451,6 @@
this.noClassMerging = previous.noClassMerging;
this.noVerticalClassMerging = previous.noVerticalClassMerging;
this.noHorizontalClassMerging = previous.noHorizontalClassMerging;
- this.noStaticClassMerging = previous.noStaticClassMerging;
this.neverPropagateValue = previous.neverPropagateValue;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = previous.prunedTypes;
@@ -592,6 +587,10 @@
return noSideEffects.containsKey(method);
}
+ public boolean isAssumeNoSideEffectsMethod(DexClassAndMethod method) {
+ return isAssumeNoSideEffectsMethod(method.getReference());
+ }
+
public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
return whyAreYouNotInlining.contains(method);
}
@@ -743,7 +742,7 @@
DexType type = clazz.type;
return
// TODO(b/165224388): Synthetic classes should be represented in the allocation info.
- getSyntheticItems().isSyntheticClass(clazz)
+ getSyntheticItems().isLegacySyntheticClass(clazz)
|| (!clazz.isInterface() && objectAllocationInfoCollection.isInstantiatedDirectly(clazz))
// TODO(b/145344105): Model annotations in the object allocation info.
|| (clazz.isAnnotation() && liveTypes.contains(type));
@@ -769,11 +768,6 @@
if (keepInfo.isPinned(field, this)) {
return true;
}
- // Fields in the class that is synthesized by D8/R8 would be used soon.
- // TODO(b/165229577): Do we need this special handling of synthetics?
- if (getSyntheticItems().isSyntheticClass(field.holder)) {
- return true;
- }
// For library classes we don't know whether a field is read.
return isLibraryOrClasspathField(encodedField);
}
@@ -791,10 +785,6 @@
// The field is written directly by the program itself.
return true;
}
- // TODO(b/165229577): Do we need this special handling of synthetics?
- if (getSyntheticItems().isSyntheticClass(field.holder)) {
- return true;
- }
// For library classes we don't know whether a field is rewritten.
return isLibraryOrClasspathField(encodedField);
}
@@ -853,9 +843,9 @@
&& !keepInfo.getMethodInfo(method).isPinned();
}
- public boolean mayPropagateValueFor(DexMember<?, ?> reference) {
+ public boolean mayPropagateValueFor(DexClassAndMember<?, ?> member) {
assert checkIfObsolete();
- return reference.apply(this::mayPropagateValueFor, this::mayPropagateValueFor);
+ return member.getReference().apply(this::mayPropagateValueFor, this::mayPropagateValueFor);
}
public boolean mayPropagateValueFor(DexField field) {
@@ -944,6 +934,11 @@
return isPinned(definition.getReference());
}
+ public boolean isPinned(DexClassAndMember<?, ?> member) {
+ assert member != null;
+ return isPinned(member.getReference());
+ }
+
public boolean hasPinnedInstanceInitializer(DexType type) {
assert type.isClassType();
DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
@@ -1037,7 +1032,6 @@
lens.rewriteTypes(noClassMerging),
lens.rewriteTypes(noVerticalClassMerging),
lens.rewriteTypes(noHorizontalClassMerging),
- lens.rewriteTypes(noStaticClassMerging),
lens.rewriteReferences(neverPropagateValue),
lens.rewriteReferenceKeys(identifierNameStrings),
// Don't rewrite pruned types - the removed types are identified by their original name.
@@ -1402,12 +1396,4 @@
public boolean isNoVerticalClassMergingOfType(DexType type) {
return noClassMerging.contains(type) || noVerticalClassMerging.contains(type);
}
-
- /**
- * All types that *must* never be merged by the static class merger due to a configuration
- * directive (testing only).
- */
- public Set<DexType> getNoStaticClassMergingSet() {
- return noStaticClassMerging;
- }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
deleted file mode 100644
index 540db63..0000000
--- a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.shaking;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.SubtypingInfo;
-
-class ConsequentRootSetBuilder extends RootSetBuilder {
-
- private final Enqueuer enqueuer;
-
- ConsequentRootSetBuilder(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- SubtypingInfo subtypingInfo,
- Enqueuer enqueuer) {
- super(appView, subtypingInfo, null);
- this.enqueuer = enqueuer;
- }
-
- @Override
- void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
- if (enqueuer.getMode().isInitialTreeShaking()
- && annotationMatchResult.isConcreteAnnotationMatchResult()) {
- enqueuer.retainAnnotationForFinalTreeShaking(
- annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotations());
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
index 1c99cc6..b7a3810 100644
--- a/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
+++ b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import java.util.function.Consumer;
public abstract class DelayedRootSetActionItem {
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index bbcaf32..b04748c 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index bc50542..ca98aff 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -101,10 +101,12 @@
import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
import com.android.tools.r8.shaking.KeepInfo.Joiner;
import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
-import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
-import com.android.tools.r8.shaking.RootSetBuilder.ItemsWithRules;
-import com.android.tools.r8.shaking.RootSetBuilder.MutableItemsWithRules;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
+import com.android.tools.r8.shaking.RootSetUtils.ItemsWithRules;
+import com.android.tools.r8.shaking.RootSetUtils.MutableItemsWithRules;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.InternalOptions;
@@ -156,6 +158,7 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.function.Supplier;
/**
* Approximates the runtime dependencies for the given set of roots.
@@ -333,6 +336,8 @@
/** A queue of items that need processing. Different items trigger different actions. */
private final EnqueuerWorklist workList;
+ private final ProguardCompatibilityActions.Builder proguardCompatibilityActionsBuilder;
+
/** A set of methods that need code inspection for Java reflection in use. */
private final ProgramMethodSet pendingReflectiveUses = ProgramMethodSet.createLinked();
@@ -415,6 +420,10 @@
this.options = options;
this.useRegistryFactory = createUseRegistryFactory();
this.workList = EnqueuerWorklist.createWorklist();
+ this.proguardCompatibilityActionsBuilder =
+ mode.isInitialTreeShaking() && options.forceProguardCompatibility
+ ? ProguardCompatibilityActions.builder()
+ : null;
this.previousMainDexTracingResult = previousMainDexTracingResult;
if (mode.isInitialOrFinalTreeShaking()) {
@@ -1091,7 +1100,7 @@
if (baseClass != null) {
// Don't require any constructor, see b/112386012.
markClassAsInstantiatedWithCompatRule(
- baseClass, graphReporter.reportCompatInstantiated(baseClass, currentMethod));
+ baseClass, () -> graphReporter.reportCompatInstantiated(baseClass, currentMethod));
}
}
}
@@ -1448,6 +1457,12 @@
return;
}
+ assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+ : "Unexpected reference in `"
+ + currentMethod.toSourceString()
+ + "` to field marked dead: "
+ + field.getReference().toSourceString();
+
if (fromMethodHandle) {
fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
}
@@ -1498,6 +1513,12 @@
return;
}
+ assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+ : "Unexpected reference in `"
+ + currentMethod.toSourceString()
+ + "` to field marked dead: "
+ + field.getReference().toSourceString();
+
if (fromMethodHandle) {
fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
}
@@ -1551,6 +1572,12 @@
return;
}
+ assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+ : "Unexpected reference in `"
+ + currentMethod.toSourceString()
+ + "` to field marked dead: "
+ + field.getReference().toSourceString();
+
if (fromMethodHandle) {
fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
}
@@ -1608,6 +1635,12 @@
return;
}
+ assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+ : "Unexpected reference in `"
+ + currentMethod.toSourceString()
+ + "` to field marked dead: "
+ + field.getReference().toSourceString();
+
if (fromMethodHandle) {
fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
}
@@ -2189,7 +2222,7 @@
private void keepClassAndAllMembers(DexProgramClass clazz, KeepReason keepReason) {
KeepReasonWitness keepReasonWitness = graphReporter.registerClass(clazz, keepReason);
- markClassAsInstantiatedWithCompatRule(clazz.asProgramClass(), keepReasonWitness);
+ markClassAsInstantiatedWithCompatRule(clazz.asProgramClass(), () -> keepReasonWitness);
keepInfo.keepClass(clazz);
shouldNotBeMinified(clazz.getReference());
clazz.forEachProgramField(
@@ -2390,7 +2423,7 @@
} else {
markLibraryAndClasspathMethodOverridesAsLive(instantiation, clazz);
}
- worklist.addIfNotSeen(Arrays.asList(clazz.interfaces.values));
+ worklist.addIfNotSeen(clazz.interfaces);
clazz = clazz.superType != null ? appInfo().definitionFor(clazz.superType) : null;
}
// The targets for methods on the type and its supertype that are reachable are now marked.
@@ -3053,6 +3086,11 @@
finalizeLibraryMethodOverrideInformation();
analyses.forEach(analyses -> analyses.done(this));
assert verifyKeptGraph();
+ if (mode.isInitialTreeShaking() && forceProguardCompatibility) {
+ appView.setProguardCompatibilityActions(proguardCompatibilityActionsBuilder.build());
+ } else {
+ assert proguardCompatibilityActionsBuilder == null;
+ }
if (mode.isWhyAreYouKeeping()) {
// For why are you keeping the information is reported through the kept graph callbacks and
// no AppInfo is returned.
@@ -3499,7 +3537,6 @@
noClassMerging,
rootSet.noVerticalClassMerging,
rootSet.noHorizontalClassMerging,
- rootSet.noStaticClassMerging,
rootSet.neverPropagateValue,
joinIdentifierNameStrings(rootSet.identifierNameStrings, identifierNameStrings),
Collections.emptySet(),
@@ -3674,7 +3711,7 @@
}
}
ConsequentRootSetBuilder consequentSetBuilder =
- new ConsequentRootSetBuilder(appView, subtypingInfo, this);
+ ConsequentRootSet.builder(appView, subtypingInfo, this);
IfRuleEvaluator ifRuleEvaluator =
new IfRuleEvaluator(
appView,
@@ -3818,7 +3855,7 @@
}
private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() {
- RootSetBuilder builder = new RootSetBuilder(appView, subtypingInfo);
+ RootSetBuilder builder = RootSet.builder(appView, subtypingInfo);
for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) {
if (delayedRootSetActionItem.isInterfaceMethodSyntheticBridgeAction()) {
handleInterfaceMethodSyntheticBridgeAction(
@@ -4067,7 +4104,19 @@
}
private void markClassAsInstantiatedWithCompatRule(
- DexProgramClass clazz, KeepReasonWitness witness) {
+ DexProgramClass clazz, Supplier<KeepReason> reasonSupplier) {
+ assert forceProguardCompatibility;
+
+ if (appView.hasProguardCompatibilityActions()
+ && !appView.getProguardCompatibilityActions().isCompatInstantiated(clazz)) {
+ return;
+ }
+
+ if (mode.isInitialTreeShaking()) {
+ proguardCompatibilityActionsBuilder.addCompatInstantiatedType(clazz);
+ }
+
+ KeepReasonWitness witness = graphReporter.registerClass(clazz, reasonSupplier.get());
if (clazz.isAnnotation()) {
markTypeAsLive(clazz, witness);
} else if (clazz.isInterface()) {
@@ -4144,15 +4193,8 @@
}
markTypeAsLive(clazz, KeepReason.reflectiveUseIn(method));
if (clazz.canBeInstantiatedByNewInstance()
- && identifierTypeLookupResult.isTypeInstantiatedFromUse(options)) {
- workList.enqueueMarkInstantiatedAction(
- clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
- if (clazz.hasDefaultInitializer()) {
- ProgramMethod initializer = clazz.getProgramDefaultInitializer();
- KeepReason reason = KeepReason.reflectiveUseIn(method);
- markMethodAsTargeted(initializer, reason);
- markDirectStaticOrConstructorMethodAsLive(initializer, reason);
- }
+ && identifierTypeLookupResult.isTypeCompatInstantiatedFromUse(options)) {
+ markClassAsInstantiatedWithCompatRule(clazz, () -> KeepReason.reflectiveUseIn(method));
} else if (identifierTypeLookupResult.isTypeInitializedFromUse()) {
markDirectAndIndirectClassInitializersAsLive(clazz);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
index f656a56..a05a33b 100644
--- a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
@@ -4,59 +4,108 @@
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.AbstractAccessContexts;
+import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.IdentityHashMap;
import java.util.Map;
public class FieldAccessInfoCollectionModifier {
- static class FieldReferences {
- private final ProgramMethodSet writeContexts = ProgramMethodSet.create();
- private final ProgramMethodSet readContexts = ProgramMethodSet.create();
+ private static class FieldAccessContexts {
+
+ private AbstractAccessContexts readsWithContexts = AbstractAccessContexts.empty();
+ private AbstractAccessContexts writesWithContexts = AbstractAccessContexts.empty();
+
+ void addReadContext(DexField field, ProgramMethod context) {
+ if (readsWithContexts.isBottom()) {
+ ConcreteAccessContexts concreteReadContexts = new ConcreteAccessContexts();
+ concreteReadContexts.recordAccess(field, context);
+ readsWithContexts = concreteReadContexts;
+ } else if (readsWithContexts.isConcrete()) {
+ readsWithContexts.asConcrete().recordAccess(field, context);
+ } else {
+ assert readsWithContexts.isTop();
+ }
+ }
+
+ void recordReadInUnknownContext() {
+ readsWithContexts = AbstractAccessContexts.unknown();
+ }
+
+ void addWriteContext(DexField field, ProgramMethod context) {
+ if (writesWithContexts.isBottom()) {
+ ConcreteAccessContexts concreteWriteContexts = new ConcreteAccessContexts();
+ concreteWriteContexts.recordAccess(field, context);
+ writesWithContexts = concreteWriteContexts;
+ } else if (writesWithContexts.isConcrete()) {
+ writesWithContexts.asConcrete().recordAccess(field, context);
+ } else {
+ assert writesWithContexts.isTop();
+ }
+ }
+
+ void recordWriteInUnknownContext() {
+ writesWithContexts = AbstractAccessContexts.unknown();
+ }
}
- private final Map<DexField, FieldReferences> newFieldAccesses;
+ private final Map<DexField, FieldAccessContexts> newFieldAccessContexts;
- FieldAccessInfoCollectionModifier(Map<DexField, FieldReferences> newFieldAccesses) {
- this.newFieldAccesses = newFieldAccesses;
+ private FieldAccessInfoCollectionModifier(
+ Map<DexField, FieldAccessContexts> newFieldAccessContexts) {
+ this.newFieldAccessContexts = newFieldAccessContexts;
+ }
+
+ public static Builder builder() {
+ return new Builder();
}
public void modify(AppView<AppInfoWithLiveness> appView) {
FieldAccessInfoCollectionImpl impl = appView.appInfo().getMutableFieldAccessInfoCollection();
- newFieldAccesses.forEach(
- (field, info) -> {
+ newFieldAccessContexts.forEach(
+ (field, accessContexts) -> {
FieldAccessInfoImpl fieldAccessInfo = new FieldAccessInfoImpl(field);
- info.readContexts.forEach(context -> fieldAccessInfo.recordRead(field, context));
- info.writeContexts.forEach(context -> fieldAccessInfo.recordWrite(field, context));
+ fieldAccessInfo.setReadsWithContexts(accessContexts.readsWithContexts);
+ fieldAccessInfo.setWritesWithContexts(accessContexts.writesWithContexts);
impl.extend(field, fieldAccessInfo);
});
}
public static class Builder {
- private final Map<DexField, FieldReferences> newFieldAccesses = new IdentityHashMap<>();
+ private final Map<DexField, FieldAccessContexts> newFieldAccessContexts =
+ new IdentityHashMap<>();
public Builder() {}
- private FieldReferences getFieldReferences(DexField field) {
- return newFieldAccesses.computeIfAbsent(field, ignore -> new FieldReferences());
+ private FieldAccessContexts getFieldAccessContexts(DexField field) {
+ return newFieldAccessContexts.computeIfAbsent(field, ignore -> new FieldAccessContexts());
}
public void recordFieldReadInContext(DexField field, ProgramMethod context) {
- getFieldReferences(field).readContexts.add(context);
+ getFieldAccessContexts(field).addReadContext(field, context);
+ }
+
+ public Builder recordFieldReadInUnknownContext(DexField field) {
+ getFieldAccessContexts(field).recordReadInUnknownContext();
+ return this;
}
public void recordFieldWrittenInContext(DexField field, ProgramMethod context) {
- getFieldReferences(field).writeContexts.add(context);
+ getFieldAccessContexts(field).addWriteContext(field, context);
+ }
+
+ public void recordFieldWriteInUnknownContext(DexField field) {
+ getFieldAccessContexts(field).recordWriteInUnknownContext();
}
public FieldAccessInfoCollectionModifier build() {
- return new FieldAccessInfoCollectionModifier(newFieldAccesses);
+ return new FieldAccessInfoCollectionModifier(newFieldAccessContexts);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 8f62c4d..e843d0d 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -16,7 +16,9 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.base.Equivalence.Wrapper;
diff --git a/src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java b/src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java
deleted file mode 100644
index dc19834..0000000
--- a/src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.shaking;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import java.util.List;
-
-public class NoStaticClassMergingRule extends ProguardConfigurationRule {
- public static final String RULE_NAME = "nostaticclassmerging";
-
- public static class Builder
- extends ProguardConfigurationRule.Builder<NoStaticClassMergingRule, Builder> {
-
- private Builder() {
- super();
- }
-
- @Override
- public NoStaticClassMergingRule.Builder self() {
- return this;
- }
-
- @Override
- public NoStaticClassMergingRule build() {
- return new NoStaticClassMergingRule(
- origin,
- getPosition(),
- source,
- buildClassAnnotations(),
- classAccessFlags,
- negatedClassAccessFlags,
- classTypeNegated,
- classType,
- classNames,
- buildInheritanceAnnotations(),
- inheritanceClassName,
- inheritanceIsExtends,
- memberRules);
- }
- }
-
- private NoStaticClassMergingRule(
- Origin origin,
- Position position,
- String source,
- List<ProguardTypeMatcher> classAnnotations,
- ProguardAccessFlags classAccessFlags,
- ProguardAccessFlags negatedClassAccessFlags,
- boolean classTypeNegated,
- ProguardClassType classType,
- ProguardClassNameList classNames,
- List<ProguardTypeMatcher> inheritanceAnnotations,
- ProguardTypeMatcher inheritanceClassName,
- boolean inheritanceIsExtends,
- List<ProguardMemberRule> memberRules) {
- super(
- origin,
- position,
- source,
- classAnnotations,
- classAccessFlags,
- negatedClassAccessFlags,
- classTypeNegated,
- classType,
- classNames,
- inheritanceAnnotations,
- inheritanceClassName,
- inheritanceIsExtends,
- memberRules);
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- @Override
- String typeString() {
- return RULE_NAME;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java
new file mode 100644
index 0000000..eed4fec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java
@@ -0,0 +1,64 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class ProguardCompatibilityActions {
+
+ private final Set<DexType> compatInstantiatedTypes;
+
+ private ProguardCompatibilityActions(Set<DexType> compatInstantiatedTypes) {
+ this.compatInstantiatedTypes = compatInstantiatedTypes;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public boolean isCompatInstantiated(DexProgramClass clazz) {
+ return compatInstantiatedTypes.contains(clazz.getType());
+ }
+
+ public ProguardCompatibilityActions withoutPrunedItems(PrunedItems prunedItems) {
+ Builder builder = builder();
+ for (DexType compatInstantiatedType : compatInstantiatedTypes) {
+ if (!prunedItems.isRemoved(compatInstantiatedType)) {
+ builder.addCompatInstantiatedType(compatInstantiatedType);
+ }
+ }
+ return builder.build();
+ }
+
+ public ProguardCompatibilityActions rewrittenWithLens(GraphLens lens) {
+ Builder builder = builder();
+ for (DexType compatInstantiatedType : compatInstantiatedTypes) {
+ builder.addCompatInstantiatedType(lens.lookupType(compatInstantiatedType));
+ }
+ return builder.build();
+ }
+
+ public static class Builder {
+
+ private final Set<DexType> compatInstantiatedTypes = Sets.newIdentityHashSet();
+
+ public void addCompatInstantiatedType(DexProgramClass clazz) {
+ addCompatInstantiatedType(clazz.getType());
+ }
+
+ private void addCompatInstantiatedType(DexType type) {
+ compatInstantiatedTypes.add(type);
+ }
+
+ public ProguardCompatibilityActions build() {
+ return new ProguardCompatibilityActions(compatInstantiatedTypes);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 49e9f1e..2871b74 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -500,11 +500,6 @@
configurationBuilder.addRule(rule);
return true;
}
- if (acceptString(NoStaticClassMergingRule.RULE_NAME)) {
- ProguardConfigurationRule rule = parseNoStaticClassMergingRule(optionStart);
- configurationBuilder.addRule(rule);
- return true;
- }
if (acceptString("neverpropagatevalue")) {
MemberValuePropagationRule rule =
parseMemberValuePropagationRule(MemberValuePropagationRule.Type.NEVER, optionStart);
@@ -805,17 +800,6 @@
return keepRuleBuilder.build();
}
- private NoStaticClassMergingRule parseNoStaticClassMergingRule(Position start)
- throws ProguardRuleParserException {
- NoStaticClassMergingRule.Builder keepRuleBuilder =
- NoStaticClassMergingRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(keepRuleBuilder, false);
- Position end = getPosition();
- keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
- keepRuleBuilder.setEnd(end);
- return keepRuleBuilder.build();
- }
-
private MemberValuePropagationRule parseMemberValuePropagationRule(
MemberValuePropagationRule.Type type, Position start)
throws ProguardRuleParserException {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 5163a69..1b80a1e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Iterables;
import java.util.Collections;
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
deleted file mode 100644
index 33bfc85..0000000
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ /dev/null
@@ -1,2142 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.shaking;
-
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexLibraryClass;
-import com.android.tools.r8.graph.DexMember;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.ProgramMember;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
-import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
-import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
-import com.android.tools.r8.utils.ArrayUtils;
-import com.android.tools.r8.utils.Consumer3;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.OriginWithPosition;
-import com.android.tools.r8.utils.PredicateSet;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
-import java.io.PrintStream;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Queue;
-import java.util.Set;
-import java.util.Stack;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-public class RootSetBuilder {
-
- private final AppView<? extends AppInfoWithClassHierarchy> appView;
- private final SubtypingInfo subtypingInfo;
- private final DirectMappedDexApplication application;
- private final Iterable<? extends ProguardConfigurationRule> rules;
- private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
- private final MutableItemsWithRules softPinned = new MutableItemsWithRules();
- private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
- private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
- private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
- private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
- private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
- private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
- private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet();
- private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
- private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
- private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
- private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
- private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
- private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
- private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
- private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
- private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet();
- private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
- private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
- private final Set<DexType> noStaticClassMerging = Sets.newIdentityHashSet();
- private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
- private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
- new IdentityHashMap<>();
- private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned =
- new IdentityHashMap<>();
- private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
- new IdentityHashMap<>();
- private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects = new IdentityHashMap<>();
- private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
- private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
- private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
- private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
- new ConcurrentLinkedQueue<>();
- private final InternalOptions options;
-
- private final DexStringCache dexStringCache = new DexStringCache();
- private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
-
- private final Map<OriginWithPosition, Set<DexMethod>> assumeNoSideEffectsWarnings =
- new LinkedHashMap<>();
-
- public RootSetBuilder(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- SubtypingInfo subtypingInfo,
- Iterable<? extends ProguardConfigurationRule> rules) {
- this.appView = appView;
- this.subtypingInfo = subtypingInfo;
- this.application = appView.appInfo().app().asDirect();
- this.rules = rules;
- this.options = appView.options();
- }
-
- public RootSetBuilder(
- AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
- this(appView, subtypingInfo, null);
- }
-
- void handleMatchedAnnotation(AnnotationMatchResult annotation) {
- // Intentionally empty.
- }
-
- // Process a class with the keep rule.
- private void process(
- DexClass clazz,
- ProguardConfigurationRule rule,
- ProguardIfRule ifRule) {
- if (!satisfyClassType(rule, clazz)) {
- return;
- }
- if (!satisfyAccessFlag(rule, clazz)) {
- return;
- }
- AnnotationMatchResult annotationMatchResult = satisfyAnnotation(rule, clazz);
- if (annotationMatchResult == null) {
- return;
- }
- handleMatchedAnnotation(annotationMatchResult);
- // In principle it should make a difference whether the user specified in a class
- // spec that a class either extends or implements another type. However, proguard
- // seems not to care, so users have started to use this inconsistently. We are thus
- // inconsistent, as well, but tell them.
- // TODO(herhut): One day make this do what it says.
- if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) {
- return;
- }
-
- if (!rule.getClassNames().matches(clazz.type)) {
- return;
- }
-
- Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
- if (rule instanceof ProguardKeepRule) {
- if (clazz.isNotProgramClass()) {
- return;
- }
- switch (((ProguardKeepRule) rule).getType()) {
- case KEEP_CLASS_MEMBERS:
- // Members mentioned at -keepclassmembers always depend on their holder.
- preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
- markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
- markMatchingVisibleFields(
- clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
- break;
- case KEEP_CLASSES_WITH_MEMBERS:
- if (!allRulesSatisfied(memberKeepRules, clazz)) {
- break;
- }
- // fall through;
- case KEEP:
- markClass(clazz, rule, ifRule);
- preconditionSupplier = new HashMap<>();
- if (ifRule != null) {
- // Static members in -keep are pinned no matter what.
- preconditionSupplier.put(DexDefinition::isStaticMember, null);
- // Instance members may need to be kept even though the holder is not instantiated.
- preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
- } else {
- // Members mentioned at -keep should always be pinned as long as that -keep rule is
- // not triggered conditionally.
- preconditionSupplier.put((definition -> true), null);
- }
- markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
- markMatchingVisibleFields(
- clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
- break;
- case CONDITIONAL:
- throw new Unreachable("-if rule will be evaluated separately, not here.");
- }
- return;
- }
- // Only the ordinary keep rules are supported in a conditional rule.
- assert ifRule == null;
- if (rule instanceof ProguardIfRule) {
- throw new Unreachable("-if rule will be evaluated separately, not here.");
- } else if (rule instanceof ProguardCheckDiscardRule) {
- if (memberKeepRules.isEmpty()) {
- markClass(clazz, rule, ifRule);
- } else {
- preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
- markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
- }
- } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
- markClass(clazz, rule, ifRule);
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
- } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
- || rule instanceof ProguardAssumeNoSideEffectRule
- || rule instanceof ProguardAssumeValuesRule) {
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
- markMatchingOverriddenMethods(
- appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
- } else if (rule instanceof InlineRule
- || rule instanceof ConstantArgumentRule
- || rule instanceof UnusedArgumentRule
- || rule instanceof ReprocessMethodRule
- || rule instanceof WhyAreYouNotInliningRule) {
- markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
- } else if (rule instanceof ClassInlineRule
- || rule instanceof NoUnusedInterfaceRemovalRule
- || rule instanceof NoVerticalClassMergingRule
- || rule instanceof NoHorizontalClassMergingRule
- || rule instanceof NoStaticClassMergingRule
- || rule instanceof ReprocessClassInitializerRule) {
- if (allRulesSatisfied(memberKeepRules, clazz)) {
- markClass(clazz, rule, ifRule);
- }
- } else if (rule instanceof MemberValuePropagationRule) {
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
- } else {
- assert rule instanceof ProguardIdentifierNameStringRule;
- markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
- markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
- }
- }
-
- void runPerRule(
- ExecutorService executorService,
- List<Future<?>> futures,
- ProguardConfigurationRule rule,
- ProguardIfRule ifRule) {
- List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
- if (specifics != null) {
- // This keep rule only lists specific type matches.
- // This means there is no need to iterate over all classes.
- for (DexType type : specifics) {
- DexClass clazz = application.definitionFor(type);
- // Ignore keep rule iff it does not reference a class in the app.
- if (clazz != null) {
- process(clazz, rule, ifRule);
- }
- }
- return;
- }
-
- futures.add(
- executorService.submit(
- () -> {
- for (DexProgramClass clazz :
- rule.relevantCandidatesForRule(appView, subtypingInfo, application.classes())) {
- process(clazz, rule, ifRule);
- }
- if (rule.applyToNonProgramClasses()) {
- for (DexLibraryClass clazz : application.libraryClasses()) {
- process(clazz, rule, ifRule);
- }
- }
- }));
- }
-
- public RootSet run(ExecutorService executorService) throws ExecutionException {
- application.timing.begin("Build root set...");
- try {
- List<Future<?>> futures = new ArrayList<>();
- // Mark all the things explicitly listed in keep rules.
- if (rules != null) {
- for (ProguardConfigurationRule rule : rules) {
- if (rule instanceof ProguardIfRule) {
- ProguardIfRule ifRule = (ProguardIfRule) rule;
- ifRules.add(ifRule);
- } else {
- runPerRule(executorService, futures, rule, null);
- }
- }
- ThreadUtils.awaitFutures(futures);
- }
- } finally {
- application.timing.end();
- }
- generateAssumeNoSideEffectsWarnings();
- if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
- BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
- .visit(appView.appInfo().classes(), this::propagateAssumeRules);
- }
- if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
- GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
- appView,
- subtypingInfo,
- alwaysClassInline,
- noVerticalClassMerging,
- noHorizontalClassMerging,
- noStaticClassMerging,
- alwaysInline,
- bypassClinitforInlining);
- }
- assert Sets.intersection(neverInline, alwaysInline).isEmpty()
- && Sets.intersection(neverInline, forceInline).isEmpty()
- : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
- assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty();
- return new RootSet(
- noShrinking,
- softPinned,
- noObfuscation,
- ImmutableList.copyOf(reasonAsked.values()),
- ImmutableList.copyOf(checkDiscarded.values()),
- alwaysInline,
- forceInline,
- neverInline,
- neverInlineDueToSingleCaller,
- bypassClinitforInlining,
- whyAreYouNotInlining,
- keepParametersWithConstantValue,
- keepUnusedArguments,
- reprocess,
- neverReprocess,
- alwaysClassInline,
- neverClassInline,
- noUnusedInterfaceRemoval,
- noVerticalClassMerging,
- noHorizontalClassMerging,
- noStaticClassMerging,
- neverPropagateValue,
- mayHaveSideEffects,
- noSideEffects,
- assumedValues,
- dependentNoShrinking,
- dependentSoftPinned,
- dependentKeepClassCompatRule,
- identifierNameStrings,
- ifRules,
- Lists.newArrayList(delayedRootSetActionItems));
- }
-
- private void propagateAssumeRules(DexClass clazz) {
- Set<DexType> subTypes = subtypingInfo.allImmediateSubtypes(clazz.type);
- if (subTypes.isEmpty()) {
- return;
- }
- for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
- // If the method has a body, it may have side effects. Don't do bottom-up propagation.
- if (encodedMethod.hasCode()) {
- assert !encodedMethod.shouldNotHaveCode();
- continue;
- }
- propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, noSideEffects);
- propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, assumedValues);
- }
- }
-
- private void propagateAssumeRules(
- DexType type,
- DexMethod reference,
- Set<DexType> subTypes,
- Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) {
- ProguardMemberRule ruleToBePropagated = null;
- for (DexType subType : subTypes) {
- DexMethod referenceInSubType =
- appView.dexItemFactory().createMethod(subType, reference.proto, reference.name);
- // Those rules are bound to definitions, not references. If the current subtype does not
- // override the method, and when the retrieval of bound rule fails, it is unclear whether it
- // is due to the lack of the definition or it indeed means no matching rules. Similar to how
- // we apply those assume rules, here we use a resolved target.
- DexEncodedMethod target =
- appView.appInfo().unsafeResolveMethodDueToDexFormat(referenceInSubType).getSingleTarget();
- // But, the resolution should not be landed on the current type we are visiting.
- if (target == null || target.getHolderType() == type) {
- continue;
- }
- ProguardMemberRule ruleInSubType = assumeRulePool.get(target.method);
- // We are looking for the greatest lower bound of assume rules from all sub types.
- // If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
- if (ruleInSubType == null) {
- ruleToBePropagated = null;
- break;
- }
- if (ruleToBePropagated == null) {
- ruleToBePropagated = ruleInSubType;
- } else {
- // TODO(b/133208961): Introduce comparison/meet of assume rules.
- if (!ruleToBePropagated.equals(ruleInSubType)) {
- ruleToBePropagated = null;
- break;
- }
- }
- }
- if (ruleToBePropagated != null) {
- assumeRulePool.put(reference, ruleToBePropagated);
- }
- }
-
- ConsequentRootSet buildConsequentRootSet() {
- return new ConsequentRootSet(
- neverInline,
- neverInlineDueToSingleCaller,
- neverClassInline,
- noShrinking,
- softPinned,
- noObfuscation,
- dependentNoShrinking,
- dependentSoftPinned,
- dependentKeepClassCompatRule,
- Lists.newArrayList(delayedRootSetActionItems));
- }
-
- private static DexDefinition testAndGetPrecondition(
- DexDefinition definition, Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
- if (preconditionSupplier == null) {
- return null;
- }
- DexDefinition precondition = null;
- boolean conditionEverMatched = false;
- for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) {
- if (entry.getKey().test(definition)) {
- precondition = entry.getValue();
- conditionEverMatched = true;
- break;
- }
- }
- // If precondition-supplier is given, there should be at least one predicate that holds.
- // Actually, there should be only one predicate as we break the loop when it is found.
- assert conditionEverMatched;
- return precondition;
- }
-
- private void markMatchingVisibleMethods(
- DexClass clazz,
- Collection<ProguardMemberRule> memberKeepRules,
- ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
- boolean includeLibraryClasses,
- ProguardIfRule ifRule) {
- Set<Wrapper<DexMethod>> methodsMarked =
- options.forceProguardCompatibility ? null : new HashSet<>();
- Stack<DexClass> worklist = new Stack<>();
- worklist.add(clazz);
- while (!worklist.isEmpty()) {
- DexClass currentClass = worklist.pop();
- if (!includeLibraryClasses && currentClass.isNotProgramClass()) {
- break;
- }
- // In compat mode traverse all direct methods in the hierarchy.
- if (currentClass == clazz || options.forceProguardCompatibility) {
- currentClass
- .directMethods()
- .forEach(
- method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
- });
- }
- currentClass
- .virtualMethods()
- .forEach(
- method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
- });
- if (currentClass.superType != null) {
- DexClass dexClass = application.definitionFor(currentClass.superType);
- if (dexClass != null) {
- worklist.add(dexClass);
- }
- }
- }
- // TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
- // fullmode.
- if (clazz.isProgramClass()
- && rule.isProguardKeepRule()
- && !rule.asProguardKeepRule().getModifiers().allowsShrinking) {
- new SynthesizeMissingInterfaceMethodsForMemberRules(
- clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule)
- .run();
- }
- }
-
- /**
- * Utility class for visiting all super interfaces to ensure we keep method definitions specified
- * by proguard rules. If possible, we generate a forwarding bridge to the resolved target. If not,
- * we specifically synthesize a keep rule for the interface method.
- */
- private class SynthesizeMissingInterfaceMethodsForMemberRules {
- private final DexProgramClass originalClazz;
- private final Collection<ProguardMemberRule> memberKeepRules;
- private final ProguardConfigurationRule context;
- private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
- private final ProguardIfRule ifRule;
- private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
- private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
-
- private SynthesizeMissingInterfaceMethodsForMemberRules(
- DexProgramClass originalClazz,
- Collection<ProguardMemberRule> memberKeepRules,
- ProguardConfigurationRule context,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
- ProguardIfRule ifRule) {
- assert context.isProguardKeepRule();
- assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
- this.originalClazz = originalClazz;
- this.memberKeepRules = memberKeepRules;
- this.context = context;
- this.preconditionSupplier = preconditionSupplier;
- this.ifRule = ifRule;
- }
-
- void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
- // Intentionally empty.
- }
-
- void run() {
- visitAllSuperInterfaces(originalClazz.type);
- }
-
- private void visitAllSuperInterfaces(DexType type) {
- DexClass clazz = appView.definitionFor(type);
- if (clazz == null || clazz.isNotProgramClass() || !seenTypes.add(type)) {
- return;
- }
- for (DexType iface : clazz.interfaces.values) {
- visitAllSuperInterfaces(iface);
- }
- if (!clazz.isInterface()) {
- visitAllSuperInterfaces(clazz.superType);
- return;
- }
- if (originalClazz == clazz) {
- return;
- }
- for (DexEncodedMethod method : clazz.virtualMethods()) {
- // Check if we already added this.
- Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.method);
- if (!seenMethods.add(wrapped)) {
- continue;
- }
- for (ProguardMemberRule rule : memberKeepRules) {
- if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
- tryAndKeepMethodOnClass(method, rule);
- }
- }
- }
- }
-
- private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
- SingleResolutionResult resolutionResult =
- appView.appInfo().resolveMethodOn(originalClazz, method.method).asSingleResolution();
- if (resolutionResult == null || !resolutionResult.isVirtualTarget()) {
- return;
- }
- if (resolutionResult.getResolvedHolder() == originalClazz
- || resolutionResult.getResolvedHolder().isNotProgramClass()) {
- return;
- }
- if (!resolutionResult.getResolvedHolder().isInterface()) {
- // TODO(b/143643942): For fullmode, this check should probably be removed.
- return;
- }
- ProgramMethod resolutionMethod =
- new ProgramMethod(
- resolutionResult.getResolvedHolder().asProgramClass(),
- resolutionResult.getResolvedMethod());
- ProgramMethod methodToKeep =
- canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())
- ? new ProgramMethod(
- originalClazz,
- resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView))
- : resolutionMethod;
-
- delayedRootSetActionItems.add(
- new InterfaceMethodSyntheticBridgeAction(
- methodToKeep,
- resolutionMethod,
- (rootSetBuilder) -> {
- if (Log.ENABLED) {
- Log.verbose(
- getClass(),
- "Marking method `%s` due to `%s { %s }`.",
- methodToKeep,
- context,
- rule);
- }
- DexDefinition precondition =
- testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
- rootSetBuilder.addItemToSets(
- methodToKeep.getDefinition(), context, rule, precondition, ifRule);
- }));
- }
- }
-
- private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
- return appView.options().isGeneratingDex()
- || ArrayUtils.contains(holder.interfaces.values, target.getHolderType());
- }
-
- private void markMatchingOverriddenMethods(
- AppInfoWithClassHierarchy appInfoWithSubtyping,
- DexClass clazz,
- Collection<ProguardMemberRule> memberKeepRules,
- ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
- boolean onlyIncludeProgramClasses,
- ProguardIfRule ifRule) {
- Set<DexType> visited = new HashSet<>();
- Deque<DexType> worklist = new ArrayDeque<>();
- // Intentionally skip the current `clazz`, assuming it's covered by markMatchingVisibleMethods.
- worklist.addAll(subtypingInfo.allImmediateSubtypes(clazz.type));
-
- while (!worklist.isEmpty()) {
- DexType currentType = worklist.poll();
- if (!visited.add(currentType)) {
- continue;
- }
- DexClass currentClazz = appView.definitionFor(currentType);
- if (currentClazz == null) {
- continue;
- }
- if (!onlyIncludeProgramClasses && currentClazz.isNotProgramClass()) {
- continue;
- }
- currentClazz
- .virtualMethods()
- .forEach(
- method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
- });
- worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type));
- }
- }
-
- private void markMatchingMethods(
- DexClass clazz,
- Collection<ProguardMemberRule> memberKeepRules,
- ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
- ProguardIfRule ifRule) {
- clazz.forEachMethod(
- method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
- });
- }
-
- private void markMatchingVisibleFields(
- DexClass clazz,
- Collection<ProguardMemberRule> memberKeepRules,
- ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
- boolean includeLibraryClasses,
- ProguardIfRule ifRule) {
- while (clazz != null) {
- if (!includeLibraryClasses && clazz.isNotProgramClass()) {
- return;
- }
- clazz.forEachField(
- field -> {
- DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
- markField(field, memberKeepRules, rule, precondition, ifRule);
- });
- clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
- }
- }
-
- private void markMatchingFields(
- DexClass clazz,
- Collection<ProguardMemberRule> memberKeepRules,
- ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
- ProguardIfRule ifRule) {
- clazz.forEachField(
- field -> {
- DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
- markField(field, memberKeepRules, rule, precondition, ifRule);
- });
- }
-
- // TODO(b/67934426): Test this code.
- public static void writeSeeds(
- AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
- appInfo
- .getKeepInfo()
- .forEachPinnedType(
- type -> {
- if (include.test(type)) {
- out.println(type.toSourceString());
- }
- });
- appInfo
- .getKeepInfo()
- .forEachPinnedField(
- field -> {
- if (include.test(field.holder)) {
- out.println(
- field.holder.toSourceString()
- + ": "
- + field.type.toSourceString()
- + " "
- + field.name.toSourceString());
- }
- });
- appInfo
- .getKeepInfo()
- .forEachPinnedMethod(
- method -> {
- if (!include.test(method.holder)) {
- return;
- }
- DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method));
- DexEncodedMethod definition = method.lookupOnClass(holder);
- if (definition == null) {
- assert method.match(appInfo.dexItemFactory().deserializeLambdaMethod);
- return;
- }
- out.print(method.holder.toSourceString() + ": ");
- if (definition.isClassInitializer()) {
- out.print(Constants.CLASS_INITIALIZER_NAME);
- } else if (definition.isInstanceInitializer()) {
- String holderName = method.holder.toSourceString();
- String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
- out.print(constrName);
- } else {
- out.print(
- method.proto.returnType.toSourceString() + " " + method.name.toSourceString());
- }
- boolean first = true;
- out.print("(");
- for (DexType param : method.proto.parameters.values) {
- if (!first) {
- out.print(",");
- }
- first = false;
- out.print(param.toSourceString());
- }
- out.println(")");
- });
- out.close();
- }
-
- static boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
- return rule.getClassType().matches(clazz) != rule.getClassTypeNegated();
- }
-
- static boolean satisfyAccessFlag(ProguardConfigurationRule rule, DexClass clazz) {
- return rule.getClassAccessFlags().containsAll(clazz.accessFlags)
- && rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags);
- }
-
- static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
- return containsAllAnnotations(rule.getClassAnnotations(), clazz);
- }
-
- boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
- if (satisfyExtendsRule(clazz, rule)) {
- return true;
- }
-
- return satisfyImplementsRule(clazz, rule);
- }
-
- boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) {
- if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) {
- return true;
- }
- // It is possible that this class used to inherit from another class X, but no longer does it,
- // because X has been merged into `clazz`.
- return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false);
- }
-
- boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) {
- while (type != null) {
- DexClass clazz = application.definitionFor(type);
- if (clazz == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- // TODO(b/110141157): Should the vertical class merger move annotations from the source to
- // the target class? If so, it is sufficient only to apply the annotation-matcher to the
- // annotations of `class`.
- if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
- AnnotationMatchResult annotationMatchResult =
- containsAllAnnotations(rule.getInheritanceAnnotations(), clazz);
- if (annotationMatchResult != null) {
- handleMatchedAnnotation(annotationMatchResult);
- return true;
- }
- }
- type = clazz.superType;
- }
- return false;
- }
-
- boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) {
- if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) {
- return true;
- }
- // It is possible that this class used to implement an interface I, but no longer does it,
- // because I has been merged into `clazz`.
- return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true);
- }
-
- private boolean anyImplementedInterfaceMatchesImplementsRule(
- DexClass clazz, ProguardConfigurationRule rule) {
- // TODO(herhut): Maybe it would be better to do this breadth first.
- if (clazz == null) {
- return false;
- }
- for (DexType iface : clazz.interfaces.values) {
- DexClass ifaceClass = application.definitionFor(iface);
- if (ifaceClass == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- // TODO(b/110141157): Should the vertical class merger move annotations from the source to
- // the target class? If so, it is sufficient only to apply the annotation-matcher to the
- // annotations of `ifaceClass`.
- if (rule.getInheritanceClassName().matches(iface, appView)) {
- AnnotationMatchResult annotationMatchResult =
- containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass);
- if (annotationMatchResult != null) {
- handleMatchedAnnotation(annotationMatchResult);
- return true;
- }
- }
- if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
- return true;
- }
- }
- if (clazz.superType == null) {
- return false;
- }
- DexClass superClass = application.definitionFor(clazz.superType);
- if (superClass == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- return anyImplementedInterfaceMatchesImplementsRule(superClass, rule);
- }
-
- private boolean anySourceMatchesInheritanceRuleDirectly(
- DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
- // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
- // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
- return appView.verticallyMergedClasses() != null
- && appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
- .filter(
- sourceType ->
- appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
- .anyMatch(rule.getInheritanceClassName()::matches);
- }
-
- private boolean allRulesSatisfied(Collection<ProguardMemberRule> memberKeepRules,
- DexClass clazz) {
- for (ProguardMemberRule rule : memberKeepRules) {
- if (!ruleSatisfied(rule, clazz)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Checks whether the given rule is satisfied by this clazz, not taking superclasses into
- * account.
- */
- private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
- return ruleSatisfiedByMethods(rule, clazz.directMethods())
- || ruleSatisfiedByMethods(rule, clazz.virtualMethods())
- || ruleSatisfiedByFields(rule, clazz.staticFields())
- || ruleSatisfiedByFields(rule, clazz.instanceFields());
- }
-
- boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
- if (rule.getRuleType().includesMethods()) {
- for (DexEncodedMethod method : methods) {
- if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
- return true;
- }
- }
- }
- return false;
- }
-
- boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
- if (rule.getRuleType().includesFields()) {
- for (DexEncodedField field : fields) {
- if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
- return true;
- }
- }
- }
- return false;
- }
-
- static AnnotationMatchResult containsAllAnnotations(
- List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
- return containsAllAnnotations(annotationMatchers, clazz.annotations());
- }
-
- static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
- boolean containsAllAnnotations(
- List<ProguardTypeMatcher> annotationMatchers,
- DexEncodedMember<D, R> member,
- Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
- AnnotationMatchResult annotationMatchResult =
- containsAllAnnotations(annotationMatchers, member.annotations());
- if (annotationMatchResult != null) {
- matchedAnnotationsConsumer.accept(annotationMatchResult);
- return true;
- }
- if (member.isDexEncodedMethod()) {
- DexEncodedMethod method = member.asDexEncodedMethod();
- for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
- annotationMatchResult =
- containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
- if (annotationMatchResult != null) {
- matchedAnnotationsConsumer.accept(annotationMatchResult);
- return true;
- }
- }
- }
- return false;
- }
-
- private static AnnotationMatchResult containsAllAnnotations(
- List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
- if (annotationMatchers.isEmpty()) {
- return AnnotationsIgnoredMatchResult.getInstance();
- }
- List<DexAnnotation> matchedAnnotations = new ArrayList<>();
- for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
- DexAnnotation matchedAnnotation =
- getFirstAnnotationThatMatches(annotationMatcher, annotations);
- if (matchedAnnotation == null) {
- return null;
- }
- matchedAnnotations.add(matchedAnnotation);
- }
- return new ConcreteAnnotationMatchResult(matchedAnnotations);
- }
-
- private static DexAnnotation getFirstAnnotationThatMatches(
- ProguardTypeMatcher annotationMatcher, DexAnnotationSet annotations) {
- for (DexAnnotation annotation : annotations.annotations) {
- if (annotationMatcher.matches(annotation.getAnnotationType())) {
- return annotation;
- }
- }
- return null;
- }
-
- private void markMethod(
- DexEncodedMethod method,
- Collection<ProguardMemberRule> rules,
- Set<Wrapper<DexMethod>> methodsMarked,
- ProguardConfigurationRule context,
- DexDefinition precondition,
- ProguardIfRule ifRule) {
- if (methodsMarked != null
- && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
- // Ignore, method is overridden in sub class.
- return;
- }
- for (ProguardMemberRule rule : rules) {
- if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
- rule);
- }
- if (methodsMarked != null) {
- methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
- }
- addItemToSets(method, context, rule, precondition, ifRule);
- }
- }
- }
-
- private void markField(
- DexEncodedField field,
- Collection<ProguardMemberRule> rules,
- ProguardConfigurationRule context,
- DexDefinition precondition,
- ProguardIfRule ifRule) {
- for (ProguardMemberRule rule : rules) {
- if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context,
- rule);
- }
- addItemToSets(field, context, rule, precondition, ifRule);
- }
- }
- }
-
- private void markClass(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Marking class `%s` due to `%s`.", clazz.type, rule);
- }
- addItemToSets(clazz, rule, null, null, ifRule);
- }
-
- private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRuleBase context) {
- if (type.isVoidType()) {
- return;
- }
- if (type.isArrayType()) {
- type = type.toBaseType(appView.dexItemFactory());
- }
- if (type.isPrimitiveType()) {
- return;
- }
- DexClass definition = appView.definitionFor(type);
- if (definition == null || definition.isNotProgramClass()) {
- return;
- }
- // Keep the type if the item is also kept.
- dependentNoShrinking
- .computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules())
- .addClassWithRule(type, context);
- // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
- if (appView.options().isMinificationEnabled()) {
- noObfuscation.add(type);
- }
- }
-
- private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
- if (item.isDexEncodedMethod()) {
- DexMethod method = item.asDexEncodedMethod().method;
- includeDescriptor(item, method.proto.returnType, context);
- for (DexType value : method.proto.parameters.values) {
- includeDescriptor(item, value, context);
- }
- } else if (item.isDexEncodedField()) {
- DexField field = item.asDexEncodedField().field;
- includeDescriptor(item, field.type, context);
- } else {
- assert item.isDexClass();
- }
- }
-
- private synchronized void addItemToSets(
- DexDefinition item,
- ProguardConfigurationRule context,
- ProguardMemberRule rule,
- DexDefinition precondition,
- ProguardIfRule ifRule) {
- if (context instanceof ProguardKeepRule) {
- if (item.isDexEncodedField()) {
- DexEncodedField encodedField = item.asDexEncodedField();
- if (encodedField.getOptimizationInfo().cannotBeKept()) {
- // We should only ever get here with if rules.
- assert ifRule != null;
- return;
- }
- } else if (item.isDexEncodedMethod()) {
- DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
- if (encodedMethod.isClassInitializer() && !options.debug) {
- // Don't keep class initializers.
- return;
- }
- if (encodedMethod.getOptimizationInfo().cannotBeKept()) {
- // We should only ever get here with if rules.
- assert ifRule != null;
- return;
- }
- if (options.isGeneratingDex()
- && encodedMethod.method.isLambdaDeserializeMethod(appView.dexItemFactory())) {
- // Don't keep lambda deserialization methods.
- return;
- }
- // If desugaring is enabled, private and static interface methods will be moved to a
- // companion class. So we don't need to add them to the root set in the beginning.
- if (options.isInterfaceMethodDesugaringEnabled()
- && encodedMethod.hasCode()
- && (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) {
- DexClass holder = appView.definitionFor(encodedMethod.getHolderType());
- if (holder != null && holder.isInterface()) {
- if (rule.isSpecific()) {
- options.reporter.warning(
- new StringDiagnostic(
- "The rule `" + rule + "` is ignored because the targeting interface method `"
- + encodedMethod.method.toSourceString() + "` will be desugared."));
- }
- return;
- }
- }
- }
-
- // The reason for keeping should link to the conditional rule as a whole, if present.
- ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context;
- // The modifiers are specified on the actual keep rule (ie, the consequent/context).
- ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers();
- // In compatibility mode, for a match on instance members a referenced class becomes live.
- if (options.forceProguardCompatibility
- && !modifiers.allowsShrinking
- && precondition != null
- && precondition.isDexClass()) {
- if (!item.isDexClass() && !item.isStaticMember()) {
- dependentKeepClassCompatRule
- .computeIfAbsent(precondition.asDexClass().getType(), i -> new HashSet<>())
- .add(keepRule);
- context.markAsUsed();
- }
- }
- if (!modifiers.allowsShrinking) {
- if (precondition != null) {
- dependentNoShrinking
- .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
- .addReferenceWithRule(item.getReference(), keepRule);
- } else {
- noShrinking.addReferenceWithRule(item.getReference(), keepRule);
- }
- context.markAsUsed();
- } else if (!modifiers.allowsOptimization) {
- if (precondition != null) {
- dependentSoftPinned
- .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
- .addReferenceWithRule(item.getReference(), keepRule);
- } else {
- softPinned.addReferenceWithRule(item.getReference(), keepRule);
- }
- }
- if (!modifiers.allowsOptimization) {
- // The -dontoptimize flag has only effect through the keep all rule, but we still
- // need to mark the rule as used.
- context.markAsUsed();
- }
-
- if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
- noObfuscation.add(item.getReference());
- context.markAsUsed();
- }
- if (modifiers.includeDescriptorClasses) {
- includeDescriptorClasses(item, keepRule);
- context.markAsUsed();
- }
- } else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
- mayHaveSideEffects.put(item.getReference(), rule);
- context.markAsUsed();
- } else if (context instanceof ProguardAssumeNoSideEffectRule) {
- if (item.isDexEncodedMember()) {
- DexEncodedMember<?, ?> member = item.asDexEncodedMember();
- if (member.getHolderType() == appView.dexItemFactory().objectType) {
- assert member.isDexEncodedMethod();
- reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
- member.asDexEncodedMethod(), (ProguardAssumeNoSideEffectRule) context);
- } else {
- noSideEffects.put(member.getReference(), rule);
- }
- context.markAsUsed();
- }
- } else if (context instanceof ProguardWhyAreYouKeepingRule) {
- reasonAsked.computeIfAbsent(item.getReference(), i -> i);
- context.markAsUsed();
- } else if (context instanceof ProguardAssumeValuesRule) {
- if (item.isDexEncodedMember()) {
- assumedValues.put(item.asDexEncodedMember().getReference(), rule);
- context.markAsUsed();
- }
- } else if (context instanceof ProguardCheckDiscardRule) {
- checkDiscarded.computeIfAbsent(item.getReference(), i -> i);
- context.markAsUsed();
- } else if (context instanceof InlineRule) {
- if (item.isDexEncodedMethod()) {
- DexMethod reference = item.asDexEncodedMethod().getReference();
- switch (((InlineRule) context).getType()) {
- case ALWAYS:
- alwaysInline.add(reference);
- break;
- case FORCE:
- forceInline.add(reference);
- break;
- case NEVER:
- neverInline.add(reference);
- break;
- case NEVER_SINGLE_CALLER:
- neverInlineDueToSingleCaller.add(reference);
- break;
- default:
- throw new Unreachable();
- }
- context.markAsUsed();
- }
- } else if (context instanceof WhyAreYouNotInliningRule) {
- if (!item.isDexEncodedMethod()) {
- throw new Unreachable();
- }
- whyAreYouNotInlining.add(item.asDexEncodedMethod().method);
- context.markAsUsed();
- } else if (context.isClassInlineRule()) {
- ClassInlineRule classInlineRule = context.asClassInlineRule();
- DexClass clazz = item.asDexClass();
- if (clazz == null) {
- throw new IllegalStateException(
- "Unexpected -"
- + classInlineRule.typeString()
- + " rule for a non-class type: `"
- + item.getReference().toSourceString()
- + "`");
- }
- switch (classInlineRule.getType()) {
- case ALWAYS:
- alwaysClassInline.addElement(item.asDexClass().type);
- break;
- case NEVER:
- neverClassInline.add(item.asDexClass().type);
- break;
- default:
- throw new Unreachable();
- }
- context.markAsUsed();
- } else if (context instanceof NoUnusedInterfaceRemovalRule) {
- noUnusedInterfaceRemoval.add(item.asDexClass().type);
- context.markAsUsed();
- } else if (context instanceof NoVerticalClassMergingRule) {
- noVerticalClassMerging.add(item.asDexClass().type);
- context.markAsUsed();
- } else if (context instanceof NoHorizontalClassMergingRule) {
- noHorizontalClassMerging.add(item.asDexClass().type);
- context.markAsUsed();
- } else if (context instanceof NoStaticClassMergingRule) {
- noStaticClassMerging.add(item.asDexClass().type);
- context.markAsUsed();
- } else if (context instanceof MemberValuePropagationRule) {
- switch (((MemberValuePropagationRule) context).getType()) {
- case NEVER:
- // Only add members from propgram classes to `neverPropagateValue` since class member
- // values from library types are not propagated by default.
- if (item.isDexEncodedField()) {
- DexEncodedField field = item.asDexEncodedField();
- if (field.isProgramField(appView)) {
- neverPropagateValue.add(item.asDexEncodedField().field);
- context.markAsUsed();
- }
- } else if (item.isDexEncodedMethod()) {
- DexEncodedMethod method = item.asDexEncodedMethod();
- if (method.isProgramMethod(appView)) {
- neverPropagateValue.add(item.asDexEncodedMethod().method);
- context.markAsUsed();
- }
- }
- break;
- default:
- throw new Unreachable();
- }
- } else if (context instanceof ProguardIdentifierNameStringRule) {
- if (item.isDexEncodedField()) {
- identifierNameStrings.add(item.asDexEncodedField().field);
- context.markAsUsed();
- } else if (item.isDexEncodedMethod()) {
- identifierNameStrings.add(item.asDexEncodedMethod().method);
- context.markAsUsed();
- }
- } else if (context instanceof ConstantArgumentRule) {
- if (item.isDexEncodedMethod()) {
- keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
- context.markAsUsed();
- }
- } else if (context instanceof ReprocessClassInitializerRule) {
- DexProgramClass clazz = item.asProgramClass();
- if (clazz != null && clazz.hasClassInitializer()) {
- switch (context.asReprocessClassInitializerRule().getType()) {
- case ALWAYS:
- reprocess.add(clazz.getClassInitializer().method);
- break;
- case NEVER:
- neverReprocess.add(clazz.getClassInitializer().method);
- break;
- default:
- throw new Unreachable();
- }
- context.markAsUsed();
- }
- } else if (context.isReprocessMethodRule()) {
- if (item.isDexEncodedMethod()) {
- DexEncodedMethod method = item.asDexEncodedMethod();
- switch (context.asReprocessMethodRule().getType()) {
- case ALWAYS:
- reprocess.add(method.method);
- break;
- case NEVER:
- neverReprocess.add(method.method);
- break;
- default:
- throw new Unreachable();
- }
- context.markAsUsed();
- }
- } else if (context instanceof UnusedArgumentRule) {
- if (item.isDexEncodedMethod()) {
- keepUnusedArguments.add(item.asDexEncodedMethod().method);
- context.markAsUsed();
- }
- } else {
- throw new Unreachable();
- }
- }
-
- abstract static class RootSetBase {
-
- final Set<DexMethod> neverInline;
- final Set<DexMethod> neverInlineDueToSingleCaller;
- final Set<DexType> neverClassInline;
- final MutableItemsWithRules noShrinking;
- final MutableItemsWithRules softPinned;
- final Set<DexReference> noObfuscation;
- final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
- final Map<DexReference, MutableItemsWithRules> dependentSoftPinned;
- final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
- final List<DelayedRootSetActionItem> delayedRootSetActionItems;
-
- RootSetBase(
- Set<DexMethod> neverInline,
- Set<DexMethod> neverInlineDueToSingleCaller,
- Set<DexType> neverClassInline,
- MutableItemsWithRules noShrinking,
- MutableItemsWithRules softPinned,
- Set<DexReference> noObfuscation,
- Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
- Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
- Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
- List<DelayedRootSetActionItem> delayedRootSetActionItems) {
- this.neverInline = neverInline;
- this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
- this.neverClassInline = neverClassInline;
- this.noShrinking = noShrinking;
- this.softPinned = softPinned;
- this.noObfuscation = noObfuscation;
- this.dependentNoShrinking = dependentNoShrinking;
- this.dependentSoftPinned = dependentSoftPinned;
- this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
- this.delayedRootSetActionItems = delayedRootSetActionItems;
- }
-
- public void forEachClassWithDependentItems(
- DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
- for (DexReference reference : dependentNoShrinking.keySet()) {
- if (reference.isDexType()) {
- DexType type = reference.asDexType();
- DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
- if (clazz != null) {
- consumer.accept(clazz);
- }
- }
- }
- }
-
- public void forEachMemberWithDependentItems(
- DexDefinitionSupplier definitions,
- BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) {
- dependentNoShrinking.forEach(
- (reference, dependentItems) -> {
- if (reference.isDexMember()) {
- DexMember<?, ?> member = reference.asDexMember();
- DexProgramClass holder =
- asProgramClassOrNull(definitions.definitionForHolder(member));
- if (holder != null) {
- DexEncodedMember<?, ?> definition = holder.lookupMember(member);
- if (definition != null) {
- consumer.accept(definition, dependentItems);
- }
- }
- }
- });
- }
-
- public void forEachDependentInstanceConstructor(
- DexProgramClass clazz,
- AppView<?> appView,
- BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
- getDependentItems(clazz)
- .forEachMethod(
- (reference, reasons) -> {
- DexProgramClass holder =
- asProgramClassOrNull(appView.definitionForHolder(reference));
- if (holder != null) {
- ProgramMethod method = holder.lookupProgramMethod(reference);
- if (method != null && method.getDefinition().isInstanceInitializer()) {
- fn.accept(method, reasons);
- }
- }
- });
- }
-
- public void forEachDependentMember(
- DexDefinition item,
- AppView<?> appView,
- Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
- getDependentItems(item)
- .forEachMember(
- (reference, reasons) -> {
- DexProgramClass holder =
- asProgramClassOrNull(appView.definitionForHolder(reference));
- if (holder != null) {
- ProgramMember<?, ?> member = holder.lookupProgramMember(reference);
- if (member != null) {
- fn.accept(item, member, reasons);
- }
- }
- });
- }
-
- public void forEachDependentNonStaticMember(
- DexDefinition item,
- AppView<?> appView,
- Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
- forEachDependentMember(
- item,
- appView,
- (precondition, member, reasons) -> {
- if (!member.getDefinition().isStatic()) {
- fn.accept(precondition, member, reasons);
- }
- });
- }
-
- public void forEachDependentStaticMember(
- DexDefinition item,
- AppView<?> appView,
- Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
- forEachDependentMember(
- item,
- appView,
- (precondition, member, reasons) -> {
- if (member.getDefinition().isStatic()) {
- fn.accept(precondition, member, reasons);
- }
- });
- }
-
- ItemsWithRules getDependentItems(DexDefinition item) {
- ItemsWithRules found = dependentNoShrinking.get(item.getReference());
- return found != null ? found : ItemsWithRules.empty();
- }
-
- Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
- return dependentKeepClassCompatRule.get(type);
- }
- }
-
- abstract static class ItemsWithRules {
-
- public static ItemsWithRules empty() {
- return MutableItemsWithRules.EMPTY;
- }
-
- public abstract boolean containsClass(DexType type);
-
- public abstract boolean containsField(DexField field);
-
- public abstract boolean containsMethod(DexMethod method);
-
- public final boolean containsReference(DexReference reference) {
- return reference.apply(this::containsClass, this::containsField, this::containsMethod);
- }
-
- public abstract void forEachClass(Consumer<? super DexType> consumer);
-
- public abstract void forEachClass(
- BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer);
-
- public abstract void forEachField(Consumer<? super DexField> consumer);
-
- public abstract void forEachField(
- BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer);
-
- public abstract void forEachMember(Consumer<? super DexMember<?, ?>> consumer);
-
- public abstract void forEachMember(
- BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer);
-
- public abstract void forEachMethod(Consumer<? super DexMethod> consumer);
-
- public abstract void forEachMethod(
- BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer);
-
- public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type);
-
- public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field);
-
- public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method);
-
- public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) {
- return reference.apply(
- this::getRulesForClass, this::getRulesForField, this::getRulesForMethod);
- }
- }
-
- static class MutableItemsWithRules extends ItemsWithRules {
-
- private static final ItemsWithRules EMPTY =
- new MutableItemsWithRules(
- Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
-
- final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
- final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
- final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules;
-
- MutableItemsWithRules() {
- this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
- }
-
- private MutableItemsWithRules(
- Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules,
- Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules,
- Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) {
- this.classesWithRules = classesWithRules;
- this.fieldsWithRules = fieldsWithRules;
- this.methodsWithRules = methodsWithRules;
- }
-
- public void addAll(ItemsWithRules items) {
- items.forEachClass(this::addClassWithRules);
- items.forEachField(this::addFieldWithRules);
- items.forEachMethod(this::addMethodWithRules);
- }
-
- public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) {
- classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule);
- }
-
- public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
- classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules);
- }
-
- public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) {
- fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule);
- }
-
- public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
- fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules);
- }
-
- public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) {
- methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule);
- }
-
- public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
- methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules);
- }
-
- public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) {
- reference.accept(
- this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule);
- }
-
- public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
- reference.accept(
- this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules);
- }
-
- @Override
- public boolean containsClass(DexType type) {
- return classesWithRules.containsKey(type);
- }
-
- @Override
- public boolean containsField(DexField field) {
- return fieldsWithRules.containsKey(field);
- }
-
- @Override
- public boolean containsMethod(DexMethod method) {
- return methodsWithRules.containsKey(method);
- }
-
- public void forEachReference(Consumer<DexReference> consumer) {
- forEachClass(consumer);
- forEachMember(consumer);
- }
-
- @Override
- public void forEachClass(Consumer<? super DexType> consumer) {
- classesWithRules.keySet().forEach(consumer);
- }
-
- @Override
- public void forEachClass(BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer) {
- classesWithRules.forEach(consumer);
- }
-
- @Override
- public void forEachField(Consumer<? super DexField> consumer) {
- fieldsWithRules.keySet().forEach(consumer);
- }
-
- @Override
- public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) {
- fieldsWithRules.forEach(consumer);
- }
-
- @Override
- public void forEachMember(Consumer<? super DexMember<?, ?>> consumer) {
- forEachField(consumer);
- forEachMethod(consumer);
- }
-
- @Override
- public void forEachMember(
- BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) {
- forEachField(consumer);
- forEachMethod(consumer);
- }
-
- @Override
- public void forEachMethod(Consumer<? super DexMethod> consumer) {
- methodsWithRules.keySet().forEach(consumer);
- }
-
- @Override
- public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) {
- methodsWithRules.forEach(consumer);
- }
-
- @Override
- public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) {
- return classesWithRules.get(type);
- }
-
- @Override
- public Set<ProguardKeepRuleBase> getRulesForField(DexField field) {
- return fieldsWithRules.get(field);
- }
-
- @Override
- public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) {
- return methodsWithRules.get(method);
- }
-
- public void removeClass(DexType type) {
- classesWithRules.remove(type);
- }
-
- public void removeField(DexField field) {
- fieldsWithRules.remove(field);
- }
-
- public void removeMethod(DexMethod method) {
- methodsWithRules.remove(method);
- }
-
- public void removeReference(DexReference reference) {
- reference.accept(this::removeClass, this::removeField, this::removeMethod);
- }
-
- public void putAll(ItemsWithRules items) {
- items.forEachClass(this::putClassWithRules);
- items.forEachField(this::putFieldWithRules);
- items.forEachMethod(this::putMethodWithRules);
- }
-
- public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
- classesWithRules.put(type, rules);
- }
-
- public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
- fieldsWithRules.put(field, rules);
- }
-
- public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
- methodsWithRules.put(method, rules);
- }
-
- public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
- reference.accept(
- this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules);
- }
-
- public int size() {
- return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
- }
- }
-
- private void reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
- DexEncodedMethod method, ProguardAssumeNoSideEffectRule context) {
- assert method.getHolderType() == options.dexItemFactory().objectType;
- OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition());
- assumeNoSideEffectsWarnings
- .computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::compareTo))
- .add(method.getReference());
- }
-
- private boolean isWaitOrNotifyMethod(DexMethod method) {
- return method.name == options.itemFactory.waitMethodName
- || method.name == options.itemFactory.notifyMethodName
- || method.name == options.itemFactory.notifyAllMethodName;
- }
-
- private void generateAssumeNoSideEffectsWarnings() {
- if (appView.getDontWarnConfiguration().matches(options.itemFactory.objectType)) {
- // Don't report any warnings since we don't apply -assumenosideeffects rules to notify() or
- // wait() anyway.
- return;
- }
-
- assumeNoSideEffectsWarnings.forEach(
- (originWithPosition, methods) -> {
- boolean matchesWaitOrNotifyMethods =
- methods.stream().anyMatch(this::isWaitOrNotifyMethod);
- if (!matchesWaitOrNotifyMethods) {
- // We model the remaining methods on java.lang.Object, and thus there should be no need
- // to warn in this case.
- return;
- }
- options.reporter.warning(
- new AssumeNoSideEffectsRuleForObjectMembersDiagnostic.Builder()
- .addMatchedMethods(methods)
- .setOrigin(originWithPosition.getOrigin())
- .setPosition(originWithPosition.getPosition())
- .build());
- });
- }
-
- public static class RootSet extends RootSetBase {
-
- public final ImmutableList<DexReference> reasonAsked;
- public final ImmutableList<DexReference> checkDiscarded;
- public final Set<DexMethod> alwaysInline;
- public final Set<DexMethod> forceInline;
- public final Set<DexMethod> bypassClinitForInlining;
- public final Set<DexMethod> whyAreYouNotInlining;
- public final Set<DexMethod> keepConstantArguments;
- public final Set<DexMethod> keepUnusedArguments;
- public final Set<DexMethod> reprocess;
- public final Set<DexMethod> neverReprocess;
- public final PredicateSet<DexType> alwaysClassInline;
- public final Set<DexType> noUnusedInterfaceRemoval;
- public final Set<DexType> noVerticalClassMerging;
- public final Set<DexType> noHorizontalClassMerging;
- public final Set<DexType> noStaticClassMerging;
- public final Set<DexReference> neverPropagateValue;
- public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
- public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
- public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
- public final Set<DexReference> identifierNameStrings;
- public final Set<ProguardIfRule> ifRules;
-
- private RootSet(
- MutableItemsWithRules noShrinking,
- MutableItemsWithRules softPinned,
- Set<DexReference> noObfuscation,
- ImmutableList<DexReference> reasonAsked,
- ImmutableList<DexReference> checkDiscarded,
- Set<DexMethod> alwaysInline,
- Set<DexMethod> forceInline,
- Set<DexMethod> neverInline,
- Set<DexMethod> neverInlineDueToSingleCaller,
- Set<DexMethod> bypassClinitForInlining,
- Set<DexMethod> whyAreYouNotInlining,
- Set<DexMethod> keepConstantArguments,
- Set<DexMethod> keepUnusedArguments,
- Set<DexMethod> reprocess,
- Set<DexMethod> neverReprocess,
- PredicateSet<DexType> alwaysClassInline,
- Set<DexType> neverClassInline,
- Set<DexType> noUnusedInterfaceRemoval,
- Set<DexType> noVerticalClassMerging,
- Set<DexType> noHorizontalClassMerging,
- Set<DexType> noStaticClassMerging,
- Set<DexReference> neverPropagateValue,
- Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
- Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
- Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
- Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
- Set<DexReference> identifierNameStrings,
- Set<ProguardIfRule> ifRules,
- List<DelayedRootSetActionItem> delayedRootSetActionItems) {
- super(
- neverInline,
- neverInlineDueToSingleCaller,
- neverClassInline,
- noShrinking,
- softPinned,
- noObfuscation,
- dependentNoShrinking,
- dependentSoftPinned,
- dependentKeepClassCompatRule,
- delayedRootSetActionItems);
- this.reasonAsked = reasonAsked;
- this.checkDiscarded = checkDiscarded;
- this.alwaysInline = alwaysInline;
- this.forceInline = forceInline;
- this.bypassClinitForInlining = bypassClinitForInlining;
- this.whyAreYouNotInlining = whyAreYouNotInlining;
- this.keepConstantArguments = keepConstantArguments;
- this.keepUnusedArguments = keepUnusedArguments;
- this.reprocess = reprocess;
- this.neverReprocess = neverReprocess;
- this.alwaysClassInline = alwaysClassInline;
- this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
- this.noVerticalClassMerging = noVerticalClassMerging;
- this.noHorizontalClassMerging = noHorizontalClassMerging;
- this.noStaticClassMerging = noStaticClassMerging;
- this.neverPropagateValue = neverPropagateValue;
- this.mayHaveSideEffects = mayHaveSideEffects;
- this.noSideEffects = noSideEffects;
- this.assumedValues = assumedValues;
- this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
- this.ifRules = Collections.unmodifiableSet(ifRules);
- }
-
- public void checkAllRulesAreUsed(InternalOptions options) {
- List<ProguardConfigurationRule> rules = options.getProguardConfiguration().getRules();
- if (rules != null) {
- for (ProguardConfigurationRule rule : rules) {
- if (!rule.isUsed()) {
- String message =
- "Proguard configuration rule does not match anything: `" + rule.toString() + "`";
- StringDiagnostic diagnostic = new StringDiagnostic(message, rule.getOrigin());
- if (options.testing.reportUnusedProguardConfigurationRules) {
- options.reporter.info(diagnostic);
- }
- }
- }
- }
- }
-
- void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
- neverInline.addAll(consequentRootSet.neverInline);
- neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
- neverClassInline.addAll(consequentRootSet.neverClassInline);
- noObfuscation.addAll(consequentRootSet.noObfuscation);
- if (addNoShrinking) {
- noShrinking.addAll(consequentRootSet.noShrinking);
- }
- addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
- addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned);
- consequentRootSet.dependentKeepClassCompatRule.forEach(
- (type, rules) ->
- dependentKeepClassCompatRule.computeIfAbsent(
- type, k -> new HashSet<>()).addAll(rules));
- delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems);
- }
-
- // Add dependent items that depend on -if rules.
- private static void addDependentItems(
- Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd,
- Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) {
- dependentItemsToAdd.forEach(
- (reference, dependence) ->
- dependentItemsToAddTo
- .computeIfAbsent(reference, x -> new MutableItemsWithRules())
- .putAll(dependence));
- }
-
- public void copy(DexReference original, DexReference rewritten) {
- if (noShrinking.containsReference(original)) {
- noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
- }
- if (noObfuscation.contains(original)) {
- noObfuscation.add(rewritten);
- }
- if (original.isDexMember()) {
- assert rewritten.isDexMember();
- DexMember<?, ?> originalMember = original.asDexMember();
- if (noSideEffects.containsKey(originalMember)) {
- noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember));
- }
- if (assumedValues.containsKey(originalMember)) {
- assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember));
- }
- }
- }
-
- public void prune(DexReference reference) {
- noShrinking.removeReference(reference);
- noObfuscation.remove(reference);
- noSideEffects.remove(reference);
- assumedValues.remove(reference);
- }
-
- public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
- pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
- pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
- pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
- pruneDeadReferences(noStaticClassMerging, definitions, enqueuer);
- pruneDeadReferences(alwaysInline, definitions, enqueuer);
- pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
- }
-
- private static void pruneDeadReferences(
- Set<? extends DexReference> references,
- DexDefinitionSupplier definitions,
- Enqueuer enqueuer) {
- references.removeIf(
- reference -> {
- if (reference.isDexType()) {
- DexClass definition = definitions.definitionFor(reference.asDexType());
- return definition == null || !enqueuer.isTypeLive(definition);
- }
-
- assert reference.isDexMember();
-
- DexMember<?, ?> member = reference.asDexMember();
- DexClass holder = definitions.definitionForHolder(member);
- DexEncodedMember<?, ?> definition = member.lookupOnClass(holder);
- if (definition == null) {
- return true;
- }
- if (holder.isProgramClass()) {
- if (definition.isDexEncodedField()) {
- DexEncodedField field = definition.asDexEncodedField();
- return !enqueuer.isFieldReferenced(field);
- }
- assert definition.isDexEncodedMethod();
- DexEncodedMethod method = definition.asDexEncodedMethod();
- return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method);
- }
- return !enqueuer.isNonProgramTypeLive(holder);
- });
- }
-
- public void move(DexReference original, DexReference rewritten) {
- copy(original, rewritten);
- prune(original);
- }
-
- void shouldNotBeMinified(DexReference reference) {
- noObfuscation.add(reference);
- }
-
- public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
- noShrinking.forEachField(
- reference -> {
- DexClass holder = appInfo.definitionForHolder(reference);
- DexEncodedField field = reference.lookupOnClass(holder);
- if (field != null
- && (field.isStatic()
- || isKeptDirectlyOrIndirectly(field.getHolderType(), appInfo))) {
- assert appInfo.isFieldRead(field)
- : "Expected kept field `" + field.toSourceString() + "` to be read";
- assert appInfo.isFieldWritten(field)
- : "Expected kept field `" + field.toSourceString() + "` to be written";
- }
- });
- return true;
- }
-
- public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
- noShrinking.forEachMethod(
- reference -> {
- assert appInfo.isTargetedMethod(reference)
- : "Expected kept method `" + reference.toSourceString() + "` to be targeted";
- DexEncodedMethod method =
- appInfo.definitionForHolder(reference).lookupMethod(reference);
- if (!method.isAbstract()
- && isKeptDirectlyOrIndirectly(method.getHolderType(), appInfo)) {
- assert appInfo.isLiveMethod(reference)
- : "Expected non-abstract kept method `"
- + reference.toSourceString()
- + "` to be live";
- }
- });
- return true;
- }
-
- public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
- noShrinking.forEachClass(
- type -> {
- assert appInfo.isLiveProgramType(type)
- : "Expected kept type `" + type.toSourceString() + "` to be live";
- });
- return true;
- }
-
- private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
- if (noShrinking.containsClass(type)) {
- return true;
- }
- DexClass clazz = appInfo.definitionFor(type);
- if (clazz == null) {
- return false;
- }
- if (clazz.superType != null) {
- return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
- }
- return false;
- }
-
- public boolean verifyKeptItemsAreKept(AppView<? extends AppInfoWithClassHierarchy> appView) {
- AppInfoWithClassHierarchy appInfo = appView.appInfo();
- GraphLens lens = appView.graphLens();
- // Create a mapping from each required type to the set of required members on that type.
- Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>();
- noShrinking.forEachClass(
- type -> {
- DexType rewrittenType = lens.lookupType(type);
- assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenType)
- : "Expected reference `" + rewrittenType.toSourceString() + "` to be pinned";
- requiredMembersPerType.computeIfAbsent(rewrittenType, key -> Sets.newIdentityHashSet());
- });
- noShrinking.forEachMember(
- member -> {
- DexMember<?, ?> rewrittenMember = lens.getRenamedMemberSignature(member);
- assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenMember)
- : "Expected reference `" + rewrittenMember.toSourceString() + "` to be pinned";
- requiredMembersPerType
- .computeIfAbsent(rewrittenMember.holder, key -> Sets.newIdentityHashSet())
- .add(rewrittenMember);
- });
-
- // Run through each class in the program and check that it has members it must have.
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- Set<DexMember<?, ?>> requiredMembers =
- requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of());
-
- Set<DexField> fields = null;
- Set<DexMethod> methods = null;
-
- for (DexMember<?, ?> requiredMember : requiredMembers) {
- if (requiredMember.isDexField()) {
- DexField requiredField = requiredMember.asDexField();
- if (fields == null) {
- // Create a Set of the fields to avoid quadratic behavior.
- fields =
- Streams.stream(clazz.fields())
- .map(DexEncodedField::getReference)
- .collect(Collectors.toSet());
- }
- assert fields.contains(requiredField)
- : "Expected field `"
- + requiredField.toSourceString()
- + "` from the root set to be present";
- } else {
- DexMethod requiredMethod = requiredMember.asDexMethod();
- if (methods == null) {
- // Create a Set of the methods to avoid quadratic behavior.
- methods =
- Streams.stream(clazz.methods())
- .map(DexEncodedMethod::getReference)
- .collect(Collectors.toSet());
- }
- assert methods.contains(requiredMethod)
- : "Expected method `"
- + requiredMethod.toSourceString()
- + "` from the root set to be present";
- }
- }
- requiredMembersPerType.remove(clazz.type);
- }
-
- // If the map is non-empty, then a type in the root set was not in the application.
- if (!requiredMembersPerType.isEmpty()) {
- DexType type = requiredMembersPerType.keySet().iterator().next();
- DexClass clazz = appView.definitionFor(type);
- assert clazz == null || clazz.isProgramClass()
- : "Unexpected library type in root set: `" + type + "`";
- assert requiredMembersPerType.isEmpty()
- : "Expected type `" + type.toSourceString() + "` to be present";
- }
-
- return true;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("RootSet");
- builder.append("\nnoShrinking: " + noShrinking.size());
- builder.append("\nnoObfuscation: " + noObfuscation.size());
- builder.append("\nreasonAsked: " + reasonAsked.size());
- builder.append("\ncheckDiscarded: " + checkDiscarded.size());
- builder.append("\nnoSideEffects: " + noSideEffects.size());
- builder.append("\nassumedValues: " + assumedValues.size());
- builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
- builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
- builder.append("\nifRules: " + ifRules.size());
- return builder.toString();
- }
- }
-
- // A partial RootSet that becomes live due to the enabled -if rule or the addition of interface
- // keep rules.
- public static class ConsequentRootSet extends RootSetBase {
-
- ConsequentRootSet(
- Set<DexMethod> neverInline,
- Set<DexMethod> neverInlineDueToSingleCaller,
- Set<DexType> neverClassInline,
- MutableItemsWithRules noShrinking,
- MutableItemsWithRules softPinned,
- Set<DexReference> noObfuscation,
- Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
- Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
- Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
- List<DelayedRootSetActionItem> delayedRootSetActionItems) {
- super(
- neverInline,
- neverInlineDueToSingleCaller,
- neverClassInline,
- noShrinking,
- softPinned,
- noObfuscation,
- dependentNoShrinking,
- dependentSoftPinned,
- dependentKeepClassCompatRule,
- delayedRootSetActionItems);
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
new file mode 100644
index 0000000..f4567e9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -0,0 +1,2196 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMember;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
+import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
+import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Consumer3;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OriginWithPosition;
+import com.android.tools.r8.utils.PredicateSet;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.io.PrintStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class RootSetUtils {
+
+ public static class RootSetBuilder {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final SubtypingInfo subtypingInfo;
+ private final DirectMappedDexApplication application;
+ private final Iterable<? extends ProguardConfigurationRule> rules;
+ private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
+ private final MutableItemsWithRules softPinned = new MutableItemsWithRules();
+ private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
+ private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
+ private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
+ private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet();
+ private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
+ private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
+ private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
+ private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
+ private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
+ private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
+ private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
+ private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
+ private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet();
+ private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
+ private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
+ private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
+ private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
+ new IdentityHashMap<>();
+ private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned =
+ new IdentityHashMap<>();
+ private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
+ new IdentityHashMap<>();
+ private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
+ new IdentityHashMap<>();
+ private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
+ private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
+ private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
+ private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
+ new ConcurrentLinkedQueue<>();
+ private final InternalOptions options;
+
+ private final DexStringCache dexStringCache = new DexStringCache();
+ private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
+
+ private final Map<OriginWithPosition, Set<DexMethod>> assumeNoSideEffectsWarnings =
+ new LinkedHashMap<>();
+
+ private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance();
+
+ private RootSetBuilder(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ SubtypingInfo subtypingInfo,
+ Iterable<? extends ProguardConfigurationRule> rules) {
+ this.appView = appView;
+ this.subtypingInfo = subtypingInfo;
+ this.application = appView.appInfo().app().asDirect();
+ this.rules = rules;
+ this.options = appView.options();
+ }
+
+ private RootSetBuilder(
+ AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+ this(appView, subtypingInfo, null);
+ }
+
+ void handleMatchedAnnotation(AnnotationMatchResult annotation) {
+ // Intentionally empty.
+ }
+
+ // Process a class with the keep rule.
+ private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
+ if (!satisfyClassType(rule, clazz)) {
+ return;
+ }
+ if (!satisfyAccessFlag(rule, clazz)) {
+ return;
+ }
+ AnnotationMatchResult annotationMatchResult = satisfyAnnotation(rule, clazz);
+ if (annotationMatchResult == null) {
+ return;
+ }
+ handleMatchedAnnotation(annotationMatchResult);
+ // In principle it should make a difference whether the user specified in a class
+ // spec that a class either extends or implements another type. However, proguard
+ // seems not to care, so users have started to use this inconsistently. We are thus
+ // inconsistent, as well, but tell them.
+ // TODO(herhut): One day make this do what it says.
+ if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) {
+ return;
+ }
+
+ if (!rule.getClassNames().matches(clazz.type)) {
+ return;
+ }
+
+ Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+ if (rule instanceof ProguardKeepRule) {
+ if (clazz.isNotProgramClass()) {
+ return;
+ }
+ switch (((ProguardKeepRule) rule).getType()) {
+ case KEEP_CLASS_MEMBERS:
+ // Members mentioned at -keepclassmembers always depend on their holder.
+ preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
+ markMatchingVisibleMethods(
+ clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+ markMatchingVisibleFields(
+ clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+ break;
+ case KEEP_CLASSES_WITH_MEMBERS:
+ if (!allRulesSatisfied(memberKeepRules, clazz)) {
+ break;
+ }
+ // fall through;
+ case KEEP:
+ markClass(clazz, rule, ifRule);
+ preconditionSupplier = new HashMap<>();
+ if (ifRule != null) {
+ // Static members in -keep are pinned no matter what.
+ preconditionSupplier.put(DexDefinition::isStaticMember, null);
+ // Instance members may need to be kept even though the holder is not instantiated.
+ preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
+ } else {
+ // Members mentioned at -keep should always be pinned as long as that -keep rule is
+ // not triggered conditionally.
+ preconditionSupplier.put((definition -> true), null);
+ }
+ markMatchingVisibleMethods(
+ clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+ markMatchingVisibleFields(
+ clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+ break;
+ case CONDITIONAL:
+ throw new Unreachable("-if rule will be evaluated separately, not here.");
+ }
+ return;
+ }
+ // Only the ordinary keep rules are supported in a conditional rule.
+ assert ifRule == null;
+ if (rule instanceof ProguardIfRule) {
+ throw new Unreachable("-if rule will be evaluated separately, not here.");
+ } else if (rule instanceof ProguardCheckDiscardRule) {
+ if (memberKeepRules.isEmpty()) {
+ markClass(clazz, rule, ifRule);
+ } else {
+ preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+ markMatchingVisibleMethods(
+ clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
+ markMatchingVisibleFields(
+ clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
+ }
+ } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
+ markClass(clazz, rule, ifRule);
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+ } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
+ || rule instanceof ProguardAssumeNoSideEffectRule
+ || rule instanceof ProguardAssumeValuesRule) {
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingOverriddenMethods(
+ appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+ } else if (rule instanceof InlineRule
+ || rule instanceof ConstantArgumentRule
+ || rule instanceof UnusedArgumentRule
+ || rule instanceof ReprocessMethodRule
+ || rule instanceof WhyAreYouNotInliningRule) {
+ markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
+ } else if (rule instanceof ClassInlineRule
+ || rule instanceof NoUnusedInterfaceRemovalRule
+ || rule instanceof NoVerticalClassMergingRule
+ || rule instanceof NoHorizontalClassMergingRule
+ || rule instanceof ReprocessClassInitializerRule) {
+ if (allRulesSatisfied(memberKeepRules, clazz)) {
+ markClass(clazz, rule, ifRule);
+ }
+ } else if (rule instanceof MemberValuePropagationRule) {
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+ } else {
+ assert rule instanceof ProguardIdentifierNameStringRule;
+ markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
+ markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
+ }
+ }
+
+ void runPerRule(
+ ExecutorService executorService,
+ List<Future<?>> futures,
+ ProguardConfigurationRule rule,
+ ProguardIfRule ifRule) {
+ List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
+ if (specifics != null) {
+ // This keep rule only lists specific type matches.
+ // This means there is no need to iterate over all classes.
+ for (DexType type : specifics) {
+ DexClass clazz = application.definitionFor(type);
+ // Ignore keep rule iff it does not reference a class in the app.
+ if (clazz != null) {
+ process(clazz, rule, ifRule);
+ }
+ }
+ return;
+ }
+
+ futures.add(
+ executorService.submit(
+ () -> {
+ for (DexProgramClass clazz :
+ rule.relevantCandidatesForRule(appView, subtypingInfo, application.classes())) {
+ process(clazz, rule, ifRule);
+ }
+ if (rule.applyToNonProgramClasses()) {
+ for (DexLibraryClass clazz : application.libraryClasses()) {
+ process(clazz, rule, ifRule);
+ }
+ }
+ }));
+ }
+
+ public RootSet build(ExecutorService executorService) throws ExecutionException {
+ application.timing.begin("Build root set...");
+ try {
+ List<Future<?>> futures = new ArrayList<>();
+ // Mark all the things explicitly listed in keep rules.
+ if (rules != null) {
+ for (ProguardConfigurationRule rule : rules) {
+ if (rule instanceof ProguardIfRule) {
+ ProguardIfRule ifRule = (ProguardIfRule) rule;
+ ifRules.add(ifRule);
+ } else {
+ runPerRule(executorService, futures, rule, null);
+ }
+ }
+ ThreadUtils.awaitFutures(futures);
+ }
+ } finally {
+ application.timing.end();
+ }
+ generateAssumeNoSideEffectsWarnings();
+ if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
+ BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
+ .visit(appView.appInfo().classes(), this::propagateAssumeRules);
+ }
+ if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
+ GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
+ appView,
+ subtypingInfo,
+ alwaysClassInline,
+ noVerticalClassMerging,
+ noHorizontalClassMerging,
+ alwaysInline,
+ bypassClinitforInlining);
+ }
+ assert Sets.intersection(neverInline, alwaysInline).isEmpty()
+ && Sets.intersection(neverInline, forceInline).isEmpty()
+ : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
+ assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty();
+ return new RootSet(
+ noShrinking,
+ softPinned,
+ noObfuscation,
+ ImmutableList.copyOf(reasonAsked.values()),
+ ImmutableList.copyOf(checkDiscarded.values()),
+ alwaysInline,
+ forceInline,
+ neverInline,
+ neverInlineDueToSingleCaller,
+ bypassClinitforInlining,
+ whyAreYouNotInlining,
+ keepParametersWithConstantValue,
+ keepUnusedArguments,
+ reprocess,
+ neverReprocess,
+ alwaysClassInline,
+ neverClassInline,
+ noUnusedInterfaceRemoval,
+ noVerticalClassMerging,
+ noHorizontalClassMerging,
+ neverPropagateValue,
+ mayHaveSideEffects,
+ noSideEffects,
+ assumedValues,
+ dependentNoShrinking,
+ dependentSoftPinned,
+ dependentKeepClassCompatRule,
+ identifierNameStrings,
+ ifRules,
+ Lists.newArrayList(delayedRootSetActionItems));
+ }
+
+ private void propagateAssumeRules(DexClass clazz) {
+ Set<DexType> subTypes = subtypingInfo.allImmediateSubtypes(clazz.type);
+ if (subTypes.isEmpty()) {
+ return;
+ }
+ for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+ // If the method has a body, it may have side effects. Don't do bottom-up propagation.
+ if (encodedMethod.hasCode()) {
+ assert !encodedMethod.shouldNotHaveCode();
+ continue;
+ }
+ propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, noSideEffects);
+ propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, assumedValues);
+ }
+ }
+
+ private void propagateAssumeRules(
+ DexType type,
+ DexMethod reference,
+ Set<DexType> subTypes,
+ Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) {
+ ProguardMemberRule ruleToBePropagated = null;
+ for (DexType subType : subTypes) {
+ DexMethod referenceInSubType =
+ appView.dexItemFactory().createMethod(subType, reference.proto, reference.name);
+ // Those rules are bound to definitions, not references. If the current subtype does not
+ // override the method, and when the retrieval of bound rule fails, it is unclear whether it
+ // is due to the lack of the definition or it indeed means no matching rules. Similar to how
+ // we apply those assume rules, here we use a resolved target.
+ DexEncodedMethod target =
+ appView
+ .appInfo()
+ .unsafeResolveMethodDueToDexFormat(referenceInSubType)
+ .getSingleTarget();
+ // But, the resolution should not be landed on the current type we are visiting.
+ if (target == null || target.getHolderType() == type) {
+ continue;
+ }
+ ProguardMemberRule ruleInSubType = assumeRulePool.get(target.method);
+ // We are looking for the greatest lower bound of assume rules from all sub types.
+ // If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
+ if (ruleInSubType == null) {
+ ruleToBePropagated = null;
+ break;
+ }
+ if (ruleToBePropagated == null) {
+ ruleToBePropagated = ruleInSubType;
+ } else {
+ // TODO(b/133208961): Introduce comparison/meet of assume rules.
+ if (!ruleToBePropagated.equals(ruleInSubType)) {
+ ruleToBePropagated = null;
+ break;
+ }
+ }
+ }
+ if (ruleToBePropagated != null) {
+ assumeRulePool.put(reference, ruleToBePropagated);
+ }
+ }
+
+ ConsequentRootSet buildConsequentRootSet() {
+ return new ConsequentRootSet(
+ neverInline,
+ neverInlineDueToSingleCaller,
+ neverClassInline,
+ noShrinking,
+ softPinned,
+ noObfuscation,
+ dependentNoShrinking,
+ dependentSoftPinned,
+ dependentKeepClassCompatRule,
+ Lists.newArrayList(delayedRootSetActionItems));
+ }
+
+ private static DexDefinition testAndGetPrecondition(
+ DexDefinition definition,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+ if (preconditionSupplier == null) {
+ return null;
+ }
+ DexDefinition precondition = null;
+ boolean conditionEverMatched = false;
+ for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) {
+ if (entry.getKey().test(definition)) {
+ precondition = entry.getValue();
+ conditionEverMatched = true;
+ break;
+ }
+ }
+ // If precondition-supplier is given, there should be at least one predicate that holds.
+ // Actually, there should be only one predicate as we break the loop when it is found.
+ assert conditionEverMatched;
+ return precondition;
+ }
+
+ private void markMatchingVisibleMethods(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ boolean includeLibraryClasses,
+ ProguardIfRule ifRule) {
+ Set<Wrapper<DexMethod>> methodsMarked =
+ options.forceProguardCompatibility ? null : new HashSet<>();
+ Stack<DexClass> worklist = new Stack<>();
+ worklist.add(clazz);
+ while (!worklist.isEmpty()) {
+ DexClass currentClass = worklist.pop();
+ if (!includeLibraryClasses && currentClass.isNotProgramClass()) {
+ break;
+ }
+ // In compat mode traverse all direct methods in the hierarchy.
+ if (currentClass == clazz || options.forceProguardCompatibility) {
+ currentClass
+ .directMethods()
+ .forEach(
+ method -> {
+ DexDefinition precondition =
+ testAndGetPrecondition(method, preconditionSupplier);
+ markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
+ });
+ }
+ currentClass
+ .virtualMethods()
+ .forEach(
+ method -> {
+ DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+ markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
+ });
+ if (currentClass.superType != null) {
+ DexClass dexClass = application.definitionFor(currentClass.superType);
+ if (dexClass != null) {
+ worklist.add(dexClass);
+ }
+ }
+ }
+ // TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
+ // fullmode.
+ if (clazz.isProgramClass()
+ && rule.isProguardKeepRule()
+ && !rule.asProguardKeepRule().getModifiers().allowsShrinking) {
+ new SynthesizeMissingInterfaceMethodsForMemberRules(
+ clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule)
+ .run();
+ }
+ }
+
+ /**
+ * Utility class for visiting all super interfaces to ensure we keep method definitions
+ * specified by proguard rules. If possible, we generate a forwarding bridge to the resolved
+ * target. If not, we specifically synthesize a keep rule for the interface method.
+ */
+ private class SynthesizeMissingInterfaceMethodsForMemberRules {
+
+ private final DexProgramClass originalClazz;
+ private final Collection<ProguardMemberRule> memberKeepRules;
+ private final ProguardConfigurationRule context;
+ private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+ private final ProguardIfRule ifRule;
+ private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
+ private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
+
+ private SynthesizeMissingInterfaceMethodsForMemberRules(
+ DexProgramClass originalClazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule context,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ ProguardIfRule ifRule) {
+ assert context.isProguardKeepRule();
+ assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
+ this.originalClazz = originalClazz;
+ this.memberKeepRules = memberKeepRules;
+ this.context = context;
+ this.preconditionSupplier = preconditionSupplier;
+ this.ifRule = ifRule;
+ }
+
+ void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
+ // Intentionally empty.
+ }
+
+ void run() {
+ visitAllSuperInterfaces(originalClazz.type);
+ }
+
+ private void visitAllSuperInterfaces(DexType type) {
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz == null || clazz.isNotProgramClass() || !seenTypes.add(type)) {
+ return;
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ visitAllSuperInterfaces(iface);
+ }
+ if (!clazz.isInterface()) {
+ visitAllSuperInterfaces(clazz.superType);
+ return;
+ }
+ if (originalClazz == clazz) {
+ return;
+ }
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ // Check if we already added this.
+ Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.method);
+ if (!seenMethods.add(wrapped)) {
+ continue;
+ }
+ for (ProguardMemberRule rule : memberKeepRules) {
+ if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
+ tryAndKeepMethodOnClass(method, rule);
+ }
+ }
+ }
+ }
+
+ private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
+ SingleResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOn(originalClazz, method.method).asSingleResolution();
+ if (resolutionResult == null || !resolutionResult.isVirtualTarget()) {
+ return;
+ }
+ if (resolutionResult.getResolvedHolder() == originalClazz
+ || resolutionResult.getResolvedHolder().isNotProgramClass()) {
+ return;
+ }
+ if (!resolutionResult.getResolvedHolder().isInterface()) {
+ // TODO(b/143643942): For fullmode, this check should probably be removed.
+ return;
+ }
+ ProgramMethod resolutionMethod =
+ new ProgramMethod(
+ resolutionResult.getResolvedHolder().asProgramClass(),
+ resolutionResult.getResolvedMethod());
+ ProgramMethod methodToKeep =
+ canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())
+ ? new ProgramMethod(
+ originalClazz,
+ resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView))
+ : resolutionMethod;
+
+ delayedRootSetActionItems.add(
+ new InterfaceMethodSyntheticBridgeAction(
+ methodToKeep,
+ resolutionMethod,
+ (rootSetBuilder) -> {
+ if (Log.ENABLED) {
+ Log.verbose(
+ getClass(),
+ "Marking method `%s` due to `%s { %s }`.",
+ methodToKeep,
+ context,
+ rule);
+ }
+ DexDefinition precondition =
+ testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
+ rootSetBuilder.addItemToSets(
+ methodToKeep.getDefinition(), context, rule, precondition, ifRule);
+ }));
+ }
+ }
+
+ private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
+ return appView.options().isGeneratingDex()
+ || ArrayUtils.contains(holder.interfaces.values, target.getHolderType());
+ }
+
+ private void markMatchingOverriddenMethods(
+ AppInfoWithClassHierarchy appInfoWithSubtyping,
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ boolean onlyIncludeProgramClasses,
+ ProguardIfRule ifRule) {
+ Set<DexType> visited = new HashSet<>();
+ Deque<DexType> worklist = new ArrayDeque<>();
+ // Intentionally skip the current `clazz`, assuming it's covered by
+ // markMatchingVisibleMethods.
+ worklist.addAll(subtypingInfo.allImmediateSubtypes(clazz.type));
+
+ while (!worklist.isEmpty()) {
+ DexType currentType = worklist.poll();
+ if (!visited.add(currentType)) {
+ continue;
+ }
+ DexClass currentClazz = appView.definitionFor(currentType);
+ if (currentClazz == null) {
+ continue;
+ }
+ if (!onlyIncludeProgramClasses && currentClazz.isNotProgramClass()) {
+ continue;
+ }
+ currentClazz
+ .virtualMethods()
+ .forEach(
+ method -> {
+ DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+ markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
+ });
+ worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type));
+ }
+ }
+
+ private void markMatchingMethods(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ ProguardIfRule ifRule) {
+ clazz.forEachMethod(
+ method -> {
+ DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+ markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
+ });
+ }
+
+ private void markMatchingVisibleFields(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ boolean includeLibraryClasses,
+ ProguardIfRule ifRule) {
+ while (clazz != null) {
+ if (!includeLibraryClasses && clazz.isNotProgramClass()) {
+ return;
+ }
+ clazz.forEachField(
+ field -> {
+ DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+ markField(field, memberKeepRules, rule, precondition, ifRule);
+ });
+ clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
+ }
+ }
+
+ private void markMatchingFields(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ ProguardIfRule ifRule) {
+ clazz.forEachField(
+ field -> {
+ DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+ markField(field, memberKeepRules, rule, precondition, ifRule);
+ });
+ }
+
+ // TODO(b/67934426): Test this code.
+ public static void writeSeeds(
+ AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedType(
+ type -> {
+ if (include.test(type)) {
+ out.println(type.toSourceString());
+ }
+ });
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedField(
+ field -> {
+ if (include.test(field.holder)) {
+ out.println(
+ field.holder.toSourceString()
+ + ": "
+ + field.type.toSourceString()
+ + " "
+ + field.name.toSourceString());
+ }
+ });
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedMethod(
+ method -> {
+ if (!include.test(method.holder)) {
+ return;
+ }
+ DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method));
+ DexEncodedMethod definition = method.lookupOnClass(holder);
+ if (definition == null) {
+ assert method.match(appInfo.dexItemFactory().deserializeLambdaMethod);
+ return;
+ }
+ out.print(method.holder.toSourceString() + ": ");
+ if (definition.isClassInitializer()) {
+ out.print(Constants.CLASS_INITIALIZER_NAME);
+ } else if (definition.isInstanceInitializer()) {
+ String holderName = method.holder.toSourceString();
+ String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
+ out.print(constrName);
+ } else {
+ out.print(
+ method.proto.returnType.toSourceString()
+ + " "
+ + method.name.toSourceString());
+ }
+ boolean first = true;
+ out.print("(");
+ for (DexType param : method.proto.parameters.values) {
+ if (!first) {
+ out.print(",");
+ }
+ first = false;
+ out.print(param.toSourceString());
+ }
+ out.println(")");
+ });
+ out.close();
+ }
+
+ static boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
+ return rule.getClassType().matches(clazz) != rule.getClassTypeNegated();
+ }
+
+ static boolean satisfyAccessFlag(ProguardConfigurationRule rule, DexClass clazz) {
+ return rule.getClassAccessFlags().containsAll(clazz.accessFlags)
+ && rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags);
+ }
+
+ static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
+ return containsAllAnnotations(rule.getClassAnnotations(), clazz);
+ }
+
+ boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
+ if (satisfyExtendsRule(clazz, rule)) {
+ return true;
+ }
+
+ return satisfyImplementsRule(clazz, rule);
+ }
+
+ boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) {
+ if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) {
+ return true;
+ }
+ // It is possible that this class used to inherit from another class X, but no longer does it,
+ // because X has been merged into `clazz`.
+ return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false);
+ }
+
+ boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) {
+ while (type != null) {
+ DexClass clazz = application.definitionFor(type);
+ if (clazz == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `class`.
+ if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
+ AnnotationMatchResult annotationMatchResult =
+ containsAllAnnotations(rule.getInheritanceAnnotations(), clazz);
+ if (annotationMatchResult != null) {
+ handleMatchedAnnotation(annotationMatchResult);
+ return true;
+ }
+ }
+ type = clazz.superType;
+ }
+ return false;
+ }
+
+ boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) {
+ if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) {
+ return true;
+ }
+ // It is possible that this class used to implement an interface I, but no longer does it,
+ // because I has been merged into `clazz`.
+ return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true);
+ }
+
+ private boolean anyImplementedInterfaceMatchesImplementsRule(
+ DexClass clazz, ProguardConfigurationRule rule) {
+ // TODO(herhut): Maybe it would be better to do this breadth first.
+ if (clazz == null) {
+ return false;
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ DexClass ifaceClass = application.definitionFor(iface);
+ if (ifaceClass == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `ifaceClass`.
+ if (rule.getInheritanceClassName().matches(iface, appView)) {
+ AnnotationMatchResult annotationMatchResult =
+ containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass);
+ if (annotationMatchResult != null) {
+ handleMatchedAnnotation(annotationMatchResult);
+ return true;
+ }
+ }
+ if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
+ return true;
+ }
+ }
+ if (clazz.superType == null) {
+ return false;
+ }
+ DexClass superClass = application.definitionFor(clazz.superType);
+ if (superClass == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ return anyImplementedInterfaceMatchesImplementsRule(superClass, rule);
+ }
+
+ private boolean anySourceMatchesInheritanceRuleDirectly(
+ DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
+ // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
+ // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
+ return appView.verticallyMergedClasses() != null
+ && appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
+ .filter(
+ sourceType ->
+ appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
+ .anyMatch(rule.getInheritanceClassName()::matches);
+ }
+
+ private boolean allRulesSatisfied(
+ Collection<ProguardMemberRule> memberKeepRules, DexClass clazz) {
+ for (ProguardMemberRule rule : memberKeepRules) {
+ if (!ruleSatisfied(rule, clazz)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether the given rule is satisfied by this clazz, not taking superclasses into
+ * account.
+ */
+ private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
+ return ruleSatisfiedByMethods(rule, clazz.directMethods())
+ || ruleSatisfiedByMethods(rule, clazz.virtualMethods())
+ || ruleSatisfiedByFields(rule, clazz.staticFields())
+ || ruleSatisfiedByFields(rule, clazz.instanceFields());
+ }
+
+ boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
+ if (rule.getRuleType().includesMethods()) {
+ for (DexEncodedMethod method : methods) {
+ if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
+ if (rule.getRuleType().includesFields()) {
+ for (DexEncodedField field : fields) {
+ if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static AnnotationMatchResult containsAllAnnotations(
+ List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
+ return containsAllAnnotations(annotationMatchers, clazz.annotations());
+ }
+
+ static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ boolean containsAllAnnotations(
+ List<ProguardTypeMatcher> annotationMatchers,
+ DexEncodedMember<D, R> member,
+ Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
+ AnnotationMatchResult annotationMatchResult =
+ containsAllAnnotations(annotationMatchers, member.annotations());
+ if (annotationMatchResult != null) {
+ matchedAnnotationsConsumer.accept(annotationMatchResult);
+ return true;
+ }
+ if (member.isDexEncodedMethod()) {
+ DexEncodedMethod method = member.asDexEncodedMethod();
+ for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
+ annotationMatchResult =
+ containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
+ if (annotationMatchResult != null) {
+ matchedAnnotationsConsumer.accept(annotationMatchResult);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static AnnotationMatchResult containsAllAnnotations(
+ List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
+ if (annotationMatchers.isEmpty()) {
+ return AnnotationsIgnoredMatchResult.getInstance();
+ }
+ List<DexAnnotation> matchedAnnotations = new ArrayList<>();
+ for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
+ DexAnnotation matchedAnnotation =
+ getFirstAnnotationThatMatches(annotationMatcher, annotations);
+ if (matchedAnnotation == null) {
+ return null;
+ }
+ matchedAnnotations.add(matchedAnnotation);
+ }
+ return new ConcreteAnnotationMatchResult(matchedAnnotations);
+ }
+
+ private static DexAnnotation getFirstAnnotationThatMatches(
+ ProguardTypeMatcher annotationMatcher, DexAnnotationSet annotations) {
+ for (DexAnnotation annotation : annotations.annotations) {
+ if (annotationMatcher.matches(annotation.getAnnotationType())) {
+ return annotation;
+ }
+ }
+ return null;
+ }
+
+ private void markMethod(
+ DexEncodedMethod method,
+ Collection<ProguardMemberRule> rules,
+ Set<Wrapper<DexMethod>> methodsMarked,
+ ProguardConfigurationRule context,
+ DexDefinition precondition,
+ ProguardIfRule ifRule) {
+ if (methodsMarked != null
+ && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
+ // Ignore, method is overridden in sub class.
+ return;
+ }
+ for (ProguardMemberRule rule : rules) {
+ if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
+ if (Log.ENABLED) {
+ Log.verbose(
+ getClass(), "Marking method `%s` due to `%s { %s }`.", method, context, rule);
+ }
+ if (methodsMarked != null) {
+ methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
+ }
+ addItemToSets(method, context, rule, precondition, ifRule);
+ }
+ }
+ }
+
+ private void markField(
+ DexEncodedField field,
+ Collection<ProguardMemberRule> rules,
+ ProguardConfigurationRule context,
+ DexDefinition precondition,
+ ProguardIfRule ifRule) {
+ for (ProguardMemberRule rule : rules) {
+ if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context, rule);
+ }
+ addItemToSets(field, context, rule, precondition, ifRule);
+ }
+ }
+ }
+
+ private void markClass(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Marking class `%s` due to `%s`.", clazz.type, rule);
+ }
+ addItemToSets(clazz, rule, null, null, ifRule);
+ }
+
+ private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRuleBase context) {
+ if (type.isVoidType()) {
+ return;
+ }
+ if (type.isArrayType()) {
+ type = type.toBaseType(appView.dexItemFactory());
+ }
+ if (type.isPrimitiveType()) {
+ return;
+ }
+ DexClass definition = appView.definitionFor(type);
+ if (definition == null || definition.isNotProgramClass()) {
+ return;
+ }
+ // Keep the type if the item is also kept.
+ dependentNoShrinking
+ .computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules())
+ .addClassWithRule(type, context);
+ // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
+ if (appView.options().isMinificationEnabled()) {
+ noObfuscation.add(type);
+ }
+ }
+
+ private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
+ if (item.isDexEncodedMethod()) {
+ DexMethod method = item.asDexEncodedMethod().method;
+ includeDescriptor(item, method.proto.returnType, context);
+ for (DexType value : method.proto.parameters.values) {
+ includeDescriptor(item, value, context);
+ }
+ } else if (item.isDexEncodedField()) {
+ DexField field = item.asDexEncodedField().field;
+ includeDescriptor(item, field.type, context);
+ } else {
+ assert item.isDexClass();
+ }
+ }
+
+ private synchronized void addItemToSets(
+ DexDefinition item,
+ ProguardConfigurationRule context,
+ ProguardMemberRule rule,
+ DexDefinition precondition,
+ ProguardIfRule ifRule) {
+ if (context instanceof ProguardKeepRule) {
+ if (item.isDexEncodedField()) {
+ DexEncodedField encodedField = item.asDexEncodedField();
+ if (encodedField.getOptimizationInfo().cannotBeKept()) {
+ // We should only ever get here with if rules.
+ assert ifRule != null;
+ return;
+ }
+ } else if (item.isDexEncodedMethod()) {
+ DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
+ if (encodedMethod.isClassInitializer() && !options.debug) {
+ // Don't keep class initializers.
+ return;
+ }
+ if (encodedMethod.getOptimizationInfo().cannotBeKept()) {
+ // We should only ever get here with if rules.
+ assert ifRule != null;
+ return;
+ }
+ if (options.isGeneratingDex()
+ && encodedMethod.method.isLambdaDeserializeMethod(appView.dexItemFactory())) {
+ // Don't keep lambda deserialization methods.
+ return;
+ }
+ // If desugaring is enabled, private and static interface methods will be moved to a
+ // companion class. So we don't need to add them to the root set in the beginning.
+ if (options.isInterfaceMethodDesugaringEnabled()
+ && encodedMethod.hasCode()
+ && (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) {
+ DexClass holder = appView.definitionFor(encodedMethod.getHolderType());
+ if (holder != null && holder.isInterface()) {
+ if (rule.isSpecific()) {
+ options.reporter.warning(
+ new StringDiagnostic(
+ "The rule `"
+ + rule
+ + "` is ignored because the targeting interface method `"
+ + encodedMethod.method.toSourceString()
+ + "` will be desugared."));
+ }
+ return;
+ }
+ }
+ }
+
+ // The reason for keeping should link to the conditional rule as a whole, if present.
+ ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context;
+ // The modifiers are specified on the actual keep rule (ie, the consequent/context).
+ ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers();
+ // In compatibility mode, for a match on instance members a referenced class becomes live.
+ if (options.forceProguardCompatibility
+ && !modifiers.allowsShrinking
+ && precondition != null
+ && precondition.isDexClass()) {
+ if (!item.isDexClass() && !item.isStaticMember()) {
+ dependentKeepClassCompatRule
+ .computeIfAbsent(precondition.asDexClass().getType(), i -> new HashSet<>())
+ .add(keepRule);
+ context.markAsUsed();
+ }
+ }
+ if (!modifiers.allowsShrinking) {
+ if (precondition != null) {
+ dependentNoShrinking
+ .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
+ .addReferenceWithRule(item.getReference(), keepRule);
+ } else {
+ noShrinking.addReferenceWithRule(item.getReference(), keepRule);
+ }
+ context.markAsUsed();
+ } else if (!modifiers.allowsOptimization) {
+ if (precondition != null) {
+ dependentSoftPinned
+ .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
+ .addReferenceWithRule(item.getReference(), keepRule);
+ } else {
+ softPinned.addReferenceWithRule(item.getReference(), keepRule);
+ }
+ }
+ if (!modifiers.allowsOptimization) {
+ // The -dontoptimize flag has only effect through the keep all rule, but we still
+ // need to mark the rule as used.
+ context.markAsUsed();
+ }
+
+ if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
+ noObfuscation.add(item.getReference());
+ context.markAsUsed();
+ }
+ if (modifiers.includeDescriptorClasses) {
+ includeDescriptorClasses(item, keepRule);
+ context.markAsUsed();
+ }
+ } else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
+ mayHaveSideEffects.put(item.getReference(), rule);
+ context.markAsUsed();
+ } else if (context instanceof ProguardAssumeNoSideEffectRule) {
+ if (item.isDexEncodedMember()) {
+ DexEncodedMember<?, ?> member = item.asDexEncodedMember();
+ if (member.getHolderType() == appView.dexItemFactory().objectType) {
+ assert member.isDexEncodedMethod();
+ reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
+ member.asDexEncodedMethod(), (ProguardAssumeNoSideEffectRule) context);
+ } else {
+ noSideEffects.put(member.getReference(), rule);
+ if (member.isDexEncodedMethod()) {
+ DexEncodedMethod method = member.asDexEncodedMethod();
+ if (method.isClassInitializer()) {
+ feedback.classInitializerMayBePostponed(method);
+ }
+ }
+ }
+ context.markAsUsed();
+ }
+ } else if (context instanceof ProguardWhyAreYouKeepingRule) {
+ reasonAsked.computeIfAbsent(item.getReference(), i -> i);
+ context.markAsUsed();
+ } else if (context instanceof ProguardAssumeValuesRule) {
+ if (item.isDexEncodedMember()) {
+ assumedValues.put(item.asDexEncodedMember().getReference(), rule);
+ context.markAsUsed();
+ }
+ } else if (context instanceof ProguardCheckDiscardRule) {
+ checkDiscarded.computeIfAbsent(item.getReference(), i -> i);
+ context.markAsUsed();
+ } else if (context instanceof InlineRule) {
+ if (item.isDexEncodedMethod()) {
+ DexMethod reference = item.asDexEncodedMethod().getReference();
+ switch (((InlineRule) context).getType()) {
+ case ALWAYS:
+ alwaysInline.add(reference);
+ break;
+ case FORCE:
+ forceInline.add(reference);
+ break;
+ case NEVER:
+ neverInline.add(reference);
+ break;
+ case NEVER_SINGLE_CALLER:
+ neverInlineDueToSingleCaller.add(reference);
+ break;
+ default:
+ throw new Unreachable();
+ }
+ context.markAsUsed();
+ }
+ } else if (context instanceof WhyAreYouNotInliningRule) {
+ if (!item.isDexEncodedMethod()) {
+ throw new Unreachable();
+ }
+ whyAreYouNotInlining.add(item.asDexEncodedMethod().method);
+ context.markAsUsed();
+ } else if (context.isClassInlineRule()) {
+ ClassInlineRule classInlineRule = context.asClassInlineRule();
+ DexClass clazz = item.asDexClass();
+ if (clazz == null) {
+ throw new IllegalStateException(
+ "Unexpected -"
+ + classInlineRule.typeString()
+ + " rule for a non-class type: `"
+ + item.getReference().toSourceString()
+ + "`");
+ }
+ switch (classInlineRule.getType()) {
+ case ALWAYS:
+ alwaysClassInline.addElement(item.asDexClass().type);
+ break;
+ case NEVER:
+ neverClassInline.add(item.asDexClass().type);
+ break;
+ default:
+ throw new Unreachable();
+ }
+ context.markAsUsed();
+ } else if (context instanceof NoUnusedInterfaceRemovalRule) {
+ noUnusedInterfaceRemoval.add(item.asDexClass().type);
+ context.markAsUsed();
+ } else if (context instanceof NoVerticalClassMergingRule) {
+ noVerticalClassMerging.add(item.asDexClass().type);
+ context.markAsUsed();
+ } else if (context instanceof NoHorizontalClassMergingRule) {
+ noHorizontalClassMerging.add(item.asDexClass().type);
+ context.markAsUsed();
+ } else if (context instanceof MemberValuePropagationRule) {
+ switch (((MemberValuePropagationRule) context).getType()) {
+ case NEVER:
+ // Only add members from propgram classes to `neverPropagateValue` since class member
+ // values from library types are not propagated by default.
+ if (item.isDexEncodedField()) {
+ DexEncodedField field = item.asDexEncodedField();
+ if (field.isProgramField(appView)) {
+ neverPropagateValue.add(item.asDexEncodedField().field);
+ context.markAsUsed();
+ }
+ } else if (item.isDexEncodedMethod()) {
+ DexEncodedMethod method = item.asDexEncodedMethod();
+ if (method.isProgramMethod(appView)) {
+ neverPropagateValue.add(item.asDexEncodedMethod().method);
+ context.markAsUsed();
+ }
+ }
+ break;
+ default:
+ throw new Unreachable();
+ }
+ } else if (context instanceof ProguardIdentifierNameStringRule) {
+ if (item.isDexEncodedField()) {
+ identifierNameStrings.add(item.asDexEncodedField().field);
+ context.markAsUsed();
+ } else if (item.isDexEncodedMethod()) {
+ identifierNameStrings.add(item.asDexEncodedMethod().method);
+ context.markAsUsed();
+ }
+ } else if (context instanceof ConstantArgumentRule) {
+ if (item.isDexEncodedMethod()) {
+ keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
+ context.markAsUsed();
+ }
+ } else if (context instanceof ReprocessClassInitializerRule) {
+ DexProgramClass clazz = item.asProgramClass();
+ if (clazz != null && clazz.hasClassInitializer()) {
+ switch (context.asReprocessClassInitializerRule().getType()) {
+ case ALWAYS:
+ reprocess.add(clazz.getClassInitializer().method);
+ break;
+ case NEVER:
+ neverReprocess.add(clazz.getClassInitializer().method);
+ break;
+ default:
+ throw new Unreachable();
+ }
+ context.markAsUsed();
+ }
+ } else if (context.isReprocessMethodRule()) {
+ if (item.isDexEncodedMethod()) {
+ DexEncodedMethod method = item.asDexEncodedMethod();
+ switch (context.asReprocessMethodRule().getType()) {
+ case ALWAYS:
+ reprocess.add(method.method);
+ break;
+ case NEVER:
+ neverReprocess.add(method.method);
+ break;
+ default:
+ throw new Unreachable();
+ }
+ context.markAsUsed();
+ }
+ } else if (context instanceof UnusedArgumentRule) {
+ if (item.isDexEncodedMethod()) {
+ keepUnusedArguments.add(item.asDexEncodedMethod().method);
+ context.markAsUsed();
+ }
+ } else {
+ throw new Unreachable();
+ }
+ }
+
+ private void reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
+ DexEncodedMethod method, ProguardAssumeNoSideEffectRule context) {
+ assert method.getHolderType() == options.dexItemFactory().objectType;
+ OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition());
+ assumeNoSideEffectsWarnings
+ .computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::compareTo))
+ .add(method.getReference());
+ }
+
+ private boolean isWaitOrNotifyMethod(DexMethod method) {
+ return method.name == options.itemFactory.waitMethodName
+ || method.name == options.itemFactory.notifyMethodName
+ || method.name == options.itemFactory.notifyAllMethodName;
+ }
+
+ private void generateAssumeNoSideEffectsWarnings() {
+ if (appView.getDontWarnConfiguration().matches(options.itemFactory.objectType)) {
+ // Don't report any warnings since we don't apply -assumenosideeffects rules to notify() or
+ // wait() anyway.
+ return;
+ }
+
+ assumeNoSideEffectsWarnings.forEach(
+ (originWithPosition, methods) -> {
+ boolean matchesWaitOrNotifyMethods =
+ methods.stream().anyMatch(this::isWaitOrNotifyMethod);
+ if (!matchesWaitOrNotifyMethods) {
+ // We model the remaining methods on java.lang.Object, and thus there should be no
+ // need
+ // to warn in this case.
+ return;
+ }
+ options.reporter.warning(
+ new AssumeNoSideEffectsRuleForObjectMembersDiagnostic.Builder()
+ .addMatchedMethods(methods)
+ .setOrigin(originWithPosition.getOrigin())
+ .setPosition(originWithPosition.getPosition())
+ .build());
+ });
+ }
+ }
+
+ abstract static class RootSetBase {
+
+ final Set<DexMethod> neverInline;
+ final Set<DexMethod> neverInlineDueToSingleCaller;
+ final Set<DexType> neverClassInline;
+ final MutableItemsWithRules noShrinking;
+ final MutableItemsWithRules softPinned;
+ final Set<DexReference> noObfuscation;
+ final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
+ final Map<DexReference, MutableItemsWithRules> dependentSoftPinned;
+ final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
+ final List<DelayedRootSetActionItem> delayedRootSetActionItems;
+
+ RootSetBase(
+ Set<DexMethod> neverInline,
+ Set<DexMethod> neverInlineDueToSingleCaller,
+ Set<DexType> neverClassInline,
+ MutableItemsWithRules noShrinking,
+ MutableItemsWithRules softPinned,
+ Set<DexReference> noObfuscation,
+ Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+ Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
+ Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+ List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+ this.neverInline = neverInline;
+ this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
+ this.neverClassInline = neverClassInline;
+ this.noShrinking = noShrinking;
+ this.softPinned = softPinned;
+ this.noObfuscation = noObfuscation;
+ this.dependentNoShrinking = dependentNoShrinking;
+ this.dependentSoftPinned = dependentSoftPinned;
+ this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
+ this.delayedRootSetActionItems = delayedRootSetActionItems;
+ }
+
+ public void forEachClassWithDependentItems(
+ DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
+ for (DexReference reference : dependentNoShrinking.keySet()) {
+ if (reference.isDexType()) {
+ DexType type = reference.asDexType();
+ DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
+ if (clazz != null) {
+ consumer.accept(clazz);
+ }
+ }
+ }
+ }
+
+ public void forEachMemberWithDependentItems(
+ DexDefinitionSupplier definitions,
+ BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) {
+ dependentNoShrinking.forEach(
+ (reference, dependentItems) -> {
+ if (reference.isDexMember()) {
+ DexMember<?, ?> member = reference.asDexMember();
+ DexProgramClass holder =
+ asProgramClassOrNull(definitions.definitionForHolder(member));
+ if (holder != null) {
+ DexEncodedMember<?, ?> definition = holder.lookupMember(member);
+ if (definition != null) {
+ consumer.accept(definition, dependentItems);
+ }
+ }
+ }
+ });
+ }
+
+ public void forEachDependentInstanceConstructor(
+ DexProgramClass clazz,
+ AppView<?> appView,
+ BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
+ getDependentItems(clazz)
+ .forEachMethod(
+ (reference, reasons) -> {
+ DexProgramClass holder =
+ asProgramClassOrNull(appView.definitionForHolder(reference));
+ if (holder != null) {
+ ProgramMethod method = holder.lookupProgramMethod(reference);
+ if (method != null && method.getDefinition().isInstanceInitializer()) {
+ fn.accept(method, reasons);
+ }
+ }
+ });
+ }
+
+ public void forEachDependentMember(
+ DexDefinition item,
+ AppView<?> appView,
+ Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+ getDependentItems(item)
+ .forEachMember(
+ (reference, reasons) -> {
+ DexProgramClass holder =
+ asProgramClassOrNull(appView.definitionForHolder(reference));
+ if (holder != null) {
+ ProgramMember<?, ?> member = holder.lookupProgramMember(reference);
+ if (member != null) {
+ fn.accept(item, member, reasons);
+ }
+ }
+ });
+ }
+
+ public void forEachDependentNonStaticMember(
+ DexDefinition item,
+ AppView<?> appView,
+ Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+ forEachDependentMember(
+ item,
+ appView,
+ (precondition, member, reasons) -> {
+ if (!member.getDefinition().isStatic()) {
+ fn.accept(precondition, member, reasons);
+ }
+ });
+ }
+
+ public void forEachDependentStaticMember(
+ DexDefinition item,
+ AppView<?> appView,
+ Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+ forEachDependentMember(
+ item,
+ appView,
+ (precondition, member, reasons) -> {
+ if (member.getDefinition().isStatic()) {
+ fn.accept(precondition, member, reasons);
+ }
+ });
+ }
+
+ ItemsWithRules getDependentItems(DexDefinition item) {
+ ItemsWithRules found = dependentNoShrinking.get(item.getReference());
+ return found != null ? found : ItemsWithRules.empty();
+ }
+
+ Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
+ return dependentKeepClassCompatRule.get(type);
+ }
+ }
+
+ abstract static class ItemsWithRules {
+
+ public static ItemsWithRules empty() {
+ return MutableItemsWithRules.EMPTY;
+ }
+
+ public abstract boolean containsClass(DexType type);
+
+ public abstract boolean containsField(DexField field);
+
+ public abstract boolean containsMethod(DexMethod method);
+
+ public final boolean containsReference(DexReference reference) {
+ return reference.apply(this::containsClass, this::containsField, this::containsMethod);
+ }
+
+ public abstract void forEachClass(Consumer<? super DexType> consumer);
+
+ public abstract void forEachClass(
+ BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer);
+
+ public abstract void forEachField(Consumer<? super DexField> consumer);
+
+ public abstract void forEachField(
+ BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer);
+
+ public abstract void forEachMember(Consumer<? super DexMember<?, ?>> consumer);
+
+ public abstract void forEachMember(
+ BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer);
+
+ public abstract void forEachMethod(Consumer<? super DexMethod> consumer);
+
+ public abstract void forEachMethod(
+ BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer);
+
+ public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type);
+
+ public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field);
+
+ public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method);
+
+ public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) {
+ return reference.apply(
+ this::getRulesForClass, this::getRulesForField, this::getRulesForMethod);
+ }
+ }
+
+ static class MutableItemsWithRules extends ItemsWithRules {
+
+ private static final ItemsWithRules EMPTY =
+ new MutableItemsWithRules(
+ Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
+
+ final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
+ final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
+ final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules;
+
+ MutableItemsWithRules() {
+ this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+ }
+
+ private MutableItemsWithRules(
+ Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules,
+ Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules,
+ Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) {
+ this.classesWithRules = classesWithRules;
+ this.fieldsWithRules = fieldsWithRules;
+ this.methodsWithRules = methodsWithRules;
+ }
+
+ public void addAll(ItemsWithRules items) {
+ items.forEachClass(this::addClassWithRules);
+ items.forEachField(this::addFieldWithRules);
+ items.forEachMethod(this::addMethodWithRules);
+ }
+
+ public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) {
+ classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule);
+ }
+
+ public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
+ classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules);
+ }
+
+ public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) {
+ fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule);
+ }
+
+ public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
+ fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules);
+ }
+
+ public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) {
+ methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule);
+ }
+
+ public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
+ methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules);
+ }
+
+ public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) {
+ reference.accept(
+ this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule);
+ }
+
+ public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+ reference.accept(
+ this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules);
+ }
+
+ @Override
+ public boolean containsClass(DexType type) {
+ return classesWithRules.containsKey(type);
+ }
+
+ @Override
+ public boolean containsField(DexField field) {
+ return fieldsWithRules.containsKey(field);
+ }
+
+ @Override
+ public boolean containsMethod(DexMethod method) {
+ return methodsWithRules.containsKey(method);
+ }
+
+ public void forEachReference(Consumer<DexReference> consumer) {
+ forEachClass(consumer);
+ forEachMember(consumer);
+ }
+
+ @Override
+ public void forEachClass(Consumer<? super DexType> consumer) {
+ classesWithRules.keySet().forEach(consumer);
+ }
+
+ @Override
+ public void forEachClass(BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer) {
+ classesWithRules.forEach(consumer);
+ }
+
+ @Override
+ public void forEachField(Consumer<? super DexField> consumer) {
+ fieldsWithRules.keySet().forEach(consumer);
+ }
+
+ @Override
+ public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) {
+ fieldsWithRules.forEach(consumer);
+ }
+
+ @Override
+ public void forEachMember(Consumer<? super DexMember<?, ?>> consumer) {
+ forEachField(consumer);
+ forEachMethod(consumer);
+ }
+
+ @Override
+ public void forEachMember(
+ BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) {
+ forEachField(consumer);
+ forEachMethod(consumer);
+ }
+
+ @Override
+ public void forEachMethod(Consumer<? super DexMethod> consumer) {
+ methodsWithRules.keySet().forEach(consumer);
+ }
+
+ @Override
+ public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) {
+ methodsWithRules.forEach(consumer);
+ }
+
+ @Override
+ public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) {
+ return classesWithRules.get(type);
+ }
+
+ @Override
+ public Set<ProguardKeepRuleBase> getRulesForField(DexField field) {
+ return fieldsWithRules.get(field);
+ }
+
+ @Override
+ public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) {
+ return methodsWithRules.get(method);
+ }
+
+ public void removeClass(DexType type) {
+ classesWithRules.remove(type);
+ }
+
+ public void removeField(DexField field) {
+ fieldsWithRules.remove(field);
+ }
+
+ public void removeMethod(DexMethod method) {
+ methodsWithRules.remove(method);
+ }
+
+ public void removeReference(DexReference reference) {
+ reference.accept(this::removeClass, this::removeField, this::removeMethod);
+ }
+
+ public void putAll(ItemsWithRules items) {
+ items.forEachClass(this::putClassWithRules);
+ items.forEachField(this::putFieldWithRules);
+ items.forEachMethod(this::putMethodWithRules);
+ }
+
+ public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
+ classesWithRules.put(type, rules);
+ }
+
+ public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
+ fieldsWithRules.put(field, rules);
+ }
+
+ public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
+ methodsWithRules.put(method, rules);
+ }
+
+ public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+ reference.accept(
+ this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules);
+ }
+
+ public int size() {
+ return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
+ }
+ }
+
+ public static class RootSet extends RootSetBase {
+
+ public final ImmutableList<DexReference> reasonAsked;
+ public final ImmutableList<DexReference> checkDiscarded;
+ public final Set<DexMethod> alwaysInline;
+ public final Set<DexMethod> forceInline;
+ public final Set<DexMethod> bypassClinitForInlining;
+ public final Set<DexMethod> whyAreYouNotInlining;
+ public final Set<DexMethod> keepConstantArguments;
+ public final Set<DexMethod> keepUnusedArguments;
+ public final Set<DexMethod> reprocess;
+ public final Set<DexMethod> neverReprocess;
+ public final PredicateSet<DexType> alwaysClassInline;
+ public final Set<DexType> noUnusedInterfaceRemoval;
+ public final Set<DexType> noVerticalClassMerging;
+ public final Set<DexType> noHorizontalClassMerging;
+ public final Set<DexReference> neverPropagateValue;
+ public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
+ public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
+ public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
+ public final Set<DexReference> identifierNameStrings;
+ public final Set<ProguardIfRule> ifRules;
+
+ private RootSet(
+ MutableItemsWithRules noShrinking,
+ MutableItemsWithRules softPinned,
+ Set<DexReference> noObfuscation,
+ ImmutableList<DexReference> reasonAsked,
+ ImmutableList<DexReference> checkDiscarded,
+ Set<DexMethod> alwaysInline,
+ Set<DexMethod> forceInline,
+ Set<DexMethod> neverInline,
+ Set<DexMethod> neverInlineDueToSingleCaller,
+ Set<DexMethod> bypassClinitForInlining,
+ Set<DexMethod> whyAreYouNotInlining,
+ Set<DexMethod> keepConstantArguments,
+ Set<DexMethod> keepUnusedArguments,
+ Set<DexMethod> reprocess,
+ Set<DexMethod> neverReprocess,
+ PredicateSet<DexType> alwaysClassInline,
+ Set<DexType> neverClassInline,
+ Set<DexType> noUnusedInterfaceRemoval,
+ Set<DexType> noVerticalClassMerging,
+ Set<DexType> noHorizontalClassMerging,
+ Set<DexReference> neverPropagateValue,
+ Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
+ Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
+ Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
+ Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+ Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
+ Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+ Set<DexReference> identifierNameStrings,
+ Set<ProguardIfRule> ifRules,
+ List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+ super(
+ neverInline,
+ neverInlineDueToSingleCaller,
+ neverClassInline,
+ noShrinking,
+ softPinned,
+ noObfuscation,
+ dependentNoShrinking,
+ dependentSoftPinned,
+ dependentKeepClassCompatRule,
+ delayedRootSetActionItems);
+ this.reasonAsked = reasonAsked;
+ this.checkDiscarded = checkDiscarded;
+ this.alwaysInline = alwaysInline;
+ this.forceInline = forceInline;
+ this.bypassClinitForInlining = bypassClinitForInlining;
+ this.whyAreYouNotInlining = whyAreYouNotInlining;
+ this.keepConstantArguments = keepConstantArguments;
+ this.keepUnusedArguments = keepUnusedArguments;
+ this.reprocess = reprocess;
+ this.neverReprocess = neverReprocess;
+ this.alwaysClassInline = alwaysClassInline;
+ this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
+ this.noVerticalClassMerging = noVerticalClassMerging;
+ this.noHorizontalClassMerging = noHorizontalClassMerging;
+ this.neverPropagateValue = neverPropagateValue;
+ this.mayHaveSideEffects = mayHaveSideEffects;
+ this.noSideEffects = noSideEffects;
+ this.assumedValues = assumedValues;
+ this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
+ this.ifRules = Collections.unmodifiableSet(ifRules);
+ }
+
+ public void checkAllRulesAreUsed(InternalOptions options) {
+ List<ProguardConfigurationRule> rules = options.getProguardConfiguration().getRules();
+ if (rules != null) {
+ for (ProguardConfigurationRule rule : rules) {
+ if (!rule.isUsed()) {
+ String message =
+ "Proguard configuration rule does not match anything: `" + rule.toString() + "`";
+ StringDiagnostic diagnostic = new StringDiagnostic(message, rule.getOrigin());
+ if (options.testing.reportUnusedProguardConfigurationRules) {
+ options.reporter.info(diagnostic);
+ }
+ }
+ }
+ }
+ }
+
+ void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
+ neverInline.addAll(consequentRootSet.neverInline);
+ neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
+ neverClassInline.addAll(consequentRootSet.neverClassInline);
+ noObfuscation.addAll(consequentRootSet.noObfuscation);
+ if (addNoShrinking) {
+ noShrinking.addAll(consequentRootSet.noShrinking);
+ }
+ addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
+ addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned);
+ consequentRootSet.dependentKeepClassCompatRule.forEach(
+ (type, rules) ->
+ dependentKeepClassCompatRule
+ .computeIfAbsent(type, k -> new HashSet<>())
+ .addAll(rules));
+ delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems);
+ }
+
+ // Add dependent items that depend on -if rules.
+ private static void addDependentItems(
+ Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd,
+ Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) {
+ dependentItemsToAdd.forEach(
+ (reference, dependence) ->
+ dependentItemsToAddTo
+ .computeIfAbsent(reference, x -> new MutableItemsWithRules())
+ .putAll(dependence));
+ }
+
+ public void copy(DexReference original, DexReference rewritten) {
+ if (noShrinking.containsReference(original)) {
+ noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
+ }
+ if (noObfuscation.contains(original)) {
+ noObfuscation.add(rewritten);
+ }
+ if (original.isDexMember()) {
+ assert rewritten.isDexMember();
+ DexMember<?, ?> originalMember = original.asDexMember();
+ if (noSideEffects.containsKey(originalMember)) {
+ noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember));
+ }
+ if (assumedValues.containsKey(originalMember)) {
+ assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember));
+ }
+ }
+ }
+
+ public void prune(DexReference reference) {
+ noShrinking.removeReference(reference);
+ noObfuscation.remove(reference);
+ noSideEffects.remove(reference);
+ assumedValues.remove(reference);
+ }
+
+ public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
+ pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
+ pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
+ pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
+ pruneDeadReferences(alwaysInline, definitions, enqueuer);
+ pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
+ }
+
+ private static void pruneDeadReferences(
+ Set<? extends DexReference> references,
+ DexDefinitionSupplier definitions,
+ Enqueuer enqueuer) {
+ references.removeIf(
+ reference -> {
+ if (reference.isDexType()) {
+ DexClass definition = definitions.definitionFor(reference.asDexType());
+ return definition == null || !enqueuer.isTypeLive(definition);
+ }
+
+ assert reference.isDexMember();
+
+ DexMember<?, ?> member = reference.asDexMember();
+ DexClass holder = definitions.definitionForHolder(member);
+ DexEncodedMember<?, ?> definition = member.lookupOnClass(holder);
+ if (definition == null) {
+ return true;
+ }
+ if (holder.isProgramClass()) {
+ if (definition.isDexEncodedField()) {
+ DexEncodedField field = definition.asDexEncodedField();
+ return !enqueuer.isFieldReferenced(field);
+ }
+ assert definition.isDexEncodedMethod();
+ DexEncodedMethod method = definition.asDexEncodedMethod();
+ return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method);
+ }
+ return !enqueuer.isNonProgramTypeLive(holder);
+ });
+ }
+
+ public void move(DexReference original, DexReference rewritten) {
+ copy(original, rewritten);
+ prune(original);
+ }
+
+ void shouldNotBeMinified(DexReference reference) {
+ noObfuscation.add(reference);
+ }
+
+ public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
+ noShrinking.forEachField(
+ reference -> {
+ DexClass holder = appInfo.definitionForHolder(reference);
+ DexEncodedField field = reference.lookupOnClass(holder);
+ if (field != null
+ && (field.isStatic()
+ || isKeptDirectlyOrIndirectly(field.getHolderType(), appInfo))) {
+ assert appInfo.isFieldRead(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be read";
+ assert appInfo.isFieldWritten(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be written";
+ }
+ });
+ return true;
+ }
+
+ public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
+ noShrinking.forEachMethod(
+ reference -> {
+ assert appInfo.isTargetedMethod(reference)
+ : "Expected kept method `" + reference.toSourceString() + "` to be targeted";
+ DexEncodedMethod method =
+ appInfo.definitionForHolder(reference).lookupMethod(reference);
+ if (!method.isAbstract()
+ && isKeptDirectlyOrIndirectly(method.getHolderType(), appInfo)) {
+ assert appInfo.isLiveMethod(reference)
+ : "Expected non-abstract kept method `"
+ + reference.toSourceString()
+ + "` to be live";
+ }
+ });
+ return true;
+ }
+
+ public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
+ noShrinking.forEachClass(
+ type -> {
+ assert appInfo.isLiveProgramType(type)
+ : "Expected kept type `" + type.toSourceString() + "` to be live";
+ });
+ return true;
+ }
+
+ private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
+ if (noShrinking.containsClass(type)) {
+ return true;
+ }
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz == null) {
+ return false;
+ }
+ if (clazz.superType != null) {
+ return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
+ }
+ return false;
+ }
+
+ public boolean verifyKeptItemsAreKept(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ AppInfoWithClassHierarchy appInfo = appView.appInfo();
+ GraphLens lens = appView.graphLens();
+ // Create a mapping from each required type to the set of required members on that type.
+ Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>();
+ noShrinking.forEachClass(
+ type -> {
+ DexType rewrittenType = lens.lookupType(type);
+ assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenType)
+ : "Expected reference `" + rewrittenType.toSourceString() + "` to be pinned";
+ requiredMembersPerType.computeIfAbsent(rewrittenType, key -> Sets.newIdentityHashSet());
+ });
+ noShrinking.forEachMember(
+ member -> {
+ DexMember<?, ?> rewrittenMember = lens.getRenamedMemberSignature(member);
+ assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenMember)
+ : "Expected reference `" + rewrittenMember.toSourceString() + "` to be pinned";
+ requiredMembersPerType
+ .computeIfAbsent(rewrittenMember.holder, key -> Sets.newIdentityHashSet())
+ .add(rewrittenMember);
+ });
+
+ // Run through each class in the program and check that it has members it must have.
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ Set<DexMember<?, ?>> requiredMembers =
+ requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of());
+
+ Set<DexField> fields = null;
+ Set<DexMethod> methods = null;
+
+ for (DexMember<?, ?> requiredMember : requiredMembers) {
+ if (requiredMember.isDexField()) {
+ DexField requiredField = requiredMember.asDexField();
+ if (fields == null) {
+ // Create a Set of the fields to avoid quadratic behavior.
+ fields =
+ Streams.stream(clazz.fields())
+ .map(DexEncodedField::getReference)
+ .collect(Collectors.toSet());
+ }
+ assert fields.contains(requiredField)
+ : "Expected field `"
+ + requiredField.toSourceString()
+ + "` from the root set to be present";
+ } else {
+ DexMethod requiredMethod = requiredMember.asDexMethod();
+ if (methods == null) {
+ // Create a Set of the methods to avoid quadratic behavior.
+ methods =
+ Streams.stream(clazz.methods())
+ .map(DexEncodedMethod::getReference)
+ .collect(Collectors.toSet());
+ }
+ assert methods.contains(requiredMethod)
+ : "Expected method `"
+ + requiredMethod.toSourceString()
+ + "` from the root set to be present";
+ }
+ }
+ requiredMembersPerType.remove(clazz.type);
+ }
+
+ // If the map is non-empty, then a type in the root set was not in the application.
+ if (!requiredMembersPerType.isEmpty()) {
+ DexType type = requiredMembersPerType.keySet().iterator().next();
+ DexClass clazz = appView.definitionFor(type);
+ assert clazz == null || clazz.isProgramClass()
+ : "Unexpected library type in root set: `" + type + "`";
+ assert requiredMembersPerType.isEmpty()
+ : "Expected type `" + type.toSourceString() + "` to be present";
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("RootSet");
+ builder.append("\nnoShrinking: " + noShrinking.size());
+ builder.append("\nnoObfuscation: " + noObfuscation.size());
+ builder.append("\nreasonAsked: " + reasonAsked.size());
+ builder.append("\ncheckDiscarded: " + checkDiscarded.size());
+ builder.append("\nnoSideEffects: " + noSideEffects.size());
+ builder.append("\nassumedValues: " + assumedValues.size());
+ builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
+ builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
+ builder.append("\nifRules: " + ifRules.size());
+ return builder.toString();
+ }
+
+ public static RootSetBuilder builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+ return new RootSetBuilder(appView, subtypingInfo);
+ }
+
+ public static RootSetBuilder builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ SubtypingInfo subtypingInfo,
+ Iterable<? extends ProguardConfigurationRule> rules) {
+ return new RootSetBuilder(appView, subtypingInfo, rules);
+ }
+ }
+
+ static class ConsequentRootSetBuilder extends RootSetBuilder {
+
+ private final Enqueuer enqueuer;
+
+ private ConsequentRootSetBuilder(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ SubtypingInfo subtypingInfo,
+ Enqueuer enqueuer) {
+ super(appView, subtypingInfo, null);
+ this.enqueuer = enqueuer;
+ }
+
+ @Override
+ void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
+ if (enqueuer.getMode().isInitialTreeShaking()
+ && annotationMatchResult.isConcreteAnnotationMatchResult()) {
+ enqueuer.retainAnnotationForFinalTreeShaking(
+ annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotations());
+ }
+ }
+ }
+
+ // A partial RootSet that becomes live due to the enabled -if rule or the addition of interface
+ // keep rules.
+ public static class ConsequentRootSet extends RootSetBase {
+
+ ConsequentRootSet(
+ Set<DexMethod> neverInline,
+ Set<DexMethod> neverInlineDueToSingleCaller,
+ Set<DexType> neverClassInline,
+ MutableItemsWithRules noShrinking,
+ MutableItemsWithRules softPinned,
+ Set<DexReference> noObfuscation,
+ Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+ Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
+ Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+ List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+ super(
+ neverInline,
+ neverInlineDueToSingleCaller,
+ neverClassInline,
+ noShrinking,
+ softPinned,
+ noObfuscation,
+ dependentNoShrinking,
+ dependentSoftPinned,
+ dependentKeepClassCompatRule,
+ delayedRootSetActionItems);
+ }
+
+ static ConsequentRootSetBuilder builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ SubtypingInfo subtypingInfo,
+ Enqueuer enqueuer) {
+ return new ConsequentRootSetBuilder(appView, subtypingInfo, enqueuer);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
deleted file mode 100644
index 33e64f0..0000000
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ /dev/null
@@ -1,579 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.shaking;
-
-import com.android.tools.r8.FeatureSplit;
-import com.android.tools.r8.features.ClassToFeatureSplitMap;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-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.DexString;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
-import com.android.tools.r8.utils.FieldSignatureEquivalence;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.SingletonEquivalence;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Multiset.Entry;
-import com.google.common.collect.Streams;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * This optimization merges all classes that only have static members and private virtual methods.
- *
- * <p>If a merge candidate does not access any package-private or protected members, then it is
- * merged into a global representative. Otherwise it is merged into a representative for its
- * package. If no such representatives exist, then the merge candidate is promoted to be the
- * representative.
- *
- * <p>Note that, when merging a merge candidate X into Y, this optimization merely moves the members
- * of X into Y -- it does not change all occurrences of X in the program into Y. This makes the
- * optimization more applicable, because it would otherwise not be possible to merge two classes if
- * they inherited from, say, X' and Y' (since multiple inheritance is not allowed).
- *
- * <p>When there is a main dex specification, merging will only happen within the three groups of
- * classes found from main dex tracing ("main dex roots", "main dex dependencies" and
- * "non main dex classes"), and not between them. This ensures that the size of the main dex
- * will not grow out of proportions due to non main dex classes getting merged into main dex
- * classes.
- */
-public class StaticClassMerger {
-
- private enum MergeGroup {
- MAIN_DEX_ROOTS,
- MAIN_DEX_DEPENDENCIES,
- NOT_MAIN_DEX,
- DONT_MERGE;
-
- @Override
- public String toString() {
- switch (this) {
- case NOT_MAIN_DEX:
- return "outside main dex";
- case MAIN_DEX_ROOTS:
- return "main dex roots";
- case MAIN_DEX_DEPENDENCIES:
- return "main dex dependencies";
- default:
- assert this == DONT_MERGE;
- return "don't merge";
- }
- }
- }
-
- private static class MergeKey {
-
- private static final String GLOBAL = "<global>";
-
- private final FeatureSplit featureSplit;
- private final MergeGroup mergeGroup;
- private final String packageOrGlobal;
-
- public MergeKey(FeatureSplit featureSplit, MergeGroup mergeGroup, String packageOrGlobal) {
- this.featureSplit = featureSplit;
- this.mergeGroup = mergeGroup;
- this.packageOrGlobal = packageOrGlobal;
- }
-
- public MergeGroup getMergeGroup() {
- return mergeGroup;
- }
-
- public MergeKey toGlobal() {
- return new MergeKey(featureSplit, mergeGroup, GLOBAL);
- }
-
- public String getPackageOrGlobal() {
- return packageOrGlobal;
- }
-
- public boolean isGlobal() {
- return packageOrGlobal.equals(GLOBAL);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(featureSplit, mergeGroup, packageOrGlobal);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
- if (other == null || this.getClass() != other.getClass()) {
- return false;
- }
- MergeKey o = (MergeKey) other;
- return o.featureSplit == featureSplit
- && o.mergeGroup == mergeGroup
- && o.packageOrGlobal.equals(packageOrGlobal);
- }
- }
-
- // There are 52 characters in [a-zA-Z], so with a capacity just below 52 the minifier should be
- // able to find single-character names for all members, but around 30 appears to work better in
- // practice.
- private static final int HEURISTIC_FOR_CAPACITY_OF_REPRESENTATIVES = 30;
-
- private class Representative {
-
- private final DexProgramClass clazz;
-
- // Put all members of this class into buckets, where each bucket represent members that have a
- // conflicting signature, and therefore must be given distinct names.
- private final HashMultiset<Wrapper<DexField>> fieldBuckets = HashMultiset.create();
- private final HashMultiset<Wrapper<DexMethod>> methodBuckets = HashMultiset.create();
-
- private boolean hasSynchronizedMethods = false;
-
- public Representative(DexProgramClass clazz) {
- this.clazz = clazz;
- include(clazz);
- }
-
- // Includes the members of `clazz` in `fieldBuckets` and `methodBuckets`.
- public void include(DexProgramClass clazz) {
- for (DexEncodedField field : clazz.fields()) {
- Wrapper<DexField> wrapper = fieldEquivalence.wrap(field.field);
- fieldBuckets.add(wrapper);
- }
- boolean classHasSynchronizedMethods = false;
- for (DexEncodedMethod method : clazz.methods()) {
- assert !hasSynchronizedMethods || !method.isSynchronized();
- classHasSynchronizedMethods |= method.isSynchronized();
- Wrapper<DexMethod> wrapper = methodEquivalence.wrap(method.method);
- methodBuckets.add(wrapper);
- }
- hasSynchronizedMethods |= classHasSynchronizedMethods;
- }
-
- // Returns true if this representative should no longer be used. The current heuristic is to
- // stop using a representative when the number of members with the same signature (ignoring the
- // name) exceeds a given threshold. This way it is unlikely that we will not be able to find a
- // single-character name for all members.
- public boolean isFull() {
- int numberOfNamesNeeded = 1;
- for (Entry<Wrapper<DexField>> entry : fieldBuckets.entrySet()) {
- numberOfNamesNeeded = Math.max(entry.getCount(), numberOfNamesNeeded);
- }
- for (Entry<Wrapper<DexMethod>> entry : methodBuckets.entrySet()) {
- numberOfNamesNeeded = Math.max(entry.getCount(), numberOfNamesNeeded);
- }
- return numberOfNamesNeeded > HEURISTIC_FOR_CAPACITY_OF_REPRESENTATIVES;
- }
- }
-
- private final AppView<AppInfoWithLiveness> appView;
- private final MainDexTracingResult mainDexClasses;
- private final StaticallyMergedClasses.Builder mergedClassesBuilder =
- StaticallyMergedClasses.builder();
-
- /** The equivalence that should be used for the member buckets in {@link Representative}. */
- private final Equivalence<DexField> fieldEquivalence;
- private final Equivalence<DexMethod> methodEquivalence;
-
- private final Map<MergeKey, Representative> representatives = new HashMap<>();
-
- private final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
- new BidirectionalOneToOneHashMap<>();
- private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> methodMapping =
- new BidirectionalOneToOneHashMap<>();
-
- private int numberOfMergedClasses = 0;
-
- public StaticClassMerger(
- AppView<AppInfoWithLiveness> appView,
- InternalOptions options,
- MainDexTracingResult mainDexClasses) {
- this.appView = appView;
- if (options.getProguardConfiguration().isOverloadAggressively()) {
- fieldEquivalence = FieldSignatureEquivalence.getEquivalenceIgnoreName();
- methodEquivalence = MethodSignatureEquivalence.getEquivalenceIgnoreName();
- } else {
- fieldEquivalence = new SingletonEquivalence<>();
- methodEquivalence = MethodJavaSignatureEquivalence.getEquivalenceIgnoreName();
- }
- this.mainDexClasses = mainDexClasses;
- }
-
- public NestedGraphLens run() {
- appView.appInfo().classesWithDeterministicOrder().forEach(this::merge);
- appView.setStaticallyMergedClasses(mergedClassesBuilder.build());
- return buildGraphLens();
- }
-
- private NestedGraphLens buildGraphLens() {
- if (!newFieldSignatures.isEmpty() || !methodMapping.isEmpty()) {
- BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures =
- methodMapping.getInverseOneToOneMap();
- return new NestedGraphLens(
- ImmutableMap.of(),
- methodMapping.getForwardMap(),
- newFieldSignatures,
- originalMethodSignatures,
- appView.graphLens(),
- appView.dexItemFactory());
- }
- return null;
- }
-
- private ClassToFeatureSplitMap getClassToFeatureSplitMap() {
- return appView.appInfo().getClassToFeatureSplitMap();
- }
-
- private MergeGroup getMergeGroup(DexProgramClass clazz) {
- if (appView.getSyntheticItems().isSyntheticClass(clazz)) {
- // In principle it would be valid to merge synthetic classes into program classes.
- // Doing so may however increase size as static methods will not be de-duplicated
- // at synthetic finalization.
- return MergeGroup.DONT_MERGE;
- }
- if (appView.appInfo().getNoStaticClassMergingSet().contains(clazz.type)) {
- return MergeGroup.DONT_MERGE;
- }
- if (clazz.staticFields().size() + clazz.getMethodCollection().size() == 0) {
- return MergeGroup.DONT_MERGE;
- }
- if (clazz.instanceFields().size() > 0) {
- return MergeGroup.DONT_MERGE;
- }
- if (clazz.staticFields().stream()
- .anyMatch(field -> appView.appInfo().isPinned(field.field))) {
- return MergeGroup.DONT_MERGE;
- }
- if (clazz.getMethodCollection().hasDirectMethods(DexEncodedMethod::isInitializer)) {
- return MergeGroup.DONT_MERGE;
- }
- if (clazz.getMethodCollection().hasVirtualMethods(method -> !method.isPrivateMethod())) {
- return MergeGroup.DONT_MERGE;
- }
- if (clazz.isInANest()) {
- // Breaks nest access control, abort merging.
- return MergeGroup.DONT_MERGE;
- }
- if (Streams.stream(clazz.methods())
- .anyMatch(
- method ->
- method.isNative()
- || appView.appInfo().isPinned(method.method)
- // TODO(christofferqa): Remove the invariant that the graph lens should not
- // modify any methods from the sets alwaysInline and noSideEffects.
- || appView.appInfo().isAlwaysInlineMethod(method.method)
- || appView.appInfo().noSideEffects.keySet().contains(method.method))) {
- return MergeGroup.DONT_MERGE;
- }
- if (clazz.classInitializationMayHaveSideEffects(appView)) {
- // This could have a negative impact on inlining.
- //
- // See {@link com.android.tools.r8.ir.optimize.DefaultInliningOracle#canInlineStaticInvoke}
- // for further details.
- //
- // Note that this will be true for all classes that inherit from or implement a library class.
- return MergeGroup.DONT_MERGE;
- }
- if (!mainDexClasses.isEmpty()) {
- if (mainDexClasses.getRoots().contains(clazz.type)) {
- return MergeGroup.MAIN_DEX_ROOTS;
- }
- if (mainDexClasses.getDependencies().contains(clazz.type)) {
- return MergeGroup.MAIN_DEX_DEPENDENCIES;
- }
- }
- return MergeGroup.NOT_MAIN_DEX;
- }
-
- private MergeKey getMergeKey(DexProgramClass clazz, MergeGroup mergeGroup) {
- return new MergeKey(
- getClassToFeatureSplitMap().getFeatureSplit(clazz),
- mergeGroup,
- mayMergeAcrossPackageBoundaries(clazz)
- ? MergeKey.GLOBAL
- : clazz.type.getPackageDescriptor());
- }
-
- private boolean isValidRepresentative(DexProgramClass clazz) {
- // Disallow interfaces from being representatives, since interface methods require desugaring.
- return !clazz.isInterface();
- }
-
- private void merge(DexProgramClass clazz) {
- MergeGroup mergeGroup = getMergeGroup(clazz);
- if (mergeGroup != MergeGroup.DONT_MERGE) {
- merge(clazz, mergeGroup);
- }
- }
-
- private void merge(DexProgramClass clazz, MergeGroup mergeGroup) {
- MergeKey key = getMergeKey(clazz, mergeGroup);
- Representative representative = representatives.get(key);
- if (representative != null) {
- if (representative.hasSynchronizedMethods && clazz.hasStaticSynchronizedMethods()) {
- // We are not allowed to merge synchronized classes with synchronized methods.
- return;
- }
- if (appView.appInfo().isLockCandidate(clazz.type)) {
- // Since the type is const-class referenced (and the static merger does not create a lens
- // to map the merged type) the class will likely remain and there is no gain from merging.
- return;
- }
- // Check if current candidate is a better choice depending on visibility. For package private
- // or protected, the key is parameterized by the package name already, so we just have to
- // check accessibility-flags. For global this is no-op.
- if (isValidRepresentative(clazz)
- && !representative.clazz.accessFlags.isAtLeastAsVisibleAs(clazz.accessFlags)) {
- assert clazz.type.getPackageDescriptor().equals(key.packageOrGlobal);
- assert representative.clazz.type.getPackageDescriptor().equals(key.packageOrGlobal);
- Representative newRepresentative = getOrCreateRepresentative(key, clazz);
- newRepresentative.include(representative.clazz);
- if (!newRepresentative.isFull()) {
- setRepresentative(key, newRepresentative);
- moveMembersFromSourceToTarget(representative.clazz, clazz);
- return;
- }
- } else {
- representative.include(clazz);
- if (!representative.isFull()) {
- moveMembersFromSourceToTarget(clazz, representative.clazz);
- return;
- }
- }
- }
- if (isValidRepresentative(clazz)) {
- setRepresentative(key, getOrCreateRepresentative(key, clazz));
- }
- }
-
- private Representative getOrCreateRepresentative(MergeKey key, DexProgramClass clazz) {
- Representative globalRepresentative = representatives.get(key.toGlobal());
- if (globalRepresentative != null && globalRepresentative.clazz == clazz) {
- return globalRepresentative;
- }
- Representative packageRepresentative = representatives.get(key);
- if (packageRepresentative != null && packageRepresentative.clazz == clazz) {
- return packageRepresentative;
- }
- return new Representative(clazz);
- }
-
- private void setRepresentative(MergeKey key, Representative representative) {
- assert isValidRepresentative(representative.clazz);
- if (Log.ENABLED) {
- if (key.isGlobal()) {
- Log.info(
- getClass(),
- "Making %s the global representative in group %s",
- representative.clazz.type.toSourceString(),
- key.getMergeGroup().toString());
- } else {
- Log.info(
- getClass(),
- "Making %s the representative for package %s in group %s",
- representative.clazz.type.toSourceString(),
- key.getPackageOrGlobal(),
- key.getMergeGroup().toString());
- }
- }
- representatives.put(key, representative);
- }
-
- private boolean mayMergeAcrossPackageBoundaries(DexProgramClass clazz) {
- // Check that the class is public. Otherwise, accesses to `clazz` from within its current
- // package may become illegal.
- if (!clazz.isPublic()) {
- return false;
- }
- // Check that all of the members are private or public.
- if (clazz
- .getMethodCollection()
- .hasDirectMethods(method -> !method.isPrivate() && !method.isPublic())) {
- return false;
- }
- if (!clazz.staticFields().stream().allMatch(field -> field.isPrivate() || field.isPublic())) {
- return false;
- }
-
- // Note that a class is only considered a candidate if it has no instance fields and all of its
- // virtual methods are private. Therefore, we don't need to consider check if there are any
- // package-private or protected instance fields or virtual methods here.
- assert clazz.instanceFields().size() == 0;
- assert !clazz.getMethodCollection().hasVirtualMethods(method -> !method.isPrivate());
-
- // Check that no methods access package-private or protected members.
- IllegalAccessDetector registry = new IllegalAccessDetector(appView, clazz);
- TraversalContinuation result =
- clazz.traverseProgramMethods(
- method -> {
- registry.setContext(method);
- method.registerCodeReferences(registry);
- if (registry.foundIllegalAccess()) {
- return TraversalContinuation.BREAK;
- }
- return TraversalContinuation.CONTINUE;
- });
- return result.shouldContinue();
- }
-
- private void moveMembersFromSourceToTarget(
- DexProgramClass sourceClass, DexProgramClass targetClass) {
- if (Log.ENABLED) {
- Log.info(
- getClass(),
- "Merging %s into %s",
- sourceClass.type.toSourceString(),
- targetClass.type.toSourceString());
- }
-
- // TODO(b/136457753) This check is a bit weird for protected, since it is moving access.
- assert targetClass.accessFlags.isAtLeastAsVisibleAs(sourceClass.accessFlags);
- assert sourceClass.instanceFields().isEmpty();
- assert targetClass.instanceFields().isEmpty();
- assert getClassToFeatureSplitMap().isInSameFeatureOrBothInBase(sourceClass, targetClass);
-
- numberOfMergedClasses++;
- mergedClassesBuilder.recordMerge(sourceClass, targetClass);
-
- // Move members from source to target.
- targetClass.addDirectMethods(
- mergeMethods(sourceClass.directMethods(), targetClass.directMethods(), targetClass));
- targetClass.addVirtualMethods(
- mergeMethods(sourceClass.virtualMethods(), targetClass.virtualMethods(), targetClass));
- targetClass.setStaticFields(
- mergeFields(sourceClass.staticFields(), targetClass.staticFields(), targetClass));
-
- // Cleanup source.
- sourceClass.setDirectMethods(DexEncodedMethod.EMPTY_ARRAY);
- sourceClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
- sourceClass.setStaticFields(DexEncodedField.EMPTY_ARRAY);
- }
-
- private List<DexEncodedMethod> mergeMethods(
- Iterable<DexEncodedMethod> sourceMethods,
- Iterable<DexEncodedMethod> targetMethods,
- DexProgramClass targetClass) {
- // Move source methods to result one by one, renaming them if needed.
- MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
- Set<Wrapper<DexMethod>> existingMethods = new HashSet<>();
- for (DexEncodedMethod targetMethod : targetMethods) {
- existingMethods.add(equivalence.wrap(targetMethod.method));
- }
-
- Predicate<DexMethod> availableMethodSignatures =
- method -> !existingMethods.contains(equivalence.wrap(method));
-
- List<DexEncodedMethod> newMethods = new ArrayList<>();
- for (DexEncodedMethod sourceMethod : sourceMethods) {
- DexEncodedMethod sourceMethodAfterMove =
- renameMethodIfNeeded(sourceMethod, targetClass, availableMethodSignatures);
- newMethods.add(sourceMethodAfterMove);
-
- DexMethod originalMethod =
- methodMapping.getRepresentativeKeyOrDefault(sourceMethod.method, sourceMethod.method);
- methodMapping.put(originalMethod, sourceMethodAfterMove.method);
-
- existingMethods.add(equivalence.wrap(sourceMethodAfterMove.method));
- }
- return newMethods;
- }
-
- private DexEncodedField[] mergeFields(
- List<DexEncodedField> sourceFields,
- List<DexEncodedField> targetFields,
- DexProgramClass targetClass) {
- DexEncodedField[] result = new DexEncodedField[sourceFields.size() + targetFields.size()];
-
- // Move all target fields to result.
- int index = 0;
- for (DexEncodedField targetField : targetFields) {
- result[index++] = targetField;
- }
-
- // Move source fields to result one by one, renaming them if needed.
- FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get();
- Set<Wrapper<DexField>> existingFields =
- targetFields.stream()
- .map(targetField -> equivalence.wrap(targetField.field))
- .collect(Collectors.toSet());
-
- Predicate<DexField> availableFieldSignatures =
- field -> !existingFields.contains(equivalence.wrap(field));
-
- for (DexEncodedField sourceField : sourceFields) {
- DexEncodedField sourceFieldAfterMove =
- renameFieldIfNeeded(sourceField, targetClass, availableFieldSignatures);
- result[index++] = sourceFieldAfterMove;
-
- DexField originalField =
- newFieldSignatures.getRepresentativeKeyOrDefault(sourceField.field, sourceField.field);
- newFieldSignatures.put(originalField, sourceFieldAfterMove.field);
-
- existingFields.add(equivalence.wrap(sourceFieldAfterMove.field));
- }
-
- assert index == result.length;
- return result;
- }
-
- private DexEncodedMethod renameMethodIfNeeded(
- DexEncodedMethod method,
- DexProgramClass targetClass,
- Predicate<DexMethod> availableMethodSignatures) {
- assert !method.accessFlags.isConstructor();
- DexString oldName = method.method.name;
- DexMethod newSignature =
- appView.dexItemFactory().createMethod(targetClass.type, method.method.proto, oldName);
- if (!availableMethodSignatures.test(newSignature)) {
- int count = 1;
- do {
- DexString newName = appView.dexItemFactory().createString(oldName.toSourceString() + count);
- newSignature =
- appView.dexItemFactory().createMethod(targetClass.type, method.method.proto, newName);
- count++;
- } while (!availableMethodSignatures.test(newSignature));
- }
- return method.toTypeSubstitutedMethod(newSignature);
- }
-
- private DexEncodedField renameFieldIfNeeded(
- DexEncodedField field,
- DexProgramClass targetClass,
- Predicate<DexField> availableFieldSignatures) {
- DexString oldName = field.field.name;
- DexField newSignature =
- appView.dexItemFactory().createField(targetClass.type, field.field.type, oldName);
- if (!availableFieldSignatures.test(newSignature)) {
- int count = 1;
- do {
- DexString newName = appView.dexItemFactory().createString(oldName.toSourceString() + count);
- newSignature =
- appView.dexItemFactory().createField(targetClass.type, field.field.type, newName);
- count++;
- } while (!availableFieldSignatures.test(newSignature));
- }
- return field.toTypeSubstitutedField(newSignature);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 071e7b6..eefe1b9 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.synthesis;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
@@ -60,30 +61,58 @@
public abstract C getHolder();
- final HashCode computeHash(RepresentativeMap map, boolean intermediate) {
+ final HashCode computeHash(
+ RepresentativeMap map, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) {
Hasher hasher = Hashing.murmur3_128().newHasher();
if (intermediate) {
// If in intermediate mode, include the context type as sharing is restricted to within a
// single context.
getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
}
+ if (!classToFeatureSplitMap.isEmpty()) {
+ hasher.putInt(
+ classToFeatureSplitMap
+ .getFeatureSplit(getContext().getSynthesizingContextType())
+ .hashCode());
+ }
+
internalComputeHash(hasher, map);
return hasher.hash();
}
abstract void internalComputeHash(Hasher hasher, RepresentativeMap map);
- final boolean isEquivalentTo(D other, boolean includeContext, GraphLens graphLens) {
- return compareTo(other, includeContext, graphLens) == 0;
+ final boolean isEquivalentTo(
+ D other,
+ boolean includeContext,
+ GraphLens graphLens,
+ ClassToFeatureSplitMap classToFeatureSplitMap) {
+ return compareTo(other, includeContext, graphLens, classToFeatureSplitMap) == 0;
}
- int compareTo(D other, boolean includeContext, GraphLens graphLens) {
+ int compareTo(
+ D other,
+ boolean includeContext,
+ GraphLens graphLens,
+ ClassToFeatureSplitMap classToFeatureSplitMap) {
if (includeContext) {
int order = getContext().compareTo(other.getContext());
if (order != 0) {
return order;
}
}
+ if (!classToFeatureSplitMap.isEmpty()) {
+ DexType synthesizingContextType = this.getContext().getSynthesizingContextType();
+ DexType otherSynthesizingContextType = other.getContext().getSynthesizingContextType();
+ if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(
+ synthesizingContextType, otherSynthesizingContextType)) {
+ int order =
+ classToFeatureSplitMap.compareFeatureSplitsForDexTypes(
+ synthesizingContextType, otherSynthesizingContextType);
+ assert order != 0;
+ return order;
+ }
+ }
DexType thisType = getHolder().getType();
DexType otherType = other.getHolder().getType();
RepresentativeMap map = null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index bf9106a..e2e0b55 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.synthesis;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexApplication;
@@ -19,6 +22,7 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.TreeFixerBase;
import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.InternalOptions;
@@ -191,8 +195,12 @@
return members;
}
- public int compareToIncludingContext(EquivalenceGroup<T> other, GraphLens graphLens) {
- return getRepresentative().compareTo(other.getRepresentative(), true, graphLens);
+ public int compareToIncludingContext(
+ EquivalenceGroup<T> other,
+ GraphLens graphLens,
+ ClassToFeatureSplitMap classToFeatureSplitMap) {
+ return getRepresentative()
+ .compareTo(other.getRepresentative(), true, graphLens, classToFeatureSplitMap);
}
@Override
@@ -213,7 +221,35 @@
this.synthetics = synthetics;
}
- public Result computeFinalSynthetics(AppView<?> appView) {
+ public static void finalize(AppView<AppInfo> appView) {
+ assert !appView.appInfo().hasClassHierarchy();
+ assert !appView.appInfo().hasLiveness();
+ Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+ appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+ appView.pruneItems(result.prunedItems);
+ if (result.lens != null) {
+ appView.setGraphLens(result.lens);
+ }
+ }
+
+ public static void finalizeWithClassHierarchy(AppView<AppInfoWithClassHierarchy> appView) {
+ assert !appView.appInfo().hasLiveness();
+ Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+ appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
+ appView.pruneItems(result.prunedItems);
+ if (result.lens != null) {
+ appView.setGraphLens(result.lens);
+ }
+ }
+
+ public static void finalizeWithLiveness(AppView<AppInfoWithLiveness> appView) {
+ Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+ appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit));
+ appView.rewriteWithLens(result.lens);
+ appView.pruneItems(result.prunedItems);
+ }
+
+ Result computeFinalSynthetics(AppView<?> appView) {
assert verifyNoNestedSynthetics();
DexApplication application;
MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
@@ -284,10 +320,19 @@
Map<DexType, NumberGenerator> generators) {
boolean intermediate = appView.options().intermediate;
Map<DexType, D> definitions = lookupDefinitions(appView, references);
+ ClassToFeatureSplitMap classToFeatureSplitMap =
+ appView.appInfo().hasClassHierarchy()
+ ? appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap()
+ : ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap();
Collection<List<D>> potentialEquivalences =
computePotentialEquivalences(
- definitions, intermediate, appView.dexItemFactory(), appView.graphLens());
- return computeActualEquivalences(potentialEquivalences, generators, appView, intermediate);
+ definitions,
+ intermediate,
+ appView.dexItemFactory(),
+ appView.graphLens(),
+ classToFeatureSplitMap);
+ return computeActualEquivalences(
+ potentialEquivalences, generators, appView, intermediate, classToFeatureSplitMap);
}
private boolean isNotSyntheticType(DexType type) {
@@ -510,8 +555,13 @@
syntheticClassGroups.forEach(
(syntheticType, syntheticGroup) -> {
DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+ SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
addFinalSyntheticClass.accept(
- externalSyntheticClass, syntheticGroup.getRepresentative().toReference());
+ externalSyntheticClass,
+ new SyntheticProgramClassReference(
+ representative.getKind(),
+ representative.getContext(),
+ externalSyntheticClass.type));
for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
addMainDexAndSynthesizedFromForMember(
member,
@@ -524,8 +574,16 @@
syntheticMethodGroups.forEach(
(syntheticType, syntheticGroup) -> {
DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+ SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
addFinalSyntheticMethod.accept(
- externalSyntheticClass, syntheticGroup.getRepresentative().toReference());
+ externalSyntheticClass,
+ new SyntheticMethodReference(
+ representative.getKind(),
+ representative.getContext(),
+ representative
+ .getMethod()
+ .getReference()
+ .withHolder(externalSyntheticClass.type, factory)));
for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
addMainDexAndSynthesizedFromForMember(
member,
@@ -607,13 +665,16 @@
Collection<List<T>> potentialEquivalences,
Map<DexType, NumberGenerator> generators,
AppView<?> appView,
- boolean intermediate) {
+ boolean intermediate,
+ ClassToFeatureSplitMap classToFeatureSplitMap) {
Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
potentialEquivalences.forEach(
members -> {
- List<List<T>> groups = groupEquivalent(members, intermediate, appView.graphLens());
+ List<List<T>> groups =
+ groupEquivalent(members, intermediate, appView.graphLens(), classToFeatureSplitMap);
for (List<T> group : groups) {
- T representative = findDeterministicRepresentative(group, appView.graphLens());
+ T representative =
+ findDeterministicRepresentative(group, appView.graphLens(), classToFeatureSplitMap);
// The representative is required to be the first element of the group.
group.remove(representative);
group.add(0, representative);
@@ -630,13 +691,17 @@
(context, groups) -> {
// Sort the equivalence groups that go into 'context' including the context type of the
// representative which is equal to 'context' here (see assert below).
- groups.sort((a, b) -> a.compareToIncludingContext(b, appView.graphLens()));
+ groups.sort(
+ (a, b) ->
+ a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap));
for (int i = 0; i < groups.size(); i++) {
EquivalenceGroup<T> group = groups.get(i);
assert group.getRepresentative().getContext().getSynthesizingContextType() == context;
// Two equivalence groups in same context type must be distinct otherwise the assignment
// of the synthetic name will be non-deterministic between the two.
- assert i == 0 || checkGroupsAreDistinct(groups.get(i - 1), group, appView.graphLens());
+ assert i == 0
+ || checkGroupsAreDistinct(
+ groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
SyntheticKind kind = group.members.get(0).getKind();
DexType representativeType = createExternalType(kind, context, generators, appView);
equivalences.put(representativeType, group);
@@ -646,13 +711,17 @@
}
private static <T extends SyntheticDefinition<?, T, ?>> List<List<T>> groupEquivalent(
- List<T> potentialEquivalence, boolean intermediate, GraphLens graphLens) {
+ List<T> potentialEquivalence,
+ boolean intermediate,
+ GraphLens graphLens,
+ ClassToFeatureSplitMap classToFeatureSplitMap) {
List<List<T>> groups = new ArrayList<>();
// Each other member is in a shared group if it is actually equivalent to the first member.
for (T synthetic : potentialEquivalence) {
boolean requireNewGroup = true;
for (List<T> group : groups) {
- if (synthetic.isEquivalentTo(group.get(0), intermediate, graphLens)) {
+ if (synthetic.isEquivalentTo(
+ group.get(0), intermediate, graphLens, classToFeatureSplitMap)) {
requireNewGroup = false;
group.add(synthetic);
break;
@@ -668,20 +737,23 @@
}
private static <T extends SyntheticDefinition<?, T, ?>> boolean checkGroupsAreDistinct(
- EquivalenceGroup<T> g1, EquivalenceGroup<T> g2, GraphLens graphLens) {
- int order = g1.compareToIncludingContext(g2, graphLens);
+ EquivalenceGroup<T> g1,
+ EquivalenceGroup<T> g2,
+ GraphLens graphLens,
+ ClassToFeatureSplitMap classToFeatureSplitMap) {
+ int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap);
assert order != 0;
- assert order != g2.compareToIncludingContext(g1, graphLens);
+ assert order != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap);
return true;
}
private static <T extends SyntheticDefinition<?, T, ?>> T findDeterministicRepresentative(
- List<T> members, GraphLens graphLens) {
+ List<T> members, GraphLens graphLens, ClassToFeatureSplitMap classToFeatureSplitMap) {
// Pick a deterministic member as representative.
T smallest = members.get(0);
for (int i = 1; i < members.size(); i++) {
T next = members.get(i);
- if (next.compareTo(smallest, true, graphLens) < 0) {
+ if (next.compareTo(smallest, true, graphLens, classToFeatureSplitMap) < 0) {
smallest = next;
}
}
@@ -718,7 +790,8 @@
Map<DexType, T> definitions,
boolean intermediate,
DexItemFactory factory,
- GraphLens graphLens) {
+ GraphLens graphLens,
+ ClassToFeatureSplitMap classToFeatureSplitMap) {
if (definitions.isEmpty()) {
return Collections.emptyList();
}
@@ -741,7 +814,7 @@
RepresentativeMap map = t -> syntheticTypes.contains(t) ? factory.voidType : t;
Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
for (T definition : definitions.values()) {
- HashCode hash = definition.computeHash(map, intermediate);
+ HashCode hash = definition.computeHash(map, intermediate, classToFeatureSplitMap);
equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
}
return equivalences.values();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index d526570..c198197 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -197,19 +197,28 @@
return pending.containsType(type);
}
- public boolean isLegacyPendingSynthetic(DexType type) {
+ private boolean isLegacyPendingSynthetic(DexType type) {
return pending.legacyClasses.containsKey(type);
}
+ public boolean isLegacySyntheticClass(DexType type) {
+ return isLegacyCommittedSynthetic(type) || isLegacyPendingSynthetic(type);
+ }
+
+ public boolean isLegacySyntheticClass(DexProgramClass clazz) {
+ return isLegacySyntheticClass(clazz.getType());
+ }
+
public boolean isNonLegacySynthetic(DexProgramClass clazz) {
- return isCommittedSynthetic(clazz.type) || isPendingSynthetic(clazz.type);
+ return isNonLegacySynthetic(clazz.type);
+ }
+
+ public boolean isNonLegacySynthetic(DexType type) {
+ return isCommittedSynthetic(type) || isPendingSynthetic(type);
}
public boolean isSyntheticClass(DexType type) {
- return isCommittedSynthetic(type)
- || isPendingSynthetic(type)
- // TODO(b/158159959): Remove usage of name-based identification.
- || type.isD8R8SynthesizedClassType();
+ return isLegacySyntheticClass(type) || isNonLegacySynthetic(type);
}
public boolean isSyntheticClass(DexProgramClass clazz) {
@@ -237,14 +246,6 @@
return true;
}
- public boolean isLegacySyntheticClass(DexType type) {
- return isLegacyCommittedSynthetic(type) || isLegacyPendingSynthetic(type);
- }
-
- public boolean isLegacySyntheticClass(DexProgramClass clazz) {
- return isLegacySyntheticClass(clazz.getType());
- }
-
public Collection<DexProgramClass> getLegacyPendingClasses() {
return Collections.unmodifiableCollection(pending.legacyClasses.values());
}
@@ -265,9 +266,8 @@
// Addition and creation of synthetic items.
- // TODO(b/158159959): Remove the usage of this direct class addition (and name-based id).
+ // TODO(b/158159959): Remove the usage of this direct class addition.
public void addLegacySyntheticClass(DexProgramClass clazz) {
- assert clazz.type.isD8R8SynthesizedClassType();
assert !isCommittedSynthetic(clazz.type);
assert !pending.nonLegacyDefinitions.containsKey(clazz.type);
DexProgramClass previous = pending.legacyClasses.put(clazz.type, clazz);
@@ -401,9 +401,9 @@
if (definition.isProgramDefinition()) {
committedProgramTypesBuilder.add(definition.getHolder().getType());
appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
- } else {
+ } else if (appBuilder.isDirect()) {
assert definition.isClasspathDefinition();
- appBuilder.addClasspathClass(definition.asClasspathDefinition().getHolder());
+ appBuilder.asDirect().addClasspathClass(definition.asClasspathDefinition().getHolder());
}
builder.addItem(definition);
}
@@ -428,7 +428,7 @@
// Finalization of synthetic items.
- public Result computeFinalSynthetics(AppView<?> appView) {
+ Result computeFinalSynthetics(AppView<?> appView) {
assert !hasPendingSyntheticClasses();
return new SyntheticFinalization(appView.options(), committed).computeFinalSynthetics(appView);
}
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 a9f55ee..0d4a768 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -41,7 +41,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.inspector.internal.InspectorImpl;
@@ -204,7 +203,6 @@
enableDevirtualization = false;
enableLambdaMerging = false;
horizontalClassMergerOptions.disable();
- enableStaticClassMerging = false;
enableVerticalClassMerging = false;
enableEnumUnboxing = false;
enableUninstantiatedTypeOptimization = false;
@@ -238,7 +236,6 @@
public boolean enableFieldAssignmentTracker = true;
public boolean enableFieldBitAccessAnalysis =
System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
- public boolean enableStaticClassMerging = true;
public boolean enableVerticalClassMerging = true;
public boolean enableArgumentRemoval = true;
public boolean enableUnusedInterfaceRemoval = true;
@@ -1263,9 +1260,6 @@
public BiConsumer<DexItemFactory, HorizontallyMergedLambdaClasses>
horizontallyMergedLambdaClassesConsumer = ConsumerUtils.emptyBiConsumer();
- public BiConsumer<DexItemFactory, StaticallyMergedClasses> staticallyMergedClassesConsumer =
- ConsumerUtils.emptyBiConsumer();
-
public BiConsumer<DexItemFactory, EnumDataMap> unboxedEnumsConsumer =
ConsumerUtils.emptyBiConsumer();
@@ -1318,6 +1312,7 @@
public boolean forceNameReflectionOptimization = false;
public boolean enableNarrowAndWideningingChecksInD8 = false;
public Consumer<IRCode> irModifier = null;
+ public Consumer<IRCode> inlineeIrModifier = null;
public int basicBlockMuncherIterationLimit = NO_LIMIT;
public boolean dontReportFailingCheckDiscarded = false;
public PrintStream whyAreYouNotInliningConsumer = System.out;
@@ -1895,4 +1890,12 @@
public boolean canHaveZipFileWithMissingCloseableBug() {
return isGeneratingClassFiles() || minApiLevel < AndroidApiLevel.K.getLevel();
}
+
+ // Some versions of Dalvik had a bug where a switch with a MAX_INT key would still go to
+ // the default case when switching on the value MAX_INT.
+ //
+ // See b/177790310.
+ public boolean canHaveSwitchMaxIntBug() {
+ return isGeneratingDex() && minApiLevel < AndroidApiLevel.K.getLevel();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/NoStaticClassMerging.java b/src/test/java/com/android/tools/r8/AssumeNoClassInitializationSideEffects.java
similarity index 85%
rename from src/test/java/com/android/tools/r8/NoStaticClassMerging.java
rename to src/test/java/com/android/tools/r8/AssumeNoClassInitializationSideEffects.java
index b97416f..bfe15ee 100644
--- a/src/test/java/com/android/tools/r8/NoStaticClassMerging.java
+++ b/src/test/java/com/android/tools/r8/AssumeNoClassInitializationSideEffects.java
@@ -1,11 +1,10 @@
// Copyright (c) 2020, 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
-public @interface NoStaticClassMerging {}
+public @interface AssumeNoClassInitializationSideEffects {}
diff --git a/src/test/java/com/android/tools/r8/NoStaticClassMerging.java b/src/test/java/com/android/tools/r8/AssumeNotNull.java
similarity index 79%
copy from src/test/java/com/android/tools/r8/NoStaticClassMerging.java
copy to src/test/java/com/android/tools/r8/AssumeNotNull.java
index b97416f..2c086a4 100644
--- a/src/test/java/com/android/tools/r8/NoStaticClassMerging.java
+++ b/src/test/java/com/android/tools/r8/AssumeNotNull.java
@@ -1,11 +1,10 @@
// Copyright (c) 2020, 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
-@Target({ElementType.TYPE})
-public @interface NoStaticClassMerging {}
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface AssumeNotNull {}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index f22a173..f3cbda6 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -737,19 +737,8 @@
TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
// Illegal class flags in Dalvik 4.0.4.
.put("121-modifiers", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
- // Switch regression still present in Dalvik 4.0.4.
- .put(
- "095-switch-MAX_INT",
- TestCondition.match(
- TestCondition.tools(DexTool.NONE),
- TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF),
- TestCondition.runtimes(DexVm.Version.V4_0_4)))
- .put(
- "095-switch-MAX_INT",
- TestCondition.match(
- TestCondition.tools(DexTool.NONE),
- TestCondition.compilers(CompilerUnderTest.D8),
- TestCondition.runtimes(DexVm.Version.V4_0_4)))
+ // Switch regression still present in the DEX code produced by DX on Dalvik 4.0.4.
+ // The fix for b/177790310 is not applied for DEX input (when merging).
.put(
"095-switch-MAX_INT",
TestCondition.match(
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 909d45e..83313a3 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
-import com.android.tools.r8.shaking.NoStaticClassMergingRule;
import com.android.tools.r8.shaking.NoUnusedInterfaceRemovalRule;
import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -335,6 +334,34 @@
"-alwaysinline class * { @" + annotationPackageName + ".AlwaysInline *; }");
}
+ public T enableAssumeNotNullAnnotations() {
+ return addAssumeNotNullAnnotation()
+ .enableAssumeNotNullAnnotations(AssumeNotNull.class.getPackage().getName());
+ }
+
+ public T enableAssumeNotNullAnnotations(String annotationPackageName) {
+ return addInternalKeepRules(
+ "-assumevalues class * {",
+ " @" + annotationPackageName + ".AssumeNotNull *** * return 1;",
+ " @" + annotationPackageName + ".AssumeNotNull *** *(...) return 1;",
+ "}");
+ }
+
+ public T enableAssumeNoClassInitializationSideEffectsAnnotations() {
+ return addAssumeNoClassInitializationSideEffectsAnnotation()
+ .enableAssumeNoClassInitializationSideEffectsAnnotations(
+ AssumeNoClassInitializationSideEffects.class.getPackage().getName());
+ }
+
+ public T enableAssumeNoClassInitializationSideEffectsAnnotations(String annotationPackageName) {
+ return addInternalKeepRules(
+ "-assumenosideeffects @"
+ + annotationPackageName
+ + ".AssumeNoClassInitializationSideEffects class * {",
+ " void <clinit>();",
+ "}");
+ }
+
public T enableAssumeNoSideEffectsAnnotations() {
return addAssumeNoSideEffectsAnnotations()
.enableAssumeNoSideEffectsAnnotations(AssumeNoSideEffects.class.getPackage().getName());
@@ -342,9 +369,9 @@
public T enableAssumeNoSideEffectsAnnotations(String annotationPackageName) {
return addInternalKeepRules(
- "-assumenosideeffects class * { @"
- + annotationPackageName
- + ".AssumeNoSideEffects <methods>; }");
+ "-assumenosideeffects class * {",
+ " @" + annotationPackageName + ".AssumeNoSideEffects <methods>;",
+ "}");
}
public T enableInliningAnnotations() {
@@ -435,7 +462,7 @@
}
public T enableNoHorizontalClassMergingAnnotations() {
- return addProgramClasses(NoHorizontalClassMerging.class)
+ return addNoHorizontalClassMergingAnnotations()
.addInternalMatchInterfaceRule(
NoHorizontalClassMergingRule.RULE_NAME, NoHorizontalClassMerging.class);
}
@@ -444,12 +471,6 @@
return addInternalKeepRules("-nohorizontalclassmerging class " + clazz);
}
- public T enableNoStaticClassMergingAnnotations() {
- return addNoStaticClassMergingAnnotations()
- .addInternalMatchInterfaceRule(
- NoStaticClassMergingRule.RULE_NAME, NoStaticClassMerging.class);
- }
-
public T enableMemberValuePropagationAnnotations() {
return enableMemberValuePropagationAnnotations(true);
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f6846b4..b2945dc 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -47,7 +47,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.EnqueuerFactory;
import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.shaking.NoStaticClassMergingRule;
+import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
import com.android.tools.r8.shaking.ProguardClassNameList;
import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -58,8 +58,7 @@
import com.android.tools.r8.shaking.ProguardMemberRule;
import com.android.tools.r8.shaking.ProguardMemberType;
import com.android.tools.r8.shaking.ProguardTypeMatcher;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
import com.android.tools.r8.shaking.serviceloader.ServiceLoaderMultipleTest.Greeter;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -763,9 +762,9 @@
ExecutorService executor = Executors.newSingleThreadExecutor();
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
RootSet rootSet =
- new RootSetBuilder(
+ RootSet.builder(
appView, subtypingInfo, appView.options().getProguardConfiguration().getRules())
- .run(executor);
+ .build(executor);
appView.setRootSet(rootSet);
AppInfoWithLiveness appInfoWithLiveness =
EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo)
@@ -1128,8 +1127,9 @@
}
@Deprecated
- public static String noStaticClassMergingRule() {
- return matchInterfaceRule(NoStaticClassMergingRule.RULE_NAME, NoStaticClassMerging.class);
+ public static String noHorizontalClassMerging() {
+ return matchInterfaceRule(
+ NoHorizontalClassMergingRule.RULE_NAME, NoHorizontalClassMerging.class);
}
/**
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 8534d9f..3d33673 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -176,7 +176,6 @@
NeverInline.class,
NoVerticalClassMerging.class,
NoHorizontalClassMerging.class,
- NoStaticClassMerging.class,
NeverPropagateValue.class);
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 79f00e2..b7ca275 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -21,7 +21,6 @@
import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector;
import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import com.android.tools.r8.utils.codeinspector.HorizontallyMergedLambdaClassesInspector;
-import com.android.tools.r8.utils.codeinspector.StaticallyMergedClassesInspector;
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
import com.google.common.base.Suppliers;
import java.io.ByteArrayOutputStream;
@@ -154,17 +153,6 @@
dexItemFactory, horizontallyMergedLambdaClasses))));
}
- public T addStaticallyMergedClassesInspector(
- Consumer<StaticallyMergedClassesInspector> inspector) {
- return addOptionsModification(
- options ->
- options.testing.staticallyMergedClassesConsumer =
- ((dexItemFactory, staticallyMergedClasses) ->
- inspector.accept(
- new StaticallyMergedClassesInspector(
- dexItemFactory, staticallyMergedClasses))));
- }
-
public T addVerticallyMergedClassesInspector(
Consumer<VerticallyMergedClassesInspector> inspector) {
return addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index b28a51f..362941b 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -460,6 +460,14 @@
return addTestingAnnotation(AlwaysInline.class);
}
+ public final T addAssumeNotNullAnnotation() {
+ return addTestingAnnotation(AssumeNotNull.class);
+ }
+
+ public final T addAssumeNoClassInitializationSideEffectsAnnotation() {
+ return addTestingAnnotation(AssumeNoClassInitializationSideEffects.class);
+ }
+
public final T addAssumeNoSideEffectsAnnotations() {
return addTestingAnnotation(AssumeNoSideEffects.class);
}
@@ -504,10 +512,6 @@
return addTestingAnnotation(NoHorizontalClassMerging.class);
}
- public final T addNoStaticClassMergingAnnotations() {
- return addTestingAnnotation(NoStaticClassMerging.class);
- }
-
public final T addNoUnusedInterfaceRemovalAnnotations() {
return addTestingAnnotation(NoUnusedInterfaceRemoval.class);
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
index 5f4945e..533cb86 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
@@ -8,7 +8,6 @@
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NoStaticClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -53,7 +52,11 @@
testForR8(parameters.getBackend())
.addInnerClasses(HorizontalClassMergerSynchronizedMethodTest.class)
.addKeepMainRule(Main.class)
- .enableNoStaticClassMergingAnnotations()
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertClassesNotMerged(LockOne.class, LockTwo.class, LockThree.class)
+ .assertMergedInto(AcquireThree.class, AcquireOne.class))
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
@@ -66,7 +69,7 @@
void action();
}
- // Will be merged with LockTwo.
+ // Must not be merged with LockTwo or LockThree.
static class LockOne {
static synchronized void acquire(I c) {
@@ -74,6 +77,7 @@
}
}
+ // Must not be merged with LockOne or LockThree.
public static class LockTwo {
static synchronized void acquire(I c) {
@@ -83,7 +87,7 @@
}
}
- @NoStaticClassMerging
+ // Must not be merged with LockOne or LockTwo.
public static class LockThree {
static synchronized void acquire(I c) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
index 3b0b12f..53f5ae2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
@@ -4,17 +4,17 @@
package com.android.tools.r8.classmerging.horizontalstatic;
+import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticCompanionClass;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
-import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -23,82 +23,78 @@
@RunWith(Parameterized.class)
public class StaticClassMergerInterfaceTest extends TestBase {
- private final Backend backend;
+ private final TestParameters parameters;
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public StaticClassMergerInterfaceTest(Backend backend) {
- this.backend = backend;
+ public StaticClassMergerInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
public void test() throws Exception {
- String expectedOutput = StringUtils.lines("In A.a()", "In B.b()", "In C.c()");
+ String expectedOutput = StringUtils.lines("In I.a()", "In J.b()", "In A.c()");
CodeInspector inspector =
- testForR8(backend)
- .addInnerClasses(StaticClassMergerInterfaceTest.class)
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
- .addKeepRules("-dontobfuscate")
+ // TODO(b/173990042): Extend horizontal class merging to interfaces.
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
.enableInliningAnnotations()
- .enableNeverClassInliningAnnotations()
- .run(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput)
.inspector();
- // Check that A has not been merged into B. The static class merger visits classes in alpha-
- // betical order. By the time A is processed, there is no merge representative and A is not
- // a valid merge representative itself, because it is an interface.
- if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V6_0_1) || backend == Backend.CF) {
- assertThat(inspector.clazz(A.class), isPresent());
+ // We do not allow horizontal class merging of interfaces and classes. Therefore, A should
+ // remain in the output.
+ assertThat(inspector.clazz(A.class), isPresent());
+
+ // TODO(b/173990042): I and J should be merged.
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ assertThat(inspector.clazz(I.class), isPresent());
+ assertThat(inspector.clazz(J.class), isPresent());
} else {
- assertThat(inspector.clazz(A.class.getTypeName() + "$-CC"), isPresent());
+ assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
+ assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
}
-
- // By the time B is processed, there is no merge representative, so it should be present.
- assertThat(inspector.clazz(B.class), isPresent());
-
- // By the time C is processed, B should be merge candidate. Therefore, we should allow C.c() to
- // be moved to B *although C is an interface*.
- assertThat(inspector.clazz(C.class), not(isPresent()));
}
static class TestClass {
public static void main(String[] args) {
- A.a();
- B.b();
- C.c();
+ I.a();
+ J.b();
+ A.c();
}
}
- @NeverClassInline
- interface A {
+ interface I {
@NeverInline
static void a() {
- System.out.println("In A.a()");
+ System.out.println("In I.a()");
}
}
- @NeverClassInline
- static class B {
+ interface J {
@NeverInline
static void b() {
- System.out.println("In B.b()");
+ System.out.println("In J.b()");
}
}
- @NeverClassInline
- interface C {
+ static class A {
@NeverInline
static void c() {
- System.out.println("In C.c()");
+ System.out.println("In A.c()");
}
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
index 7d6aa4a..12f1b94 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
@@ -6,14 +6,12 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.io.IOException;
import java.util.List;
@@ -29,32 +27,30 @@
static class StaticMergeCandidateA {
- @AssumeMayHaveSideEffects
@NeverInline
- public static String m() {
- return "StaticMergeCandidateA.m()";
+ public static void m() {
+ System.out.println("StaticMergeCandidateA.m()");
}
}
static class StaticMergeCandidateB {
- @AssumeMayHaveSideEffects
@NeverInline
- public static String m() {
- return "StaticMergeCandidateB.m()";
+ public static void m() {
+ System.out.println("StaticMergeCandidateB.m()");
}
}
static class TestClass {
public static void main(String[] args) {
- System.out.println(StaticMergeCandidateA.m());
- System.out.print(StaticMergeCandidateB.m());
+ StaticMergeCandidateA.m();
+ StaticMergeCandidateB.m();
}
}
private final String EXPECTED =
- StringUtils.joinLines("StaticMergeCandidateA.m()", "StaticMergeCandidateB.m()");
+ StringUtils.lines("StaticMergeCandidateA.m()", "StaticMergeCandidateB.m()");
private final TestParameters parameters;
@@ -77,27 +73,29 @@
@Test
public void testStaticClassIsRemoved() throws Exception {
- CodeInspector inspector =
- testForR8(parameters.getBackend())
- .addInnerClasses(StaticClassMergerTest.class)
- .addKeepMainRule(TestClass.class)
- .noMinification()
- .enableInliningAnnotations()
- .enableSideEffectAnnotations()
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspector();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertMergedInto(
+ StaticMergeCandidateB.class, StaticMergeCandidateA.class))
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ inspector -> {
+ // Check that one of the two static merge candidates has been removed
+ List<FoundClassSubject> classes =
+ inspector.allClasses().stream()
+ .filter(clazz -> clazz.getOriginalName().contains("StaticMergeCandidate"))
+ .collect(Collectors.toList());
+ assertEquals(1, classes.size());
- // Check that one of the two static merge candidates has been removed
- List<FoundClassSubject> classes =
- inspector.allClasses().stream()
- .filter(clazz -> clazz.getOriginalName().contains("StaticMergeCandidate"))
- .collect(Collectors.toList());
- assertEquals(1, classes.size());
-
- // Check that the remaining static merge candidate has two methods.
- FoundClassSubject remaining = classes.get(0);
- assertEquals(2, remaining.allMethods().size());
+ // Check that the remaining static merge candidate has two methods.
+ FoundClassSubject remaining = classes.get(0);
+ assertEquals(2, remaining.allMethods().size());
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
index 9d545e5..189ac21 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
@@ -5,17 +5,15 @@
package com.android.tools.r8.classmerging.horizontalstatic;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -37,38 +35,32 @@
@Test
public void testStaticClassIsRemoved() throws Exception {
- CodeInspector inspector =
- testForR8(parameters.getBackend())
- .addInnerClasses(StaticClassMergerVisibilityTest.class)
- .addKeepMainRule(Main.class)
- .enableInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("A.print()", "B.print()", "C.print()", "D.print()")
- .inspector();
-
- // The global group will have one representative, which is C.
- ClassSubject clazzC = inspector.clazz(C.class);
- assertThat(clazzC, isPresent());
- assertEquals(1, clazzC.allMethods().size());
-
- // The package group will be merged into one of A, B or C.
- assertExactlyOneIsPresent(
- inspector.clazz(A.class), inspector.clazz(B.class), inspector.clazz(D.class));
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StaticClassMergerVisibilityTest.class)
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertMergedInto(A.class, D.class)
+ .assertMergedInto(B.class, D.class)
+ .assertMergedInto(C.class, D.class))
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.print()", "B.print()", "C.print()", "D.print()")
+ .inspect(
+ inspector -> {
+ // All classes are merged into D.
+ ClassSubject clazzD = inspector.clazz(D.class);
+ assertThat(clazzD, isPresent());
+ assertThat(clazzD, isPublic());
+ // D now has 5 methods (there is a synthetic access bridge for the private A.print()).
+ assertEquals(5, clazzD.allMethods().size());
+ });
}
- private void assertExactlyOneIsPresent(ClassSubject... subjects) {
- boolean seenPresent = false;
- for (ClassSubject subject : subjects) {
- if (subject.isPresent()) {
- assertFalse(seenPresent);
- seenPresent = true;
- }
- }
- assertTrue(seenPresent);
- }
-
- // Will be merged into the package group.
+ // Will be merged into D.
private static class A {
@NeverInline
private static void print() {
@@ -76,7 +68,7 @@
}
}
- // Will be merged into the package group.
+ // Will be merged into D.
static class B {
@NeverInline
static void print() {
@@ -84,7 +76,7 @@
}
}
- // Will be merged into global group
+ // Will be merged into D
public static class C {
@NeverInline
public static void print() {
@@ -92,7 +84,6 @@
}
}
- // Will be merged into the package group.
protected static class D {
@NeverInline
protected static void print() {
diff --git a/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java b/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java
new file mode 100644
index 0000000..c8f709b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java
@@ -0,0 +1,85 @@
+// 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.nest;
+
+import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+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.desugar.nest.NestBasedAccessToNativeMethodTest.Main.Inner;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NestBasedAccessToNativeMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(JDK11)
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build();
+ }
+
+ public NestBasedAccessToNativeMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClassFileData(getProgramClassFileData())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello from native");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getProgramClassFileData())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello from native");
+ }
+
+ private List<byte[]> getProgramClassFileData() throws IOException, NoSuchMethodException {
+ return ImmutableList.of(
+ transformer(Main.class).setNest(Main.class, Inner.class).transform(),
+ transformer(Inner.class)
+ .setPrivate(Inner.class.getDeclaredMethod("goingToBePrivate"))
+ .setNest(Main.class, Inner.class)
+ .transform());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ try {
+ Inner.goingToBePrivate();
+ } catch (UnsatisfiedLinkError e) {
+ System.out.println("Hello from native");
+ }
+ }
+
+ static class Inner {
+
+ /*private*/ static native void goingToBePrivate();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index dce9fbc..dbb1e76 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -127,8 +127,6 @@
ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
Consumer<R8FullTestBuilder> r8TestConfigurator)
throws IOException, CompilationFailedException, E {
- Path featureOutput = temp.newFile("feature.zip").toPath();
-
R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend());
if (parameters.isCfRuntime()) {
// Compiling to jar we need to support the same way of loading code at runtime as
@@ -141,8 +139,7 @@
r8FullTestBuilder
.addProgramClasses(SplitRunner.class, RunInterface.class)
.addProgramClasses(baseClasses)
- .addFeatureSplit(
- builder -> simpleSplitProvider(builder, featureOutput, temp, featureClasses))
+ .addFeatureSplit(featureClasses.toArray(new Class[0]))
.addInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(SplitRunner.class)
@@ -153,8 +150,8 @@
R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
compileResultConsumer.accept(r8TestCompileResult);
Path baseOutput = r8TestCompileResult.writeToZip();
-
- return runFeatureOnArt(toRun, baseOutput, featureOutput, parameters.getRuntime());
+ return runFeatureOnArt(
+ toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
}
// Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
new file mode 100644
index 0000000..af73519
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
@@ -0,0 +1,165 @@
+// 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.dexsplitter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * We need to ensure that we distribute the synthetic items in the features where they where
+ * generated.
+ */
+@RunWith(Parameterized.class)
+public class SyntheticDistributionTest extends SplitterTestBase {
+
+ public static final String EXPECTED = StringUtils.lines("42");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public SyntheticDistributionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testDistribution() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ Consumer<R8FullTestBuilder> configurator =
+ r8FullTestBuilder ->
+ r8FullTestBuilder
+ .noMinification()
+ .enableInliningAnnotations()
+ .addInliningAnnotations()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O));
+ ThrowingConsumer<R8TestCompileResult, Exception> ensureLambdaNotInBase =
+ r8TestCompileResult -> {
+ r8TestCompileResult.inspect(
+ base ->
+ assertFalse(base.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)),
+ feature ->
+ assertTrue(
+ feature.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)));
+ };
+ ProcessResult processResult =
+ testR8Splitter(
+ parameters,
+ ImmutableSet.of(BaseSuperClass.class),
+ ImmutableSet.of(FeatureClass.class, MyFunction.class),
+ FeatureClass.class,
+ ensureLambdaNotInBase,
+ configurator);
+ assertEquals(processResult.exitCode, 0);
+ assertEquals(processResult.stdout, StringUtils.lines("42foobar"));
+ }
+
+ @Test
+ public void testNoMergingAcrossBoundaries() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseSuperClass.class, MyFunction.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(FeatureClass.class)
+ .addFeatureSplit(Feature2Class.class)
+ .addKeepFeatureMainRules(BaseSuperClass.class, FeatureClass.class, Feature2Class.class)
+ .noMinification()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+
+ compileResult
+ .runFeature(parameters.getRuntime(), FeatureClass.class, compileResult.getFeature(0))
+ .assertSuccessWithOutputLines("42foobar");
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature2Class.class, compileResult.getFeature(1))
+ .assertSuccessWithOutputLines("43barfoo");
+ }
+
+ public abstract static class BaseSuperClass implements RunInterface {
+ @Override
+ public void run() {
+ System.out.println(getFromFeature());
+ }
+
+ public abstract String getFromFeature();
+ }
+
+ public interface MyFunction {
+ String apply(String s);
+ }
+
+ public static class FeatureClass extends BaseSuperClass {
+ @NeverInline
+ public static String getAString() {
+ return System.currentTimeMillis() < 2 ? "Not happening" : "foobar";
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ }
+
+ @Override
+ public String getFromFeature() {
+ String s = getAString();
+ return useTheLambda(a -> a.concat(s));
+ }
+
+ @NeverInline
+ private String useTheLambda(MyFunction f) {
+ return f.apply("42");
+ }
+ }
+
+ public static class Feature2Class extends BaseSuperClass {
+ @NeverInline
+ public static String getAString() {
+ return System.currentTimeMillis() < 2 ? "Not happening" : "barfoo";
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ }
+
+ @Override
+ public String getFromFeature() {
+ // The lambda is the same as in FeatureClass, but we should not share it since there is
+ // no way for Feature2Class to access code in Feature1Class (assuming either that
+ // Feature1Class is not installed of isolated splits are used).
+ String s = getAString();
+ return useTheLambda(a -> a.concat(s));
+ }
+
+ @NeverInline
+ private String useTheLambda(MyFunction f) {
+ return f.apply("43");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
index 74ee98c..67dfbc0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import java.util.List;
@@ -43,7 +43,7 @@
.addKeepMainRule(classToTest)
.addKeepRules(enumKeepRules.getKeepRules())
.enableInliningAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableNeverClassInliningAnnotations()
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
@@ -69,7 +69,7 @@
}
@NeverClassInline
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
static class OtherClass {
static {
Switch.otherClassInit = true;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index 89395ef..b21cbd0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -97,13 +97,6 @@
return testForR8(Backend.CF)
.addProgramClasses(ToStringLib.class, ToStringLib.LibEnum.class)
.addKeepRules(enumKeepRules.getKeepRules())
- // TODO(b/160535629): Work-around on some optimizations relying on $VALUES name.
- .addKeepRules(
- "-keep enum "
- + ToStringLib.LibEnum.class.getName()
- + " { static "
- + ToStringLib.LibEnum.class.getName()
- + "[] $VALUES; }")
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.addKeepMethodRules(
Reference.methodFromMethod(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index 374895c..702ce3e 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -5,6 +5,7 @@
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestCompileResult;
@@ -100,11 +101,16 @@
InstanceFieldPutObject fieldPut = new InstanceFieldPutObject();
fieldPut.setA();
Object obj = new Object();
- fieldPut.e = obj;
+ put(fieldPut, obj);
System.out.println(fieldPut.e);
System.out.println(obj);
}
+ @NeverInline
+ static void put(InstanceFieldPutObject object, Object value) {
+ object.e = value;
+ }
+
void setA() {
e = MyEnum.A;
}
diff --git a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
index 7277139..ae244b5 100644
--- a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
@@ -8,7 +8,7 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -24,10 +24,10 @@
@RunWith(Parameterized.class)
public class MissingClassThrowingTest extends TestBase {
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
public static class MissingException extends Exception {}
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
public static class Program {
public static final String EXPECTED_OUTPUT = "Hello world!";
@@ -76,7 +76,7 @@
.noMinification()
.noTreeShaking()
.enableInliningAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.debug()
.compileWithExpectedDiagnostics(
diagnostics -> {
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
index 9c44e2a..b8dd4fc 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
@@ -6,7 +6,7 @@
import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
import static org.junit.Assert.assertEquals;
-import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -45,7 +45,25 @@
.addProgramClasses(EmptySubC.class, C.class, D.class, Main.class)
.addProgramClassFileData(getClassBWithTransformedInvoked(holder))
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("Should not be called.");
+ .assertSuccessWithOutputLinesIf(isExpectedToSucceedWithD8(), "D")
+ .assertFailureWithErrorThatThrowsIf(!isExpectedToSucceedWithD8(), VerifyError.class);
+ }
+
+ private boolean isExpectedToSucceedWithD8() {
+ if (holder == C.class) {
+ // Should always succeed.
+ return true;
+ }
+ // TODO(b/144410139): Consider making this a compilation failure instead.
+ if (holder == EmptySubC.class && parameters.isDexRuntime()) {
+ // Fails with verification error due to "Bad type on operand stack" on the JVM.
+ //
+ // D8 replaces the invoke in B.callPrint() with an invoke-virtual to EmptySubC.print().
+ // Strictly speaking this doesn't type check, since '(C) this' in B is not a subtype of
+ // EmptySubC, but the code still runs on Art and produces 'D'.
+ return true;
+ }
+ return false;
}
@Test
@@ -56,7 +74,7 @@
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("Should not be called.");
+ .assertSuccessWithOutputLines("D");
}
private byte[] getClassBWithTransformedInvoked(Class<?> holder) throws IOException {
@@ -65,8 +83,8 @@
"callPrint",
(opcode, owner, name, descriptor, isInterface, continuation) -> {
if (name.equals("replace")) {
- assertEquals(INVOKESPECIAL, opcode);
- assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
+ assertEquals(INVOKEVIRTUAL, opcode);
+ assertEquals(getBinaryNameFromJavaType(C.class.getTypeName()), owner);
continuation.visitMethodInsn(
opcode,
getBinaryNameFromJavaType(holder.getTypeName()),
@@ -93,7 +111,7 @@
public static class C extends B {
- private void replace() {
+ void replace() {
System.out.println("Should not be called.");
}
@@ -108,7 +126,7 @@
public static class D extends EmptySubC {
@Override
void print() {
- System.out.println("C");
+ System.out.println("D");
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
new file mode 100644
index 0000000..cebfab3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+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 ClankDepsTest extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public ClankDepsTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ private static final Path DIR = Paths.get("third_party", "chrome", "clank_google3_prebuilt");
+ private static final Path CLASSES = DIR.resolve("classes.jar");
+ private static final Path CONFIG = DIR.resolve("proguard.txt");
+
+ @Test
+ public void test() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramFiles(CLASSES)
+ .addKeepRuleFiles(CONFIG)
+ .addDontWarn("androidx.**")
+ .addDontWarnJavax()
+ .addDontWarn("dalvik.system.VMStack")
+ .addDontWarn("zzz.com.facebook.litho.R$id")
+ .addDontWarn("com.google.android.libraries.elements.R$id")
+ .allowUnusedProguardConfigurationRules()
+ .allowUnusedDontWarnPatterns()
+ .setMinApi(AndroidApiLevel.N)
+ .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertOnlyInfos);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 18f15fd..73f1561 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -23,7 +23,7 @@
import com.android.tools.r8.shaking.EnqueuerFactory;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardKeepRule;
-import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.InternalOptions;
@@ -68,11 +68,11 @@
ExecutorService executorService = ThreadUtils.getExecutorService(options);
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
appView.setRootSet(
- new RootSetBuilder(
+ RootSet.builder(
appView,
subtypingInfo,
ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
- .run(executorService));
+ .build(executorService));
Timing timing = Timing.empty();
Enqueuer enqueuer =
EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
index 26e44d5..4aaeda5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
@@ -41,7 +41,7 @@
ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java
new file mode 100644
index 0000000..99a169d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java
@@ -0,0 +1,77 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverSingleCallerInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 ClassInlinerDirectWithUnknownReturnTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlinerDirectWithUnknownReturnTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableNeverSingleCallerInlineAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString(
+ "Unexpected non-trivial phi in method eligible for class inlining")));
+ });
+ }
+
+ public static class A {
+
+ public int number;
+
+ @NeverSingleCallerInline
+ public A abs() {
+ if (number > 0) {
+ return this;
+ }
+ return new A();
+ }
+
+ @Override
+ @NeverSingleCallerInline
+ public String toString() {
+ return "Hello World " + number;
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A a = new A();
+ a.number = args.length;
+ System.out.println(a.abs().toString());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
new file mode 100644
index 0000000..1740c8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -0,0 +1,241 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/178608910.
+@RunWith(Parameterized.class)
+public class ClassInlinerPhiDirectUserAfterInlineTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final List<String> EXPECTED = ImmutableList.of("0", "A::baz");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ClassInlinerPhiDirectUserAfterInlineTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNoClassInlining() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> {
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ // Here we modify the IR when it is processed normally.
+ options.testing.irModifier = this::modifyIr;
+ options.enableClassInlining = false;
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> {
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.testing.inlineeIrModifier = this::modifyIr;
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED)
+ .inspect(
+ inspector -> {
+ // Assert that the A has been class-inlined into the caller.
+ ClassSubject aSubject = inspector.clazz(A.class);
+ assertThat(aSubject, not(isPresent()));
+ });
+ }
+
+ private void modifyIr(IRCode irCode) {
+ if (irCode.context().getReference().qualifiedName().equals(A.class.getTypeName() + ".foo")) {
+ assertEquals(7, irCode.blocks.size());
+ // This is the code that we expect
+ // <pre>
+ // blocks:
+ // block 0, pred-counts: 0, succ-count: 1, filled: true, sealed: true
+ // predecessors: -
+ // successors: 1 (no try/catch successors)
+ // no phis
+ // #0:foo;0:main: -1: Argument v3 <-
+ // : -1: ConstNumber v4(0) <- 0 (INT)
+ // : -1: Goto block 1
+ //
+ // block 1, pred-counts: 2, succ-count: 2, filled: true, sealed: true
+ // predecessors: 0 5
+ // successors: 2 3 (no try/catch successors)
+ // v7 <- phi(v4(0), v13) : INT
+ // #0:foo;0:main: -1: InstanceGet v6 <- v3; field: int
+ // com.android.tools.r8.ir.optimize.classinliner
+ // .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+ // : -1: If v6 EQZ block 2 (fallthrough 3)
+ //
+ // block 2, pred-counts: 1, succ-count: 0, filled: true, sealed: true
+ // predecessors: 1
+ // successors: -
+ // no phis
+ // #0:foo;0:main: -1: Invoke-Virtual v3, v7; method: void
+ // com.android.tools.r8.ir.optimize.classinliner
+ // .ClassInlinerPhiDirectUserAfterInlineTest$A.bar(int)
+ // : -1: Return
+ //
+ // block 3, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+ // predecessors: 1
+ // successors: 4 (no try/catch successors)
+ // no phis
+ // #0:foo;0:main: -1: ConstNumber v8(2) <- 2 (INT)
+ // : -1: Add v9 <- v7, v8(2)
+ // : -1: Goto block 4
+ //
+ // block 4, pred-counts: 2, succ-count: 2, filled: true, sealed: true
+ // predecessors: 3 6
+ // successors: 5 6 (no try/catch successors)
+ // v13 <- phi(v9, v15) : INT
+ // #0:foo;0:main: -1: InstanceGet v11 <- v3; field: int
+ // com.android.tools.r8.ir.optimize.classinliner
+ // .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+ // : -1: ConstNumber v12(10) <- 10 (INT)
+ // : -1: If v11, v12(10) LE block 5
+ // (fallthrough 6)
+ //
+ // block 5, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+ // predecessors: 4
+ // successors: 1 (no try/catch successors)
+ // no phis
+ // #0:foo;0:main: -1: Goto block 1
+ //
+ // block 6, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+ // predecessors: 4
+ // successors: 4 (no try/catch successors)
+ // no phis
+ // #0:foo;0:main: -1: ConstNumber v14(3) <- 3 (INT)
+ // : -1: Add v15 <- v13, v14(3)
+ // : -1: ConstNumber v16(-1) <- -1 (INT)
+ // : -1: Add v17 <- v11, v16(-1)
+ // : -1: InstancePut v3, v17; field: int
+ // com.android.tools.r8.ir.optimize.classinliner
+ // .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+ // : -1: Goto block 4
+ // </pre>
+ // We modify block 1 to have:
+ // vX : phi(v3, vY)
+ // .. InstanceGet vX ...
+ //
+ // and block 4 to have:
+ // vY : phi(v3, vX)
+ BasicBlock basicBlock = irCode.blocks.get(0);
+ Argument argument = basicBlock.getInstructions().get(0).asArgument();
+ assertNotNull(argument);
+ Value argumentValue = argument.outValue();
+
+ BasicBlock block1 = irCode.blocks.get(1);
+ assertTrue(block1.exit().isIf());
+
+ BasicBlock block3 = irCode.blocks.get(3);
+ assertTrue(block1.getSuccessors().contains(block3));
+ assertTrue(block3.exit().isGoto());
+
+ BasicBlock block4 = irCode.blocks.get(4);
+ assertSame(block3.getUniqueNormalSuccessor(), block4);
+
+ Phi firstPhi =
+ new Phi(
+ irCode.valueNumberGenerator.next(),
+ block1,
+ argumentValue.getType(),
+ null,
+ RegisterReadType.NORMAL);
+
+ Phi secondPhi =
+ new Phi(
+ irCode.valueNumberGenerator.next(),
+ block4,
+ argumentValue.getType(),
+ null,
+ RegisterReadType.NORMAL);
+
+ firstPhi.addOperands(ImmutableList.of(argumentValue, secondPhi));
+ secondPhi.addOperands(ImmutableList.of(argumentValue, firstPhi));
+
+ // Replace the invoke to use the phi
+ InstanceGet instanceGet = block1.getInstructions().get(0).asInstanceGet();
+ assertNotNull(instanceGet);
+ assertEquals(A.class.getTypeName(), instanceGet.getField().holder.toSourceString());
+ instanceGet.replaceValue(0, firstPhi);
+ }
+ }
+
+ public static class A {
+
+ int number = 0;
+
+ public void foo() {
+ int otherNumber = 0;
+ while (number != 0) {
+ otherNumber += 2;
+ while (number > 10) {
+ otherNumber += 3;
+ number--;
+ }
+ }
+ bar(otherNumber);
+ }
+
+ public void bar(int number) {
+ System.out.println(number + "");
+ }
+
+ public void baz() {
+ System.out.println("A::baz");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A a = new A();
+ a.number = args.length;
+ a.foo();
+ a.baz();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
index cc0734b..4943a47 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
@@ -4,11 +4,11 @@
package com.android.tools.r8.ir.optimize.classinliner.testclasses;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
public class ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses {
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
public static class CandidateBase {
public final String f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/logging/CustomLoggingFrameworkRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/logging/CustomLoggingFrameworkRemovalTest.java
new file mode 100644
index 0000000..b40d47e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/logging/CustomLoggingFrameworkRemovalTest.java
@@ -0,0 +1,119 @@
+// 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.ir.optimize.logging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AssumeNoClassInitializationSideEffects;
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.AssumeNotNull;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CustomLoggingFrameworkRemovalTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+ .build();
+ }
+
+ public CustomLoggingFrameworkRemovalTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules("-assumenosideeffects class java.lang.StringBuilder { void <init>(int); }")
+ .enableAssumeNotNullAnnotations()
+ .enableAssumeNoClassInitializationSideEffectsAnnotations()
+ .enableAssumeNoSideEffectsAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .allMatch(InstructionSubject::isReturnVoid));
+
+ MethodSubject clinitMethodSubject = mainClassSubject.clinit();
+ assertThat(clinitMethodSubject, isAbsent());
+
+ assertThat(inspector.clazz(Logger.class), isAbsent());
+ assertThat(inspector.clazz(LoggerFactory.class), isAbsent());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ public static class Main {
+
+ @AssumeNotNull private static final Logger logger = LoggerFactory.getLogger("MainActivity");
+
+ public static void main(String[] args) {
+ logger.print(new StringBuilder(getInt(5)).append("Hello").toString());
+ logger.print(Objects.toString(new StringBuilder(getInt(6)).append(" world")));
+ logger.print(String.valueOf(new StringBuilder(getInt(1)).append("!")));
+ }
+
+ @AssumeNoSideEffects
+ @NeverInline
+ private static int getInt(int value) {
+ return System.currentTimeMillis() > 0 ? value : -1;
+ }
+ }
+
+ public static class Logger {
+
+ private Logger() {}
+
+ @AssumeNoSideEffects
+ public void print(String message) {
+ System.out.print(message);
+ }
+ }
+
+ @AssumeNoClassInitializationSideEffects
+ public static class LoggerFactory {
+
+ private static final Map<String, Logger> loggers = new HashMap<>();
+
+ @AssumeNoSideEffects
+ static synchronized Logger getLogger(String key) {
+ return loggers.computeIfAbsent(key, ignore -> new Logger());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
index e1e6b4b..f975e36 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
@@ -4,10 +4,11 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -15,7 +16,6 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,7 +59,7 @@
assertThat(configClassSubject, isPresent());
FieldSubject alwaysTrueFieldSubject = configClassSubject.uniqueFieldWithName("alwaysTrue");
- assertThat(alwaysTrueFieldSubject, isPresent());
+ assertThat(alwaysTrueFieldSubject, isAbsent());
FieldSubject alwaysTrueNoSideEffectsFieldSubject =
configClassSubject.uniqueFieldWithName("alwaysTrueNoSideEffects");
@@ -70,14 +70,11 @@
MethodSubject mainMethodSubject = testClassSubject.mainMethod();
assertThat(mainMethodSubject, isPresent());
- assertEquals(
- 1,
- mainMethodSubject
- .streamInstructions()
- .filter(InstructionSubject::isInstanceGet)
- .map(InstructionSubject::getField)
- .filter(alwaysTrueFieldSubject.getField().field::equals)
- .count());
+ if (canUseRequireNonNull(parameters)) {
+ assertThat(mainMethodSubject, invokesMethodWithName("requireNonNull"));
+ } else {
+ assertThat(mainMethodSubject, invokesMethodWithName("getClass"));
+ }
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantInstanceFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantInstanceFieldLoadAfterStoreTest.java
index 7c39564..73e21ba 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantInstanceFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantInstanceFieldLoadAfterStoreTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestBase;
@@ -14,8 +14,6 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -27,7 +25,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public RedundantInstanceFieldLoadAfterStoreTest(TestParameters parameters) {
@@ -40,23 +38,15 @@
.addInnerClasses(RedundantInstanceFieldLoadAfterStoreTest.class)
.addKeepMainRule(TestClass.class)
.enableNeverClassInliningAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector -> {
ClassSubject classSubject = inspector.clazz(TestClass.class);
assertThat(classSubject, isPresent());
- // Removing the actual field definition would require two optimization passes, because
- // we would need another round of tree shaking to learn that the `greeting` field is
- // no longer read after the field load in main() has been eliminated.
FieldSubject fieldSubject = classSubject.uniqueFieldWithName("greeting");
- assertThat(fieldSubject, isPresent());
-
- MethodSubject methodSubject = classSubject.mainMethod();
- assertThat(methodSubject, isPresent());
- assertTrue(
- methodSubject.streamInstructions().noneMatch(InstructionSubject::isInstanceGet));
+ assertThat(fieldSubject, isAbsent());
})
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello world!");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantStaticFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantStaticFieldLoadAfterStoreTest.java
index d29688e..936b777 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantStaticFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantStaticFieldLoadAfterStoreTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -26,7 +27,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public RedundantStaticFieldLoadAfterStoreTest(TestParameters parameters) {
@@ -38,18 +39,15 @@
testForR8(parameters.getBackend())
.addInnerClasses(RedundantStaticFieldLoadAfterStoreTest.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector -> {
ClassSubject classSubject = inspector.clazz(TestClass.class);
assertThat(classSubject, isPresent());
- // Removing the actual field definition would require two optimization passes, because
- // we would need another round of tree shaking to learn that the `greeting` field is
- // no longer read after the field load in main() has been eliminated.
FieldSubject fieldSubject = classSubject.uniqueFieldWithName("greeting");
- assertThat(fieldSubject, isPresent());
+ assertThat(fieldSubject, isAbsent());
MethodSubject methodSubject = classSubject.mainMethod();
assertThat(methodSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
index 8cc815b..b254b43 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
@@ -9,7 +9,7 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexMethod;
@@ -53,7 +53,7 @@
.addInnerClasses(ParameterRewritingTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.addOptionsModification(options -> options.enableClassInlining = false)
.noMinification()
.run(TestClass.class)
@@ -114,10 +114,10 @@
}
}
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
static class Uninstantiated {}
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
static class Factory {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
index 7d3eb83..0ac4efc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -8,7 +8,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
@@ -52,7 +52,7 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.addKeepRules("-dontobfuscate")
.addOptionsModification(options -> options.enableClassInlining = false)
.run(TestClass.class)
@@ -104,7 +104,7 @@
}
}
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
@NoVerticalClassMerging
static class Uninstantiated {}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index d6d2477..b9fefbaf4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -464,8 +464,7 @@
inspector -> {
ClassSubject outerClass =
checkClassIsKept(inspector, "properties.CompanionProperties");
- ClassSubject companionClass =
- checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+ checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -474,14 +473,6 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
}
-
- MemberNaming.MethodSignature getter =
- COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, getter);
-
- MemberNaming.MethodSignature setter =
- COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, setter);
});
}
@@ -500,8 +491,7 @@
inspector -> {
ClassSubject outerClass =
checkClassIsKept(inspector, "properties.CompanionProperties");
- ClassSubject companionClass =
- checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+ checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "privateProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -517,8 +507,6 @@
// known to be non-null, thus the getter/setter can be inlined if their code is small
// enough. Because the backing field is private, they will call into an accessor
// (static) method. If access relaxation is enabled, this accessor can be removed.
- checkMethodIsAbsent(companionClass, getter);
- checkMethodIsAbsent(companionClass, setter);
if (allowAccessModification) {
assertTrue(fieldSubject.getField().accessFlags.isPublic());
} else {
@@ -542,8 +530,7 @@
inspector -> {
ClassSubject outerClass =
checkClassIsKept(inspector, "properties.CompanionProperties");
- ClassSubject companionClass =
- checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+ checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "internalProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -553,13 +540,6 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
}
-
- MemberNaming.MethodSignature getter =
- COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, getter);
- MemberNaming.MethodSignature setter =
- COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, setter);
});
}
@@ -578,8 +558,7 @@
inspector -> {
ClassSubject outerClass =
checkClassIsKept(inspector, "properties.CompanionProperties");
- ClassSubject companionClass =
- checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+ checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
String propertyName = "publicProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -589,14 +568,6 @@
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
}
-
- MemberNaming.MethodSignature getter =
- COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, getter);
-
- MemberNaming.MethodSignature setter =
- COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, setter);
});
}
@@ -616,26 +587,21 @@
inspector -> {
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName());
+ checkClassIsRemoved(inspector, testedClass.getClassName());
String propertyName = "privateLateInitProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
// Because the getter/setter are private, they can only be called from another method
// in the class. If this is an instance method, they will be called on 'this' which is
// known to be non-null, thus the getter/setter can be inlined if their code is small
// enough. Because the backing field is private, they will call into an accessor
// (static) method. If access relaxation is enabled, this accessor can be removed.
- checkMethodIsAbsent(companionClass, getter);
- checkMethodIsAbsent(companionClass, setter);
if (allowAccessModification) {
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ assertTrue(fieldSubject.getField().isPublic());
} else {
- assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+ assertTrue(fieldSubject.getField().isPrivate());
}
});
}
@@ -656,18 +622,12 @@
inspector -> {
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName());
+ checkClassIsRemoved(inspector, testedClass.getClassName());
String propertyName = "internalLateInitProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, getter);
-
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, setter);
});
}
@@ -687,18 +647,12 @@
inspector -> {
ClassSubject outerClass =
checkClassIsKept(inspector, testedClass.getOuterClassName());
- ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName());
+ checkClassIsRemoved(inspector, testedClass.getClassName());
String propertyName = "publicLateInitProp";
FieldSubject fieldSubject =
checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
- MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, getter);
-
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
- checkMethodIsRemoved(companionClass, setter);
});
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index 5a23bd3..4997fe3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -113,10 +113,13 @@
}
private static Group kstyleImpl(String pkg, String capture, int arity, int singletons) {
- assertEquals(capture.isEmpty(), singletons != 0);
return new Group(pkg, capture, arity, KOTLIN_FUNCTION_IFACE_STR + arity, singletons);
}
+ static Group kstyle(String pkg, int arity) {
+ return kstyleImpl(pkg, "", arity, 0);
+ }
+
static Group kstyle(String pkg, int arity, int singletons) {
assertTrue(singletons != 0);
return kstyleImpl(pkg, "", arity, singletons);
@@ -568,7 +571,7 @@
String grp = allowAccessModification ? "" : pkg;
verifier.assertLambdaGroups(
- kstyle(grp, 1, 1 /* 1 out of 5 lambdas in the group */),
+ kstyle(grp, 1 /* 1 out of 5 lambdas in the group */),
jstyle(grp, 2, "java.util.Comparator", 0 /* 0 out of 2 lambdas in the group */));
verifier.assertLambdas(/* None */ );
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
index 8f88d08..a76d8cc 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -79,7 +78,7 @@
.collectMainDexClasses()
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile();
@@ -130,7 +129,6 @@
}
@NoHorizontalClassMerging
- @NoStaticClassMerging
static class A {
static void bar() {
@@ -139,7 +137,6 @@
}
@NoHorizontalClassMerging
- @NoStaticClassMerging
static class B {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
new file mode 100644
index 0000000..65bf904
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
@@ -0,0 +1,129 @@
+// 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.junit.Assert;
+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 MainDexPrunedReferenceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public MainDexPrunedReferenceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeFalse(parameters.getDexRuntimeVersion().isDalvik());
+ testMainDex(builder -> {}, Assert::assertNull);
+ }
+
+ @Test
+ public void testMainDexClassesList() throws Exception {
+ assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
+ testMainDex(
+ builder -> builder.addMainDexListClasses(Main.class),
+ mainDexClasses -> {
+ assertTrue(mainDexClasses.contains(Main.class.getTypeName()));
+ assertFalse(mainDexClasses.contains(Outside.class.getTypeName()));
+ });
+ }
+
+ @Test
+ public void testMainDexTracing() throws Exception {
+ assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
+ testMainDex(
+ builder ->
+ builder.addMainDexRules(
+ "-keep class " + Main.class.getTypeName() + " { public static void notMain(); }"),
+ mainDexClasses -> {
+ assertTrue(mainDexClasses.contains(Main.class.getTypeName()));
+ // TODO(b/178362682): This should be false.
+ assertTrue(mainDexClasses.contains(Outside.class.getTypeName()));
+ });
+ }
+
+ private void testMainDex(
+ ThrowableConsumer<R8FullTestBuilder> configureMainDex,
+ Consumer<Set<String>> mainDexListConsumer)
+ throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, Outside.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(Outside.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(configureMainDex)
+ .applyIf(
+ parameters.getDexRuntimeVersion().isDalvik(),
+ TestCompilerBuilder::collectMainDexClasses)
+ .compile()
+ .apply(compileResult -> compileResult.inspectMainDexClasses(mainDexListConsumer))
+ .run(parameters.getRuntime(), Main.class)
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+ ClassSubject outsideClassSubject = inspector.clazz(Outside.class);
+ assertThat(outsideClassSubject, isPresent());
+ })
+ .assertSuccessWithOutputLines("Hello World");
+ }
+
+ @NoHorizontalClassMerging
+ public static class Outside {
+
+ @NeverInline
+ public static void foo() {
+ System.out.println("Outside::foo");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class Main {
+
+ public static void main(String[] args) {
+ int val = 0;
+ if (val != 0) {
+ notMain();
+ }
+ System.out.println("Hello World");
+ }
+
+ // If we trace before second round of enqueueing, we do not observe notMain being pruned.
+ public static void notMain() {
+ Outside.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
index 6a5ff3e..aeb142f 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinification.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.naming;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
@@ -50,7 +51,7 @@
.addKeepMainRule(mainClass)
.addKeepRules("-neverinline enum * extends java.lang.Enum { valueOf(...); }")
.enableProguardTestOptions()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile();
}
@@ -64,11 +65,11 @@
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(enumTypeName);
- // The class and fields - including field $VALUES and method valueOf - can be renamed. Only
- // the values() method needs to be
+ // The class and fields - including the method valueOf - can be renamed. Only the values()
+ // method needs to be.
assertThat(clazz, isPresentAndRenamed());
- assertThat(clazz.uniqueFieldWithName("VALUE1"), isPresentAndRenamed());
- assertThat(clazz.uniqueFieldWithName("VALUE2"), isPresentAndRenamed());
+ assertThat(clazz.uniqueFieldWithName("VALUE1"), isAbsent());
+ assertThat(clazz.uniqueFieldWithName("VALUE2"), isAbsent());
assertThat(clazz.uniqueFieldWithName("$VALUES"), isPresentAndRenamed());
assertThat(
clazz.uniqueMethodWithName("valueOf"),
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index 8dd68fc..b96ff51 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.naming;
import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -16,9 +17,6 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,9 +74,6 @@
ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
assertThat(enumClass, isPresent());
assertEquals(minify, enumClass.isRenamed());
- MethodSubject clinit = enumClass.clinit();
- assertThat(clinit, isPresent());
- assertEquals(
- 0, Streams.stream(clinit.iterateInstructions(InstructionSubject::isThrow)).count());
+ assertThat(enumClass.clinit(), isAbsent());
}
}
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 3d143a1..7a1b6e4 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.kotlin.TestKotlinClass;
import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
-import com.android.tools.r8.shaking.NoStaticClassMergingRule;
import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
@@ -148,7 +147,6 @@
"-neverclassinline class **." + targetClassName,
"-" + NoVerticalClassMergingRule.RULE_NAME + " class **." + targetClassName,
"-" + NoHorizontalClassMergingRule.RULE_NAME + " class **." + targetClassName,
- "-" + NoStaticClassMergingRule.RULE_NAME + " class **." + targetClassName,
"-neverinline class **." + targetClassName + " { <methods>; }"))
.addDontWarnJetBrainsNotNullAnnotation()
.allowDiagnosticWarningMessages()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
index 36bf030..e01926d 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -111,7 +111,6 @@
options -> {
options.enableInlining = false;
options.enableVerticalClassMerging = false;
- options.enableStaticClassMerging = false;
options.enableClassInlining = false;
})
.compile();
diff --git a/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java b/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
index 66b2a18..a5a165b 100644
--- a/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.CompatProguardCommandBuilder;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
@@ -24,10 +24,10 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-@NoStaticClassMerging
+@NoHorizontalClassMerging
class Outer {
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
static class Inner {
@NeverInline
static void foo() {
@@ -80,7 +80,7 @@
// the visiting of classes during class minification to be Outer$Inner before Outer.
"-keepnames class " + Outer.class.getCanonicalName() + "$Inner",
keepOuterName ? "-keepnames class " + Outer.class.getCanonicalName() : "",
- noStaticClassMergingRule()),
+ noHorizontalClassMerging()),
Origin.unknown());
ToolHelper.allowTestProguardOptions(builder);
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
index 81de378..6a8a384 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.repackage.RepackageInnerAndOuterClassTest.Outer.Inner;
@@ -48,7 +47,7 @@
.apply(this::configureRepackaging)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(inspector -> inspect(inspector, eligibleForRepackaging))
@@ -70,7 +69,6 @@
}
@NoHorizontalClassMerging
- @NoStaticClassMerging
public static class Outer {
@NeverInline
@@ -78,7 +76,7 @@
System.out.print("Hello");
}
- @NoStaticClassMerging
+ @NoHorizontalClassMerging
public static class Inner {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
index da09e88..179845d 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -113,7 +113,7 @@
.apply(this::configureRepackaging)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
index 6ee9df6..e9f96c9 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
index 7a80bb3..535d72e 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect {
@NeverInline
@@ -18,7 +16,6 @@
}
@NoHorizontalClassMerging
- @NoStaticClassMerging
public static class Helper {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
index f8af76d..c19f477 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateKeptMethodOnReachableClassDirect {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
index 279d85b..5b3b011 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateKeptMethodOnReachableClassIndirect {
@NeverInline
@@ -18,7 +16,6 @@
}
@NoHorizontalClassMerging
- @NoStaticClassMerging
public static class Helper {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
index a333df7..29ce708 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
index 040dd72..efdeff6 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect {
@NeverInline
@@ -18,7 +16,6 @@
}
@NoHorizontalClassMerging
- @NoStaticClassMerging
public static class Helper {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
index a7bb32f..ef4fc49 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateMethodOnKeptClassDirect {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
index 7190c3f..355420d 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateMethodOnKeptClassIndirect {
@NeverInline
@@ -18,7 +16,6 @@
}
@NoHorizontalClassMerging
- @NoStaticClassMerging
public static class Helper {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
index 07872c6b..f77847d 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateMethodOnReachableClassDirect {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
index d528244..f68331a 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPackagePrivateMethodOnReachableClassIndirect {
@NeverInline
@@ -18,7 +16,6 @@
}
@NoHorizontalClassMerging
- @NoStaticClassMerging
public static class Helper {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
index d99a9b5..1ad561e 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPublicKeptMethodAllowRenamingOnReachableClass {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
index 8880ca5..c21813f 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPublicKeptMethodOnReachableClass {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
index d3fa44b..f492f35 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPublicMethodOnKeptClass {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
index 9578cc7..3571398 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPublicMethodOnKeptClassAllowRenaming {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
index ec21ae7..4fc936f 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class AccessPublicMethodOnReachableClass {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
index 22c5aa0..dcd099d 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
@@ -6,10 +6,8 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class ReachableClass {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
index 0f3f2cc..b32e476 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
@@ -3,14 +3,22 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.rewrite.switches;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -33,12 +41,51 @@
this.mode = mode;
}
- private void checkResult(TestRunResult<?> result) {
- if (parameters.getDexRuntimeVersion().isOlderThanOrEqual(Version.V4_0_4)) {
- result.assertFailureWithErrorThatThrows(AssertionError.class);
- } else {
- result.assertSuccessWithOutputLines("good");
- }
+ private void checkSwitch(InstructionSubject instruction, boolean hasMaxIntKey) {
+ assertEquals(hasMaxIntKey, instruction.asSwitch().getKeys().contains(0x7fffffff));
+ }
+
+ private void checkSwitch(MethodSubject method, boolean hasMaxIntKey) {
+ assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isSwitch));
+ method
+ .streamInstructions()
+ .filter(InstructionSubject::isSwitch)
+ .forEach(instruction -> checkSwitch(instruction, hasMaxIntKey));
+ }
+
+ private void checkStringSwitch(MethodSubject method, boolean hasMaxIntKey) {
+ // String switch will have two switches.
+ List<InstructionSubject> switchInstructions =
+ method
+ .streamInstructions()
+ .filter(InstructionSubject::isSwitch)
+ .collect(Collectors.toList());
+ assertEquals(2, switchInstructions.size());
+ checkSwitch(switchInstructions.get(0), hasMaxIntKey);
+ }
+
+ public void checkSwitchKeys(CodeInspector inspector) {
+ checkSwitch(
+ inspector.clazz(TestClass.class).uniqueMethodWithName("f"),
+ parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K));
+ checkSwitch(
+ inspector.clazz(TestClass.class).uniqueMethodWithName("g"),
+ parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K));
+ // Debug mode will not rewrite switch statements except when the MAX_INT key is present.
+ assertEquals(
+ inspector
+ .clazz(TestClass.class)
+ .uniqueMethodWithName("h")
+ .streamInstructions()
+ .filter(InstructionSubject::isSwitch)
+ .count(),
+ BooleanUtils.intValue(
+ mode == CompilationMode.DEBUG
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)));
+
+ checkStringSwitch(
+ inspector.clazz(TestClass.class).uniqueMethodWithName("s"),
+ parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K));
}
@Test
@@ -48,7 +95,8 @@
.setMinApi(parameters.getApiLevel())
.setMode(mode)
.run(parameters.getRuntime(), TestClass.class)
- .apply(this::checkResult);
+ .inspect(this::checkSwitchKeys)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@Test
@@ -61,9 +109,21 @@
.setMinApi(parameters.getApiLevel())
.setMode(mode)
.run(parameters.getRuntime(), TestClass.class)
- .apply(this::checkResult);
+ .inspect(this::checkSwitchKeys)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
}
+ static final String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ Integer.toString(0x7ffffffc),
+ Integer.toString(0x7ffffffd),
+ Integer.toString(0x7ffffffe),
+ Integer.toString(0x7fffffff),
+ "good",
+ "show",
+ "!",
+ "?");
+
static class TestClass {
@NeverInline
@NeverPropagateValue
@@ -83,8 +143,67 @@
}
}
+ @NeverInline
+ @NeverPropagateValue
+ public static void g(int i) {
+ switch (i) {
+ case 0x7ffffffc:
+ System.out.println("a");
+ break;
+ case 0x7ffffffd:
+ System.out.println("b");
+ break;
+ case 0x7ffffffe:
+ case 0x7fffffff:
+ System.out.println("show");
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ @NeverInline
+ @NeverPropagateValue
+ public static void h(int i) {
+ switch (i) {
+ case 0x7fffffff:
+ System.out.println("!");
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ @NeverInline
+ @NeverPropagateValue
+ public static void s(String s) {
+ switch (s) {
+ case "DAEFIDU":
+ System.out.println("a");
+ break;
+ case "DAEFIDV":
+ System.out.println("b");
+ break;
+ case "DAEFIDW":
+ System.out.println("c");
+ break;
+ case "DAEFIDX":
+ System.out.println("?");
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+
public static void main(String[] args) {
+ System.out.println("DAEFIDU".hashCode());
+ System.out.println("DAEFIDV".hashCode());
+ System.out.println("DAEFIDW".hashCode());
+ System.out.println("DAEFIDX".hashCode());
f(0x7fffffff);
+ g(0x7fffffff);
+ h(0x7fffffff);
+ s("DAEFIDX");
}
}
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
index 7987f96..4f53fe2 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -210,6 +210,7 @@
Statistics stat =
countInstructions(getMethodSubject(app, "Test", signature).iterateInstructions());
+ int expectedIfCount = 0;
int expectedPackedSwitchCount, expectedSparseSwitchCount;
if (keyStep <= 2) {
expectedPackedSwitchCount = 1;
@@ -218,8 +219,15 @@
expectedPackedSwitchCount = 0;
expectedSparseSwitchCount = 1;
}
+ if (backend == Backend.DEX
+ && additionalLastKey != null
+ && additionalLastKey == Integer.MAX_VALUE) {
+ expectedIfCount = 1;
+ }
- assertEquals(new Statistics(0, expectedPackedSwitchCount, expectedSparseSwitchCount), stat);
+ assertEquals(
+ new Statistics(expectedIfCount, expectedPackedSwitchCount, expectedSparseSwitchCount),
+ stat);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
index 37c3bc3..bcd4063 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -152,8 +152,6 @@
Consumer<InternalOptions> optionsConsumer =
options -> {
options.testing.enableDeadSwitchCaseElimination = false;
- options.verbose = true;
- options.printTimes = true;
};
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, optionsConsumer);
@@ -162,7 +160,12 @@
if (keyStep <= 2) {
assertTrue(code.instructions[0] instanceof PackedSwitch);
} else {
- assertTrue(code.instructions[0] instanceof SparseSwitch);
+ if (additionalLastKey != null && additionalLastKey == Integer.MAX_VALUE) {
+ assertTrue(code.instructions[0] instanceof Const);
+ assertTrue(code.instructions[1] instanceof IfEq);
+ } else {
+ assertTrue(code.instructions[0] instanceof SparseSwitch);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
index 3c15d88..3f27bc6 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -149,40 +150,18 @@
"p:",
" putstatic Main/sField I",
" return");
- MethodSignature mainMethod = main.addMainMethod(
- ".limit stack 2",
- ".limit locals 1",
- " getstatic Main/sField I",
- " return");
- ensureFieldExistsButNoRead(builder, main, mainMethod, main, "sField");
- }
+ main.addMainMethod(
+ ".limit stack 2", ".limit locals 1", " getstatic Main/sField I", " return");
- private void ensureFieldExistsButNoRead(
- JasminBuilder app,
- ClassBuilder clazz,
- MethodSignature method,
- ClassBuilder fieldHolder,
- String fieldName)
- throws Exception {
testForR8(parameters.getBackend())
- .addProgramClassFileData(app.buildClasses())
+ .addProgramClassFileData(builder.buildClasses())
.addKeepRules("-keep class * { <methods>; }")
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
- inspector -> {
- FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
- assertThat(fld, isPresentAndRenamed());
-
- ClassSubject classSubject = inspector.clazz(clazz.name);
- assertThat(classSubject, isPresent());
- MethodSubject methodSubject = classSubject.uniqueMethodWithName(method.name);
- assertThat(methodSubject, isPresent());
- Iterator<InstructionSubject> it =
- methodSubject.iterateInstructions(InstructionSubject::isFieldAccess);
- assertFalse(it.hasNext());
- });
+ inspector ->
+ assertThat(inspector.clazz(main.name).uniqueFieldWithName("sField"), isAbsent()));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 993a658..550356e 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -224,7 +224,7 @@
.addDontWarnJavaxNullableAnnotation()
.compile()
.graphInspector();
- assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, false, false);
+ assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector);
GraphInspector ifThenKeepClassesWithMembersInspector =
testForR8(Backend.CF)
@@ -243,8 +243,7 @@
.addDontWarnJavaxNullableAnnotation()
.compile()
.graphInspector();
- assertRetainedClassesEqual(
- referenceInspector, ifThenKeepClassesWithMembersInspector, false, false);
+ assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
GraphInspector ifHasMemberThenKeepClassInspector =
testForR8(Backend.CF)
@@ -265,8 +264,12 @@
.addDontWarnJavaxNullableAnnotation()
.compile()
.graphInspector();
- // TODO(b/159418523): Should the reference be equal to the result with the conditional rule?
- assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, true);
+ assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
+ }
+
+ private void assertRetainedClassesEqual(
+ GraphInspector referenceResult, GraphInspector conditionalResult) {
+ assertRetainedClassesEqual(referenceResult, conditionalResult, false, false);
}
private void assertRetainedClassesEqual(
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/A.java b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
index 1387558..23aa04e 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/A.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
@@ -5,10 +5,8 @@
package com.android.tools.r8.shaking.testrules;
import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
@NoHorizontalClassMerging
-@NoStaticClassMerging
public class A {
public static int m(int a, int b) {
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index b669c95..aaafe0c 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -44,7 +44,7 @@
.addKeepRules(proguardConfiguration)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
- .enableNoStaticClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableProguardTestOptions()
.compile()
.inspector();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index a42fa4b..cb09a17 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -21,11 +22,6 @@
}
@Override
- public boolean isFinal() {
- throw new Unreachable("Cannot determine if an absent class is final");
- }
-
- @Override
public boolean isPresent() {
return false;
}
@@ -91,11 +87,6 @@
}
@Override
- public boolean isPublic() {
- throw new Unreachable("Cannot determine if an absent class is public");
- }
-
- @Override
public boolean isImplementing(ClassSubject subject) {
throw new Unreachable("Cannot determine if an absent class is implementing a given interface");
}
@@ -121,6 +112,11 @@
}
@Override
+ public AccessFlags<?> getAccessFlags() {
+ throw new Unreachable("Absent class has no access flags");
+ }
+
+ @Override
public String getOriginalName() {
return reference.getTypeName();
}
@@ -181,11 +177,6 @@
}
@Override
- public boolean isSynthetic() {
- throw new Unreachable("Cannot determine if an absent class is synthetic");
- }
-
- @Override
public boolean isSynthesizedJavaLambdaClass() {
throw new Unreachable("Cannot determine if an absent class is a synthesized lambda class");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
index f41135f..ceafd09 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.naming.MemberNaming.Signature;
@@ -12,36 +13,6 @@
public class AbsentFieldSubject extends FieldSubject {
@Override
- public boolean isPublic() {
- throw new Unreachable("Cannot determine if an absent field is public");
- }
-
- @Override
- public boolean isProtected() {
- throw new Unreachable("Cannot determine if an absent field is protected");
- }
-
- @Override
- public boolean isPrivate() {
- throw new Unreachable("Cannot determine if an absent field is private");
- }
-
- @Override
- public boolean isPackagePrivate() {
- throw new Unreachable("Cannot determine if an absent field is package-private");
- }
-
- @Override
- public boolean isStatic() {
- throw new Unreachable("Cannot determine if an absent field is static");
- }
-
- @Override
- public boolean isFinal() {
- throw new Unreachable("Cannot determine if an absent field is final");
- }
-
- @Override
public boolean isPresent() {
return false;
}
@@ -52,11 +23,6 @@
}
@Override
- public boolean isSynthetic() {
- throw new Unreachable("Cannot determine if an absent field is synthetic");
- }
-
- @Override
public Signature getOriginalSignature() {
return null;
}
@@ -100,4 +66,9 @@
public String getJvmFieldSignatureAsString() {
return null;
}
+
+ @Override
+ public AccessFlags<?> getAccessFlags() {
+ throw new Unreachable("Absent field has no access flags");
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index 37f26db..ec133d7 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -30,41 +30,6 @@
}
@Override
- public boolean isPublic() {
- throw new Unreachable("Cannot determine if an absent method is public");
- }
-
- @Override
- public boolean isProtected() {
- throw new Unreachable("Cannot determine if an absent method is protected");
- }
-
- @Override
- public boolean isPrivate() {
- throw new Unreachable("Cannot determine if an absent method is private");
- }
-
- @Override
- public boolean isPackagePrivate() {
- throw new Unreachable("Cannot determine if an absent method is package-private");
- }
-
- @Override
- public boolean isStatic() {
- throw new Unreachable("Cannot determine if an absent method is static");
- }
-
- @Override
- public boolean isSynthetic() {
- throw new Unreachable("Cannot determine if an absent method is synthetic");
- }
-
- @Override
- public boolean isFinal() {
- throw new Unreachable("Cannot determine if an absent method is final");
- }
-
- @Override
public boolean isAbstract() {
throw new Unreachable("Cannot determine if an absent method is abstract");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
index 14666f1..d86cc6c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
@@ -4,7 +4,39 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.graph.AccessFlags;
+
public abstract class ClassOrMemberSubject extends Subject {
- public abstract boolean isFinal();
+ public abstract AccessFlags<?> getAccessFlags();
+
+ public abstract String getOriginalName();
+
+ public final boolean isFinal() {
+ return getAccessFlags().isFinal();
+ }
+
+ public final boolean isPackagePrivate() {
+ return getAccessFlags().isPackagePrivate();
+ }
+
+ public final boolean isPrivate() {
+ return getAccessFlags().isPrivate();
+ }
+
+ public final boolean isProtected() {
+ return getAccessFlags().isProtected();
+ }
+
+ public final boolean isPublic() {
+ return getAccessFlags().isPublic();
+ }
+
+ public final boolean isStatic() {
+ return getAccessFlags().isStatic();
+ }
+
+ public final boolean isSynthetic() {
+ return getAccessFlags().isSynthetic();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 20d7466..cec4e2b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -165,8 +165,6 @@
public abstract boolean isAnnotation();
- public abstract boolean isPublic();
-
public abstract boolean isImplementing(ClassSubject subject);
public abstract boolean isImplementing(Class<?> clazz);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index d70775f..5f67ef0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.graph.DexCode.TryHandler;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.SwitchPayloadResolver;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -330,7 +331,8 @@
return originalTypeName != null ? originalTypeName : minifiedTypeName;
}
- InstructionSubject createInstructionSubject(Instruction instruction, MethodSubject method) {
+ InstructionSubject createInstructionSubject(
+ Instruction instruction, MethodSubject method, SwitchPayloadResolver switchPayloadResolver) {
DexInstructionSubject dexInst = new DexInstructionSubject(instruction, method);
if (dexInst.isInvoke()) {
return new InvokeDexInstructionSubject(this, instruction, method);
@@ -342,6 +344,8 @@
return new ConstStringDexInstructionSubject(instruction, method);
} else if (dexInst.isCheckCast()) {
return new CheckCastDexInstructionSubject(instruction, method);
+ } else if (dexInst.isSwitch()) {
+ return new SwitchDexInstructionSubject(instruction, method, switchPayloadResolver);
} else {
return dexInst;
}
@@ -359,6 +363,8 @@
return new ConstStringCfInstructionSubject(instruction, method);
} else if (cfInst.isCheckCast()) {
return new CheckCastCfInstructionSubject(instruction, method);
+ } else if (cfInst.isSwitch()) {
+ return new SwitchCfInstructionSubject(instruction, method);
} else {
return cfInst;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
index 0d98b29..1ea464c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.SwitchPayload;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.conversion.SwitchPayloadResolver;
import java.util.NoSuchElementException;
class DexInstructionIterator implements InstructionIterator {
@@ -13,6 +16,7 @@
private final CodeInspector codeInspector;
private final DexCode code;
private final MethodSubject methodSubject;
+ private SwitchPayloadResolver switchPayloadResolver;
private int index;
DexInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
@@ -35,6 +39,24 @@
if (index == code.instructions.length) {
throw new NoSuchElementException();
}
- return codeInspector.createInstructionSubject(code.instructions[index++], methodSubject);
+ if (code.instructions[index].isIntSwitch()) {
+ ensureSwitchPayloadResolver();
+ }
+ return codeInspector.createInstructionSubject(
+ code.instructions[index++], methodSubject, switchPayloadResolver);
+ }
+
+ private void ensureSwitchPayloadResolver() {
+ if (switchPayloadResolver == null) {
+ switchPayloadResolver = new SwitchPayloadResolver();
+ for (Instruction instruction : code.instructions) {
+ if (instruction.isIntSwitch()) {
+ switchPayloadResolver.addPayloadUser(instruction);
+ }
+ if (instruction.isSwitchPayload()) {
+ switchPayloadResolver.resolve((SwitchPayload) instruction);
+ }
+ }
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 4220b50..0bfe560 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -64,21 +65,11 @@
}
@Override
- public boolean isFinal() {
- return dexClass.isFinal();
- }
-
- @Override
public boolean isPresent() {
return true;
}
@Override
- public boolean isSynthetic() {
- return dexClass.accessFlags.isSynthetic();
- }
-
- @Override
public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
CodeInspector.forAll(
dexClass.directMethods(),
@@ -296,11 +287,6 @@
}
@Override
- public boolean isPublic() {
- return dexClass.accessFlags.isPublic();
- }
-
- @Override
public boolean isImplementing(ClassSubject subject) {
assertTrue(subject.isPresent());
for (DexType itf : getDexProgramClass().interfaces) {
@@ -363,6 +349,11 @@
}
@Override
+ public AccessFlags<?> getAccessFlags() {
+ return getDexProgramClass().getAccessFlags();
+ }
+
+ @Override
public String getOriginalName() {
if (naming != null) {
return naming.originalName;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 4504213..b071916 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
@@ -32,36 +33,6 @@
}
@Override
- public boolean isPublic() {
- return dexField.accessFlags.isPublic();
- }
-
- @Override
- public boolean isProtected() {
- return dexField.accessFlags.isProtected();
- }
-
- @Override
- public boolean isPrivate() {
- return dexField.accessFlags.isPrivate();
- }
-
- @Override
- public boolean isPackagePrivate() {
- return !isPublic() && !isProtected() && !isPrivate();
- }
-
- @Override
- public boolean isStatic() {
- return dexField.accessFlags.isStatic();
- }
-
- @Override
- public boolean isFinal() {
- return dexField.accessFlags.isFinal();
- }
-
- @Override
public boolean isPresent() {
return true;
}
@@ -71,11 +42,6 @@
return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
}
- @Override
- public boolean isSynthetic() {
- return dexField.accessFlags.isSynthetic();
- }
-
public TypeSubject type() {
return new TypeSubject(codeInspector, dexField.field.type);
}
@@ -159,4 +125,9 @@
public String getJvmFieldSignatureAsString() {
return dexField.field.name.toString() + ":" + dexField.field.type.toDescriptorString();
}
+
+ @Override
+ public AccessFlags<?> getAccessFlags() {
+ return getField().getAccessFlags();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index c2ae3ab..17fffc3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -78,41 +78,6 @@
}
@Override
- public boolean isPublic() {
- return dexMethod.accessFlags.isPublic();
- }
-
- @Override
- public boolean isProtected() {
- return dexMethod.accessFlags.isProtected();
- }
-
- @Override
- public boolean isPrivate() {
- return dexMethod.accessFlags.isPrivate();
- }
-
- @Override
- public boolean isPackagePrivate() {
- return !isPublic() && !isProtected() && !isPrivate();
- }
-
- @Override
- public boolean isStatic() {
- return dexMethod.accessFlags.isStatic();
- }
-
- @Override
- public boolean isSynthetic() {
- return dexMethod.accessFlags.isSynthetic();
- }
-
- @Override
- public boolean isFinal() {
- return dexMethod.accessFlags.isFinal();
- }
-
- @Override
public boolean isAbstract() {
return dexMethod.accessFlags.isAbstract();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index 7cfc3fe..e90df17 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -73,6 +75,17 @@
return this;
}
+ public HorizontallyMergedClassesInspector assertClassesNotMerged(Collection<Class<?>> classes) {
+ for (Class<?> clazz : classes) {
+ assertClassNotMerged(clazz);
+ }
+ return this;
+ }
+
+ public HorizontallyMergedClassesInspector assertClassesNotMerged(Class<?>... classes) {
+ return assertClassesNotMerged(Arrays.asList(classes));
+ }
+
public HorizontallyMergedClassesInspector assertClassNotMergedIntoDifferentType(Class<?> clazz) {
assertFalse(
horizontallyMergedClasses.hasBeenMergedIntoDifferentType(toDexType(clazz, dexItemFactory)));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 8b34260..959a2c9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -109,6 +109,10 @@
boolean isSwitch();
+ default SwitchInstructionSubject asSwitch() {
+ return null;
+ }
+
boolean isPackedSwitch();
boolean isSparseSwitch();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index c2a8cc8..28f0b9c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -304,11 +304,11 @@
return hasVisibility(Visibility.PACKAGE_PRIVATE);
}
- public static <T extends MemberSubject> Matcher<T> isPublic() {
+ public static <T extends ClassOrMemberSubject> Matcher<T> isPublic() {
return hasVisibility(Visibility.PUBLIC);
}
- private static <T extends MemberSubject> Matcher<T> hasVisibility(Visibility visibility) {
+ private static <T extends ClassOrMemberSubject> Matcher<T> hasVisibility(Visibility visibility) {
return new TypeSafeMatcher<T>() {
@Override
public boolean matchesSafely(final T subject) {
@@ -340,15 +340,9 @@
@Override
public void describeMismatchSafely(final T subject, Description description) {
- description
- .appendText("method ")
- .appendValue(subject.getOriginalName())
- .appendText(" was ");
+ description.appendText("item ").appendValue(subject.getOriginalName()).appendText(" was ");
if (subject.isPresent()) {
- AccessFlags<?> accessFlags =
- subject.isMethodSubject()
- ? subject.asMethodSubject().getMethod().accessFlags
- : subject.asFieldSubject().getField().accessFlags;
+ AccessFlags<?> accessFlags = subject.getAccessFlags();
if (accessFlags.isPublic()) {
description.appendText("public");
} else if (accessFlags.isProtected()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index 847675f..d8fd4b9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -8,16 +8,6 @@
public abstract class MemberSubject extends ClassOrMemberSubject {
- public abstract boolean isPublic();
-
- public abstract boolean isProtected();
-
- public abstract boolean isPrivate();
-
- public abstract boolean isPackagePrivate();
-
- public abstract boolean isStatic();
-
public abstract Signature getOriginalSignature();
public abstract Signature getFinalSignature();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 4826876..536fa58 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -38,6 +38,7 @@
return null;
}
+ @Override
public abstract MethodAccessFlags getAccessFlags();
@Override
@@ -45,9 +46,6 @@
public abstract String getOriginalSignatureAttribute();
- @Override
- public abstract String getFinalSignatureAttribute();
-
public abstract DexEncodedMethod getMethod();
public abstract ProgramMethod getProgramMethod();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java
deleted file mode 100644
index 0b45f27..0000000
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.utils.codeinspector;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
-import java.util.Set;
-import java.util.function.BiConsumer;
-
-public class StaticallyMergedClassesInspector {
-
- private final DexItemFactory dexItemFactory;
- private final StaticallyMergedClasses staticallyMergedClasses;
-
- public StaticallyMergedClassesInspector(
- DexItemFactory dexItemFactory, StaticallyMergedClasses staticallyMergedClasses) {
- this.dexItemFactory = dexItemFactory;
- this.staticallyMergedClasses = staticallyMergedClasses;
- }
-
- public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) {
- staticallyMergedClasses.forEachMergeGroup(consumer);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchCfInstructionSubject.java
new file mode 100644
index 0000000..4b5cbc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchCfInstructionSubject.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfSwitch;
+import java.util.List;
+
+public class SwitchCfInstructionSubject extends CfInstructionSubject
+ implements SwitchInstructionSubject {
+ public SwitchCfInstructionSubject(CfInstruction instruction, MethodSubject method) {
+ super(instruction, method);
+ assert isSwitch();
+ }
+
+ @Override
+ public List<Integer> getKeys() {
+ return ((CfSwitch) instruction).getKeys();
+ }
+
+ @Override
+ public SwitchInstructionSubject asSwitch() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchDexInstructionSubject.java
new file mode 100644
index 0000000..a49553f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchDexInstructionSubject.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.SparseSwitch;
+import com.android.tools.r8.ir.conversion.SwitchPayloadResolver;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import java.util.List;
+
+public class SwitchDexInstructionSubject extends DexInstructionSubject
+ implements SwitchInstructionSubject {
+
+ private final SwitchPayloadResolver switchPayloadResolver;
+
+ public SwitchDexInstructionSubject(
+ Instruction instruction, MethodSubject method, SwitchPayloadResolver switchPayloadResolver) {
+ super(instruction, method);
+ assert isSwitch();
+ assert instruction.isIntSwitch();
+ assert switchPayloadResolver != null;
+ this.switchPayloadResolver = switchPayloadResolver;
+ }
+
+ @Override
+ public List<Integer> getKeys() {
+ if (instruction instanceof PackedSwitch) {
+ assert switchPayloadResolver.getKeys(instruction.getOffset() + instruction.getPayloadOffset())
+ .length
+ == 1;
+ int firstKey =
+ switchPayloadResolver
+ .getKeys(instruction.getOffset() + instruction.getPayloadOffset())[0];
+ int numberOfKeys =
+ switchPayloadResolver.absoluteTargets(
+ instruction.getOffset() + instruction.getPayloadOffset())
+ .length;
+ List<Integer> keys = new IntArrayList(numberOfKeys);
+ for (int i = 0; i < numberOfKeys; i++) {
+ keys.add(firstKey + i);
+ }
+ return keys;
+ } else {
+ assert instruction instanceof SparseSwitch;
+ return new IntArrayList(
+ switchPayloadResolver.getKeys(instruction.getOffset() + instruction.getPayloadOffset()));
+ }
+ }
+
+ @Override
+ public SwitchInstructionSubject asSwitch() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchInstructionSubject.java
new file mode 100644
index 0000000..c04b4f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchInstructionSubject.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import java.util.List;
+
+public interface SwitchInstructionSubject extends InstructionSubject {
+ List<Integer> getKeys();
+}
diff --git a/third_party/chrome/clank_google3_prebuilt.tar.gz.sha1 b/third_party/chrome/clank_google3_prebuilt.tar.gz.sha1
new file mode 100644
index 0000000..862139a
--- /dev/null
+++ b/third_party/chrome/clank_google3_prebuilt.tar.gz.sha1
@@ -0,0 +1 @@
+4af48dbfdd1333bb41c716a62a136dfd1fa2354d
\ No newline at end of file
diff --git a/tools/gradle.py b/tools/gradle.py
index b6f5187..63c92b5 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -26,6 +26,9 @@
def ParseOptions():
parser = argparse.ArgumentParser(description = 'Call gradle.')
+ parser.add_argument('--exclude-deps', '--exclude_deps',
+ help='Build without internalized dependencies.',
+ default=False, action='store_true')
parser.add_argument('--no-internal', '--no_internal',
help='Do not build with support for Google internal tests.',
default=False, action='store_true')
@@ -107,6 +110,8 @@
args.append('-Dorg.gradle.java.home=' + options.java_home)
if options.no_internal:
args.append('-Pno_internal')
+ if options.exclude_deps:
+ args.append('-Pexclude_deps')
if options.worktree:
args.append('-g=' + os.path.join(utils.REPO_ROOT, ".gradle_user_home"))
return RunGradle(args)