Merge commit '90e4860801ffe94a6e91587747fe7105a4cd4740' into dev-release
diff --git a/.gitignore b/.gitignore
index b929f34..b03bb7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,8 @@
tests/2016-12-19/art.tar.gz
tests/2017-10-04/art
tests/2017-10-04/art.tar.gz
+third_party/api-outlining/simple-app-dump
+third_party/api-outlining/simple-app-dump.tar.gz
third_party/android_cts_baseline
third_party/android_cts_baseline.tar.gz
third_party/clank/clank_google3_prebuilt
diff --git a/build.gradle b/build.gradle
index 8e20e6d..3101886 100644
--- a/build.gradle
+++ b/build.gradle
@@ -315,6 +315,7 @@
"android_jar/lib-v29",
"android_jar/lib-v30",
"android_jar/lib-v31",
+ "api-outlining/simple-app-dump",
"core-lambda-stubs",
"dart-sdk",
"ddmlib",
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 81a8f82..2fadb7d 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import static com.android.tools.r8.D8Command.USAGE_MESSAGE;
+import static com.android.tools.r8.utils.AssertionUtils.forTesting;
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
import com.android.tools.r8.dex.ApplicationReader;
@@ -186,6 +187,9 @@
// Disable global optimizations.
options.disableGlobalOptimizations();
+ // Synthetic assertion to check that testing assertions works and can be enabled.
+ assert forTesting(options, () -> !options.testing.testEnableTestAssertions);
+
AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
SyntheticItems.collectSyntheticInputs(appView);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index f1ed0cd..8863e35 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.inspector.Inspector;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
@@ -24,6 +25,7 @@
import com.android.tools.r8.utils.DumpInputFlags;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -473,7 +475,6 @@
// Assert some of R8 optimizations are disabled.
assert !internal.enableInlining;
assert !internal.enableClassInlining;
- assert internal.horizontalClassMergerOptions().isDisabled();
assert !internal.enableVerticalClassMerging;
assert !internal.enableClassStaticizer;
assert !internal.enableEnumValueOptimization;
@@ -481,6 +482,13 @@
assert !internal.enableValuePropagation;
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
+ // TODO(b/187675788): Enable class merging for synthetics in D8.
+ HorizontalClassMergerOptions horizontalClassMergerOptions =
+ internal.horizontalClassMergerOptions();
+ horizontalClassMergerOptions.disable();
+ assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL);
+ assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL);
+
internal.desugarState = getDesugarState();
internal.encodeChecksums = getIncludeClassesChecksum();
internal.dexClassChecksumFilter = getDexClassChecksumFilter();
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index ec9955d..fc9a567 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.inspector.Inspector;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.origin.Origin;
@@ -18,6 +19,7 @@
import com.android.tools.r8.utils.DumpInputFlags;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -172,7 +174,6 @@
// Assert some of R8 optimizations are disabled.
assert !internal.enableInlining;
assert !internal.enableClassInlining;
- assert internal.horizontalClassMergerOptions().isDisabled();
assert !internal.enableVerticalClassMerging;
assert !internal.enableClassStaticizer;
assert !internal.enableEnumValueOptimization;
@@ -180,6 +181,12 @@
assert !internal.enableValuePropagation;
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
+ HorizontalClassMergerOptions horizontalClassMergerOptions =
+ internal.horizontalClassMergerOptions();
+ horizontalClassMergerOptions.disable();
+ assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL);
+ assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL);
+
assert internal.desugarState == DesugarState.ON;
assert internal.enableInheritanceClassInDexDistributor;
internal.enableInheritanceClassInDexDistributor = false;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e58264e..7aed2af 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import static com.android.tools.r8.R8Command.USAGE_MESSAGE;
+import static com.android.tools.r8.utils.AssertionUtils.forTesting;
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
import com.android.tools.r8.cf.code.CfInstruction;
@@ -41,8 +42,6 @@
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerResult;
-import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
@@ -282,6 +281,8 @@
new StringDiagnostic(
"Running R8 version " + Version.LABEL + " with assertions enabled."));
}
+ // Synthetic assertion to check that testing assertions works and can be enabled.
+ assert forTesting(options, () -> !options.testing.testEnableTestAssertions);
try {
AppView<AppInfoWithClassHierarchy> appView;
{
@@ -323,7 +324,7 @@
List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
timing.begin("Strip unused code");
RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
- new RuntimeTypeCheckInfo.Builder(appView.dexItemFactory());
+ new RuntimeTypeCheckInfo.Builder(appView);
try {
// Add synthesized -assumenosideeffects from min api if relevant.
if (options.isGeneratingDex()) {
@@ -453,7 +454,10 @@
boolean isKotlinLibraryCompilationWithInlinePassThrough =
options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods();
- RuntimeTypeCheckInfo runtimeTypeCheckInfo = classMergingEnqueuerExtensionBuilder.build();
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo =
+ classMergingEnqueuerExtensionBuilder.build(appView.graphLens());
+ classMergingEnqueuerExtensionBuilder = null;
+
if (!isKotlinLibraryCompilationWithInlinePassThrough
&& options.getProguardConfiguration().isOptimizing()) {
if (options.enableVerticalClassMerging) {
@@ -499,33 +503,9 @@
timing.end();
}
}
- if (options.horizontalClassMergerOptions().isEnabled()
- && options.enableInlining
- && options.isShrinking()) {
- timing.begin("HorizontalClassMerger");
- HorizontalClassMerger merger = new HorizontalClassMerger(appViewWithLiveness);
- HorizontalClassMergerResult horizontalClassMergerResult =
- merger.run(runtimeTypeCheckInfo, timing);
- if (horizontalClassMergerResult != null) {
- // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that
- // allocations sites, fields accesses, etc. are correctly transferred to the target
- // classes.
- appView.rewriteWithLens(horizontalClassMergerResult.getGraphLens());
- horizontalClassMergerResult
- .getFieldAccessInfoCollectionModifier()
- .modify(appViewWithLiveness);
- appView.pruneItems(
- PrunedItems.builder()
- .setPrunedApp(appView.appInfo().app())
- .addRemovedClasses(appView.horizontallyMergedClasses().getSources())
- .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getSources())
- .build());
- }
- timing.end();
- } else {
- appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
- }
+ HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
+ .runIfNecessary(runtimeTypeCheckInfo, timing);
}
// Clear traced methods roots to not hold on to the main dex live method set.
@@ -596,6 +576,10 @@
new SubtypingInfo(appView),
keptGraphConsumer,
prunedTypes);
+ if (options.isClassMergingExtensionRequired(enqueuer.getMode())) {
+ classMergingEnqueuerExtensionBuilder = new RuntimeTypeCheckInfo.Builder(appView);
+ classMergingEnqueuerExtensionBuilder.attach(enqueuer);
+ }
EnqueuerResult enqueuerResult =
enqueuer.traceApplication(appView.rootSet(), executorService, timing);
appView.setAppInfo(enqueuerResult.getAppInfo());
@@ -730,6 +714,15 @@
SyntheticFinalization.finalizeWithClassHierarchy(appView);
}
+ // Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
+ // are always merged.
+ HorizontalClassMerger.createForFinalClassMerging(appView)
+ .runIfNecessary(
+ classMergingEnqueuerExtensionBuilder != null
+ ? classMergingEnqueuerExtensionBuilder.build(appView.graphLens())
+ : null,
+ timing);
+
// Perform minification.
NamingLens namingLens;
if (options.getProguardConfiguration().hasApplyMappingFile()) {
@@ -808,7 +801,6 @@
.runIfNecessary(timing);
// Generate the resulting application resources.
- // TODO(b/165783399): Apply the graph lens to all instructions in the CF and DEX backends.
writeApplication(
executorService,
appView,
@@ -968,7 +960,7 @@
appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance()));
}
- if (options.isClassMergingExtensionRequired()) {
+ if (options.isClassMergingExtensionRequired(enqueuer.getMode())) {
classMergingEnqueuerExtensionBuilder.attach(enqueuer);
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index e3de56c..4fdb44d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -32,6 +32,7 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -854,8 +855,11 @@
? LineNumberOptimization.ON
: LineNumberOptimization.OFF;
+ HorizontalClassMergerOptions horizontalClassMergerOptions =
+ internal.horizontalClassMergerOptions();
assert proguardConfiguration.isOptimizing()
- || internal.horizontalClassMergerOptions().isDisabled();
+ || horizontalClassMergerOptions.isRestrictedToSynthetics();
+
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
if (internal.debug) {
@@ -864,26 +868,19 @@
internal.getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true;
internal.enableInlining = false;
internal.enableClassInlining = false;
- internal.horizontalClassMergerOptions().disable();
internal.enableVerticalClassMerging = false;
internal.enableClassStaticizer = false;
internal.outline.enabled = false;
internal.enableEnumUnboxing = false;
}
+
if (!internal.isShrinking()) {
// If R8 is not shrinking, there is no point in running various optimizations since the
// optimized classes will still remain in the program (the application size could increase).
internal.enableEnumUnboxing = false;
- internal.horizontalClassMergerOptions().disable();
internal.enableVerticalClassMerging = false;
}
- if (!internal.enableInlining) {
- // If R8 cannot perform inlining, then the synthetic constructors would not inline the called
- // constructors, producing invalid code.
- internal.horizontalClassMergerOptions().disable();
- }
-
// Amend the proguard-map consumer with options from the proguard configuration.
internal.proguardMapConsumer =
wrapStringConsumer(
@@ -939,7 +936,7 @@
if (internal.isGeneratingClassFiles()) {
internal.outline.enabled = false;
internal.enableEnumUnboxing = false;
- internal.horizontalClassMergerOptions().disable();
+ horizontalClassMergerOptions.disable();
}
// EXPERIMENTAL flags.
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index e102cf7..bf66099 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -194,7 +195,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forBinop();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 4fa3241..21e74a3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -69,7 +70,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forArrayLength();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 4f851a0..3385529 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -115,7 +116,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forArrayGet();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index cc0cd63..e4350e0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -105,7 +106,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forArrayPut();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 1e25ca7..5ed4009 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -105,7 +106,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forCheckCast(type, context);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 9572e12..e1a5ce5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -119,7 +120,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forBinop();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index a6a0933..b7524be 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -128,7 +129,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forConstClass(type, context);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 16f5e9a..7391677 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -90,7 +91,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forConstMethodHandle();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 5bb9849..e85db92 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -88,7 +89,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forConstMethodType();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 185981e..f04dcfe 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -61,7 +62,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forConstInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 7484e80..759568d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -164,7 +165,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forConstInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 376edfe..9a079e7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
@@ -91,7 +92,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forConstInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 75c5eda..924fa7e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -110,7 +111,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forDexItemBasedConstString(item, context);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 2e8eedf..592ee31 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexField;
@@ -165,7 +166,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
switch (opcode) {
case Opcodes.GETSTATIC:
return inliningConstraints.forStaticGet(field, context);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index e55bf9a..fbeda56 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
@@ -466,7 +467,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return ConstraintWithTarget.ALWAYS;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index b3043c9..6c434c4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -86,7 +87,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forJumpInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index 7653d51..a3ad1cc 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -122,7 +123,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forJumpInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 3e5c38a..e42d7b2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -123,7 +124,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forJumpInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 933dfcd..ea9e810 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -83,7 +84,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return ConstraintWithTarget.ALWAYS;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index 0b8240a..8eb0db7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexField;
@@ -103,7 +104,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forInitClass(clazz, context);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index b4d6d43..8eab765 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -113,7 +114,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forInstanceOf(type, context);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index a80dc16..6067cc3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.code.CfOrDexInstruction;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.ClasspathMethod;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -282,7 +283,7 @@
}
public abstract ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context);
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context);
public abstract void evaluate(
CfFrameVerificationHelper frameBuilder,
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index b19a183..7b6c2e7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -33,6 +34,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.StructuralSpecification;
import java.util.Arrays;
@@ -239,13 +241,9 @@
// Using invoke-super should therefore observe the correct semantics since we cannot
// target less specific targets (up in the hierarchy).
canonicalMethod = method;
- if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
- type = Type.DIRECT;
- } else if (code.getOriginalHolder() == method.holder) {
- type = invokeTypeForInvokeSpecialToNonInitMethodOnHolder(builder.appView, code);
- } else {
- type = Type.SUPER;
- }
+ type =
+ computeInvokeTypeForInvokeSpecial(
+ builder.appView, method, code.getOriginalHolder(), code.getOrigin());
break;
}
case Opcodes.INVOKESTATIC:
@@ -277,7 +275,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
GraphLens graphLens = inliningConstraints.getGraphLens();
AppView<?> appView = inliningConstraints.getAppView();
DexMethod target = method;
@@ -292,23 +290,11 @@
break;
case Opcodes.INVOKESPECIAL:
- if (appView.dexItemFactory().isConstructor(target)) {
- type = Type.DIRECT;
- assert noNeedToUseGraphLens(target, context.getReference(), type, graphLens);
- } else if (target.holder == context.getHolderType()) {
- // The method could have been publicized.
- type = graphLens.lookupMethod(target, context.getReference(), Type.DIRECT).getType();
- assert type == Type.DIRECT || type == Type.VIRTUAL;
- } else {
- // This is a super call. Note that the vertical class merger translates some invoke-super
- // instructions to invoke-direct. However, when that happens, the invoke instruction and
- // the target method end up being in the same class, and therefore, we will allow inlining
- // it. The result of using type=SUPER below will be the same, since it leads to the
- // inlining constraint SAMECLASS.
- // TODO(christofferqa): Consider using graphLens.lookupMethod (to do this, we need the
- // context for the graph lens, though).
- type = Type.SUPER;
- assert noNeedToUseGraphLens(target, context.getReference(), type, graphLens);
+ {
+ Type actualInvokeType =
+ computeInvokeTypeForInvokeSpecial(
+ appView, method, code.getOriginalHolder(), context.getOrigin());
+ type = graphLens.lookupMethod(target, context.getReference(), actualInvokeType).getType();
}
break;
@@ -371,14 +357,19 @@
}
}
- private static boolean noNeedToUseGraphLens(
- DexMethod method, DexMethod context, Invoke.Type type, GraphLens graphLens) {
- assert graphLens.lookupMethod(method, context, type).getType() == type;
- return true;
+ private Type computeInvokeTypeForInvokeSpecial(
+ AppView<?> appView, DexMethod method, DexType originalHolder, Origin origin) {
+ if (appView.dexItemFactory().isConstructor(method)) {
+ return Type.DIRECT;
+ }
+ if (originalHolder == method.holder) {
+ return invokeTypeForInvokeSpecialToNonInitMethodOnHolder(appView, origin);
+ }
+ return Type.SUPER;
}
private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
- AppView<?> appView, CfSourceCode code) {
+ AppView<?> appView, Origin origin) {
boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
MethodLookupResult lookupResult = appView.graphLens().lookupMethod(method, method, Type.DIRECT);
if (lookupResult.getType() == Type.VIRTUAL) {
@@ -408,8 +399,7 @@
}
// We cannot emulate the semantics of invoke-special in this case and should throw a compilation
// error.
- throw new CompilationError(
- "Failed to compile unsupported use of invokespecial", code.getOrigin());
+ throw new CompilationError("Failed to compile unsupported use of invokespecial", origin);
}
private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 3a98a8e..3138b27 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -154,7 +155,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forInvokeCustom();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
index 812a17c..0bff416 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
@@ -72,7 +73,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
throw error();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 5a3e4e7..028eff7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -84,7 +85,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return ConstraintWithTarget.ALWAYS;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 96a6d11..7aaf08c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -113,7 +114,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forLoad();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 19e5dd9..8a38898 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -166,7 +167,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forBinop();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index 3286da2..809317f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -83,7 +84,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forMonitor();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 682c857..e83b18d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -116,7 +117,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forInvokeMultiNewArray(type, context);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index 5de3787..e9a6518 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -108,7 +109,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forUnop();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 53fc40c..0fe8c96 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -102,7 +103,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forNewInstance(type, context);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index ad50c17..559f3ba 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -152,7 +153,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forNewArrayEmpty(type, context);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 974d928..c7be975 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -65,7 +66,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return ConstraintWithTarget.ALWAYS;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index dc1bc2c..9e656f7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -179,7 +180,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forUnop();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index f524bab..45077e2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -100,7 +101,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return ConstraintWithTarget.ALWAYS;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 2b4eed5..9a74eaf 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -101,7 +102,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forReturn();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index 08de455..84a524d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -75,7 +76,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forReturn();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 4fd9829..911141c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -342,7 +343,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return ConstraintWithTarget.ALWAYS;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index b87c87c..9177bda 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -115,7 +116,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forStore();
}
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 b84fc14..aef40c6 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
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -136,7 +137,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forJumpInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index 90d25c5..b562faf 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -82,7 +83,7 @@
@Override
public ConstraintWithTarget inliningConstraint(
- InliningConstraints inliningConstraints, ProgramMethod context) {
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
return inliningConstraints.forJumpInstruction();
}
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 26bbc5e..e2b7f93 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -819,7 +819,8 @@
directMethods,
virtualMethods,
dexItemFactory.getSkipNameValidationForTesting(),
- checksumSupplier);
+ checksumSupplier,
+ null);
classCollection.accept(clazz); // Update the application object.
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index 2cfb4d9..7fedd7c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -40,11 +40,15 @@
private final AppView<?> appView;
+ // The graph lens that was previously used to rewrite this instance.
+ private final GraphLens applied;
+
// Mapping from service types to service implementation types.
private final Map<DexType, Map<FeatureSplit, List<DexType>>> services;
private AppServices(AppView<?> appView, Map<DexType, Map<FeatureSplit, List<DexType>>> services) {
this.appView = appView;
+ this.applied = appView.graphLens();
this.services = services;
}
@@ -123,14 +127,15 @@
ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenFeatureMappings =
ImmutableMap.builder();
for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
- DexType rewrittenServiceType = graphLens.lookupType(entry.getKey());
+ DexType rewrittenServiceType = graphLens.lookupClassType(entry.getKey(), applied);
ImmutableMap.Builder<FeatureSplit, List<DexType>> rewrittenFeatureImplementations =
ImmutableMap.builder();
for (Entry<FeatureSplit, List<DexType>> featureSplitImpls : entry.getValue().entrySet()) {
ImmutableList.Builder<DexType> rewrittenServiceImplementationTypes =
ImmutableList.builder();
for (DexType serviceImplementationType : featureSplitImpls.getValue()) {
- rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+ rewrittenServiceImplementationTypes.add(
+ graphLens.lookupClassType(serviceImplementationType, applied));
}
rewrittenFeatureImplementations.put(
featureSplitImpls.getKey(), rewrittenServiceImplementationTypes.build());
@@ -173,12 +178,12 @@
return new AppServices(appView, rewrittenServicesBuilder.build());
}
- private boolean verifyRewrittenWithLens() {
+ public boolean verifyRewrittenWithLens() {
for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
- assert entry.getKey() == appView.graphLens().lookupType(entry.getKey());
+ assert entry.getKey() == appView.graphLens().lookupClassType(entry.getKey(), applied);
for (Entry<FeatureSplit, List<DexType>> featureEntry : entry.getValue().entrySet()) {
for (DexType type : featureEntry.getValue()) {
- assert type == appView.graphLens().lookupType(type);
+ assert type == appView.graphLens().lookupClassType(type, applied);
}
}
}
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 b3c5308..bf21997 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintFactory;
import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker;
@@ -48,6 +49,7 @@
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.function.Supplier;
public class AppView<T extends AppInfo> implements DexDefinitionSupplier, LibraryModeledPredicate {
@@ -91,7 +93,7 @@
private boolean allCodeProcessed = false;
private Predicate<DexType> classesEscapingIntoLibrary = Predicates.alwaysTrue();
private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
- private HorizontallyMergedClasses horizontallyMergedClasses;
+ private HorizontallyMergedClasses horizontallyMergedClasses = HorizontallyMergedClasses.empty();
private VerticallyMergedClasses verticallyMergedClasses;
private EnumDataMap unboxedEnums = EnumDataMap.empty();
// TODO(b/169115389): Remove
@@ -496,7 +498,7 @@
public MergedClassesCollection allMergedClasses() {
MergedClassesCollection collection = new MergedClassesCollection();
- if (horizontallyMergedClasses != null) {
+ if (hasHorizontallyMergedClasses()) {
collection.add(horizontallyMergedClasses);
}
if (verticallyMergedClasses != null) {
@@ -505,6 +507,10 @@
return collection;
}
+ public boolean hasHorizontallyMergedClasses() {
+ return !horizontallyMergedClasses.isEmpty();
+ }
+
/**
* Get the result of horizontal class merging. Returns null if horizontal class merging has not
* been run.
@@ -513,10 +519,15 @@
return horizontallyMergedClasses;
}
- public void setHorizontallyMergedClasses(HorizontallyMergedClasses horizontallyMergedClasses) {
- assert this.horizontallyMergedClasses == null;
- this.horizontallyMergedClasses = horizontallyMergedClasses;
- testing().horizontallyMergedClassesConsumer.accept(dexItemFactory(), horizontallyMergedClasses);
+ public void setHorizontallyMergedClasses(
+ HorizontallyMergedClasses horizontallyMergedClasses, HorizontalClassMerger.Mode mode) {
+ assert !hasHorizontallyMergedClasses() || mode.isFinal();
+ this.horizontallyMergedClasses = horizontallyMergedClasses().extend(horizontallyMergedClasses);
+ if (mode.isFinal()) {
+ testing()
+ .horizontallyMergedClassesConsumer
+ .accept(dexItemFactory(), horizontallyMergedClasses());
+ }
}
/**
@@ -637,7 +648,7 @@
public void rewriteWithLens(NonIdentityGraphLens lens) {
if (lens != null) {
- rewriteWithLens(lens, appInfo().app().asDirect(), withLiveness(), lens.getPrevious());
+ rewriteWithLens(lens, appInfo().app().asDirect(), withClassHierarchy(), lens.getPrevious());
}
}
@@ -650,13 +661,13 @@
NonIdentityGraphLens lens, DirectMappedDexApplication application, GraphLens appliedLens) {
assert lens != null;
assert application != null;
- rewriteWithLens(lens, application, withLiveness(), appliedLens);
+ rewriteWithLens(lens, application, withClassHierarchy(), appliedLens);
}
private static void rewriteWithLens(
NonIdentityGraphLens lens,
DirectMappedDexApplication application,
- AppView<AppInfoWithLiveness> appView,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
GraphLens appliedLens) {
if (lens == null) {
return;
@@ -688,7 +699,11 @@
firstUnappliedLens.withAlternativeParentLens(
newMemberRebindingLens,
() -> {
- appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
+ if (appView.hasLiveness()) {
+ appView
+ .withLiveness()
+ .setAppInfo(appView.appInfoWithLiveness().rewrittenWithLens(application, lens));
+ }
appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
if (appView.hasInitClassLens()) {
appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
@@ -715,4 +730,8 @@
assert alreadyLibraryDesugared != null;
return alreadyLibraryDesugared.contains(clazz.getType());
}
+
+ public boolean checkForTesting(Supplier<Boolean> test) {
+ return testing().enableTestAssertions ? test.get() : true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 315bb3e..31774f0 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -675,7 +675,7 @@
for (CfInstruction insn : instructions) {
constraint =
ConstraintWithTarget.meet(
- constraint, insn.inliningConstraint(inliningConstraints, context), appView);
+ constraint, insn.inliningConstraint(inliningConstraints, this, context), appView);
if (constraint == ConstraintWithTarget.NEVER) {
return constraint;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index baf5365..3e29db5 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -8,13 +8,56 @@
import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticMarker;
import java.util.List;
import java.util.function.Predicate;
/** Kind of the application class. Can be program, classpath or library. */
public class ClassKind<C extends DexClass> {
public static ClassKind<DexProgramClass> PROGRAM =
- new ClassKind<>(DexProgramClass::new, DexClass::isProgramClass);
+ new ClassKind<>(
+ (type,
+ originKind,
+ origin,
+ accessFlags,
+ superType,
+ interfaces,
+ sourceFile,
+ nestHost,
+ nestMembers,
+ enclosingMember,
+ innerClasses,
+ classSignature,
+ classAnnotations,
+ staticFields,
+ instanceFields,
+ directMethods,
+ virtualMethods,
+ skipNameValidationForTesting,
+ checksumSupplier,
+ syntheticMarker) ->
+ new DexProgramClass(
+ type,
+ originKind,
+ origin,
+ accessFlags,
+ superType,
+ interfaces,
+ sourceFile,
+ nestHost,
+ nestMembers,
+ enclosingMember,
+ innerClasses,
+ classSignature,
+ classAnnotations,
+ staticFields,
+ instanceFields,
+ directMethods,
+ virtualMethods,
+ skipNameValidationForTesting,
+ checksumSupplier,
+ syntheticMarker),
+ DexClass::isProgramClass);
public static ClassKind<DexClasspathClass> CLASSPATH =
new ClassKind<>(
(type,
@@ -35,7 +78,8 @@
directMethods,
virtualMethods,
skipNameValidationForTesting,
- checksumSupplier) ->
+ checksumSupplier,
+ syntheticMarker) ->
new DexClasspathClass(
type,
kind,
@@ -76,7 +120,8 @@
directMethods,
virtualMethods,
skipNameValidationForTesting,
- checksumSupplier) ->
+ checksumSupplier,
+ syntheticMarker) ->
new DexLibraryClass(
type,
kind,
@@ -118,7 +163,8 @@
DexEncodedMethod[] directMethods,
DexEncodedMethod[] virtualMethods,
boolean skipNameValidationForTesting,
- ChecksumSupplier checksumSupplier);
+ ChecksumSupplier checksumSupplier,
+ SyntheticMarker syntheticMarker);
}
private final Factory<C> factory;
@@ -148,7 +194,8 @@
DexEncodedMethod[] directMethods,
DexEncodedMethod[] virtualMethods,
boolean skipNameValidationForTesting,
- ChecksumSupplier checksumSupplier) {
+ ChecksumSupplier checksumSupplier,
+ SyntheticMarker syntheticMarker) {
return factory.create(
type,
kind,
@@ -168,7 +215,8 @@
directMethods,
virtualMethods,
skipNameValidationForTesting,
- checksumSupplier);
+ checksumSupplier,
+ syntheticMarker);
}
public boolean isOfKind(DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index d1e6951..0576326 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -359,19 +359,12 @@
}
public static DexAnnotation createAnnotationSynthesizedClass(
- SyntheticKind kind, DexType synthesizingContext, DexItemFactory dexItemFactory) {
+ SyntheticKind kind, DexItemFactory dexItemFactory) {
DexAnnotationElement kindElement =
new DexAnnotationElement(
dexItemFactory.kindString,
new DexValueString(dexItemFactory.createString(kind.descriptor)));
- DexAnnotationElement typeElement =
- new DexAnnotationElement(dexItemFactory.valueString, new DexValueType(synthesizingContext));
- DexAnnotationElement[] elements;
- if (synthesizingContext == null) {
- elements = new DexAnnotationElement[] {kindElement};
- } else {
- elements = new DexAnnotationElement[] {kindElement, typeElement};
- }
+ DexAnnotationElement[] elements = new DexAnnotationElement[] {kindElement};
return new DexAnnotation(
VISIBILITY_BUILD,
new DexEncodedAnnotation(dexItemFactory.annotationSynthesizedClass, elements));
@@ -379,10 +372,10 @@
public static boolean hasSynthesizedClassAnnotation(
DexAnnotationSet annotations, DexItemFactory factory) {
- return getSynthesizedClassAnnotationContextType(annotations, factory) != null;
+ return getSynthesizedClassAnnotationInfo(annotations, factory) != null;
}
- public static Pair<SyntheticKind, DexType> getSynthesizedClassAnnotationContextType(
+ public static SyntheticKind getSynthesizedClassAnnotationInfo(
DexAnnotationSet annotations, DexItemFactory factory) {
if (annotations.size() != 1) {
return null;
@@ -392,7 +385,7 @@
return null;
}
int length = annotation.annotation.elements.length;
- if (length != 1 && length != 2) {
+ if (length != 1) {
return null;
}
assert factory.kindString.isLessThan(factory.valueString);
@@ -406,20 +399,7 @@
SyntheticKind kind =
SyntheticNaming.SyntheticKind.fromDescriptor(
kindElement.value.asDexValueString().getValue().toString());
- if (kind == null) {
- return null;
- }
- if (length != 2) {
- return new Pair<>(kind, null);
- }
- DexAnnotationElement valueElement = annotation.annotation.elements[1];
- if (valueElement.name != factory.valueString) {
- return null;
- }
- if (!valueElement.value.isDexValueType()) {
- return null;
- }
- return new Pair<>(kind, valueElement.value.asDexValueType().getValue());
+ return kind;
}
public DexAnnotation rewrite(Function<DexEncodedAnnotation, DexEncodedAnnotation> rewriter) {
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 d82db4a..31b83b2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -20,6 +20,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.function.Predicate;
public abstract class DexApplication {
@@ -177,6 +178,11 @@
return self();
}
+ public synchronized T removeProgramClasses(Predicate<DexProgramClass> predicate) {
+ this.programClasses.removeIf(predicate);
+ return self();
+ }
+
public synchronized T replaceProgramClasses(Collection<DexProgramClass> newProgramClasses) {
assert newProgramClasses != null;
this.programClasses.clear();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 2d0e429..182c91c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -76,7 +76,7 @@
*/
private NestHostClassAttribute nestHost;
- private final List<NestMemberClassAttribute> nestMembers;
+ private List<NestMemberClassAttribute> nestMembers;
/** Generic signature information if the attribute is present in the input */
protected ClassSignature classSignature;
@@ -177,6 +177,10 @@
return sourceFile;
}
+ public void setSourceFile(DexString sourceFile) {
+ this.sourceFile = sourceFile;
+ }
+
public Iterable<DexEncodedField> fields() {
return fields(Predicates.alwaysTrue());
}
@@ -1038,6 +1042,10 @@
this.nestHost = new NestHostClassAttribute(type);
}
+ public void setNestHostAttribute(NestHostClassAttribute nestHostAttribute) {
+ this.nestHost = nestHostAttribute;
+ }
+
public boolean isNestHost() {
return !nestMembers.isEmpty();
}
@@ -1065,10 +1073,22 @@
return nestHost;
}
+ public boolean hasNestMemberAttributes() {
+ return nestMembers != null && !nestMembers.isEmpty();
+ }
+
public List<NestMemberClassAttribute> getNestMembersClassAttributes() {
return nestMembers;
}
+ public void setNestMemberAttributes(List<NestMemberClassAttribute> nestMemberAttributes) {
+ this.nestMembers = nestMemberAttributes;
+ }
+
+ public void removeNestMemberAttributes(Predicate<NestMemberClassAttribute> predicate) {
+ nestMembers.removeIf(predicate);
+ }
+
/** Returns kotlin class info if the class is synthesized by kotlin compiler. */
public abstract KotlinClassLevelInfo getKotlinInfo();
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1a2bc2b..315b8a1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticMarker;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.structural.Ordered;
import com.android.tools.r8.utils.structural.StructuralItem;
@@ -53,6 +54,8 @@
private final ChecksumSupplier checksumSupplier;
+ private SyntheticMarker syntheticMarker;
+
public DexProgramClass(
DexType type,
Kind originKind,
@@ -72,7 +75,8 @@
DexEncodedMethod[] directMethods,
DexEncodedMethod[] virtualMethods,
boolean skipNameValidationForTesting,
- ChecksumSupplier checksumSupplier) {
+ ChecksumSupplier checksumSupplier,
+ SyntheticMarker syntheticMarker) {
super(
sourceFile,
interfaces,
@@ -95,6 +99,50 @@
assert classAnnotations != null;
this.originKind = originKind;
this.checksumSupplier = checksumSupplier;
+ this.syntheticMarker = syntheticMarker;
+ }
+
+ public DexProgramClass(
+ DexType type,
+ Kind originKind,
+ Origin origin,
+ ClassAccessFlags accessFlags,
+ DexType superType,
+ DexTypeList interfaces,
+ DexString sourceFile,
+ NestHostClassAttribute nestHost,
+ List<NestMemberClassAttribute> nestMembers,
+ EnclosingMethodAttribute enclosingMember,
+ List<InnerClassAttribute> innerClasses,
+ ClassSignature classSignature,
+ DexAnnotationSet classAnnotations,
+ DexEncodedField[] staticFields,
+ DexEncodedField[] instanceFields,
+ DexEncodedMethod[] directMethods,
+ DexEncodedMethod[] virtualMethods,
+ boolean skipNameValidationForTesting,
+ ChecksumSupplier checksumSupplier) {
+ this(
+ type,
+ originKind,
+ origin,
+ accessFlags,
+ superType,
+ interfaces,
+ sourceFile,
+ nestHost,
+ nestMembers,
+ enclosingMember,
+ innerClasses,
+ classSignature,
+ classAnnotations,
+ staticFields,
+ instanceFields,
+ directMethods,
+ virtualMethods,
+ skipNameValidationForTesting,
+ checksumSupplier,
+ null);
}
@Override
@@ -120,6 +168,15 @@
return DexProgramClass::specify;
}
+ public SyntheticMarker stripSyntheticInputMarker() {
+ SyntheticMarker marker = syntheticMarker;
+ // The synthetic input marker is "read once". It is stored only for identifying the input as
+ // synthetic and amending it to the SyntheticItems collection. After identification this field
+ // should not be used.
+ syntheticMarker = null;
+ return marker;
+ }
+
private static void specify(StructuralSpecification<DexProgramClass, ?> spec) {
spec.withItem(c -> c.type)
.withItem(c -> c.superType)
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 07410c0..684dd7d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -16,6 +16,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -66,6 +67,14 @@
return this;
}
+ public DexTypeList map(Function<DexType, DexType> fn) {
+ if (isEmpty()) {
+ return DexTypeList.empty();
+ }
+ DexType[] newTypes = ArrayUtils.map(values, fn, DexType.EMPTY_ARRAY);
+ return newTypes != values ? create(newTypes) : this;
+ }
+
public DexTypeList removeIf(Predicate<DexType> predicate) {
return keepIf(not(predicate));
}
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index 1a3984c..fde5f6c 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -48,6 +48,10 @@
}
}
+ public boolean hasEnclosingMethod() {
+ return enclosingMethod != null;
+ }
+
public DexMethod getEnclosingMethod() {
return enclosingMethod;
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 93b3b47..da35877 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.GenericSignature.TypeSignature;
import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
import com.android.tools.r8.utils.ListUtils;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -25,7 +26,7 @@
private final DexItemFactory factory;
private final Predicate<DexType> wasPruned;
private final Function<DexType, DexType> lookupType;
- private final DexType context;
+ private final DexProgramClass context;
private final ClassTypeSignature objectTypeSignature;
@@ -36,14 +37,14 @@
? appView.appInfo().withLiveness()::wasPruned
: alwaysFalse(),
appView.graphLens()::lookupType,
- context.getType());
+ context);
}
public GenericSignatureTypeRewriter(
DexItemFactory factory,
Predicate<DexType> wasPruned,
Function<DexType, DexType> lookupType,
- DexType context) {
+ DexProgramClass context) {
this.factory = factory;
this.wasPruned = wasPruned;
this.lookupType = lookupType;
@@ -138,7 +139,9 @@
@Override
public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
ClassTypeSignature rewritten = classTypeSignature.visit(this);
- return rewritten == null || rewritten.type() == context ? objectTypeSignature : rewritten;
+ return rewritten == null || rewritten.type() == context.type
+ ? objectTypeSignature
+ : rewritten;
}
@Override
@@ -147,13 +150,25 @@
if (interfaceSignatures.isEmpty()) {
return interfaceSignatures;
}
- return ListUtils.mapOrElse(interfaceSignatures, this::visitSuperInterface);
+ List<ClassTypeSignature> rewrittenInterfaces =
+ ListUtils.mapOrElse(interfaceSignatures, this::visitSuperInterface);
+ // Map against the actual interfaces implemented on the class for us to still preserve
+ // type arguments.
+ List<ClassTypeSignature> finalInterfaces = new ArrayList<>(rewrittenInterfaces.size());
+ context.interfaces.forEach(
+ iface -> {
+ ClassTypeSignature rewrittenSignature =
+ ListUtils.firstMatching(rewrittenInterfaces, rewritten -> rewritten.type == iface);
+ finalInterfaces.add(
+ rewrittenSignature != null ? rewrittenSignature : new ClassTypeSignature(iface));
+ });
+ return finalInterfaces;
}
@Override
public ClassTypeSignature visitSuperInterface(ClassTypeSignature classTypeSignature) {
ClassTypeSignature rewritten = classTypeSignature.visit(this);
- return rewritten == null || rewritten.type() == context ? null : rewritten;
+ return rewritten == null || rewritten.type() == context.type ? null : rewritten;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
index f2a359a..7c0ee29 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
@@ -151,51 +151,28 @@
}
DexType contextType = reference.getContextType();
// TODO(b/187035453): We should visit generic signatures in the enqueuer.
- DexClass clazz =
- appView.appInfo().definitionForWithoutExistenceAssert(reference.getContextType());
+ DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(contextType);
boolean prunedHere = seenPruned || clazz == null;
if (appView.hasLiveness()
- && appView
- .withLiveness()
- .appInfo()
- .getMissingClasses()
- .contains(reference.getContextType())) {
+ && appView.withLiveness().appInfo().getMissingClasses().contains(contextType)) {
prunedHere = seenPruned;
}
- if (reference.isDexMethod()) {
- TypeParameterSubstitutions typeParameterSubstitutions = formalsInfo.get(reference);
- if (clazz != null) {
- assert clazz.isProgramClass();
- DexEncodedMethod method = clazz.lookupMethod(reference.asDexMethod());
- if (method == null) {
- prunedHere = true;
- } else if (method.isStatic()) {
- // Static methods define their own scope.
- return empty().combine(typeParameterSubstitutions, seenPruned);
- }
- }
- // Lookup the formals in the enclosing context.
- return computeTypeParameterContext(
- appView,
- contextType,
- wasPruned,
- prunedHere
- || hasPrunedRelationship(
- appView, enclosingInfo.get(contextType), contextType, wasPruned))
- // Add the formals for the class.
- .combine(formalsInfo.get(contextType), prunedHere)
- // Add the formals for the method.
- .combine(formalsInfo.get(reference), prunedHere);
+ // Lookup the formals in the enclosing context.
+ TypeParameterContext typeParameterContext =
+ computeTypeParameterContext(
+ appView,
+ enclosingInfo.get(contextType),
+ wasPruned,
+ prunedHere
+ || hasPrunedRelationship(
+ appView, enclosingInfo.get(contextType), contextType, wasPruned))
+ // Add formals for the context
+ .combine(formalsInfo.get(contextType), prunedHere);
+ if (!reference.isDexMethod()) {
+ return typeParameterContext;
}
- assert reference.isDexType();
- return computeTypeParameterContext(
- appView,
- enclosingInfo.get(reference),
- wasPruned,
- prunedHere
- || hasPrunedRelationship(
- appView, enclosingInfo.get(reference), contextType, wasPruned))
- .combine(formalsInfo.get(reference), prunedHere);
+ prunedHere = prunedHere || clazz == null || clazz.lookupMethod(reference.asDexMethod()) == null;
+ return typeParameterContext.combine(formalsInfo.get(reference), prunedHere);
}
private static boolean hasPrunedRelationship(
@@ -273,17 +250,12 @@
MethodTypeSignature methodSignature = method.getGenericSignature();
if (methodSignature.hasSignature()
&& method.getGenericSignature().isValid()) {
- if (method.isStatic()) {
- method.setGenericSignature(
- baseArgumentApplier
- .buildForMethod(methodSignature.getFormalTypeParameters())
- .visitMethodSignature(methodSignature));
- } else {
- method.setGenericSignature(
- classArgumentApplier
- .buildForMethod(methodSignature.getFormalTypeParameters())
- .visitMethodSignature(methodSignature));
- }
+ // The reflection api do not distinguish static methods context from
+ // virtual methods.
+ method.setGenericSignature(
+ classArgumentApplier
+ .buildForMethod(methodSignature.getFormalTypeParameters())
+ .visitMethodSignature(methodSignature));
}
});
clazz
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 88d0852..ff2fd57 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -23,11 +23,13 @@
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -336,9 +338,17 @@
public abstract String lookupPackageName(String pkg);
- public abstract DexType lookupClassType(DexType type);
+ public DexType lookupClassType(DexType type) {
+ return lookupClassType(type, getIdentityLens());
+ }
- public abstract DexType lookupType(DexType type);
+ public abstract DexType lookupClassType(DexType type, GraphLens applied);
+
+ public DexType lookupType(DexType type) {
+ return lookupType(type, getIdentityLens());
+ }
+
+ public abstract DexType lookupType(DexType type, GraphLens applied);
// This overload can be used when the graph lens is known to be context insensitive.
public final DexMethod lookupMethod(DexMethod method) {
@@ -579,10 +589,16 @@
return builder.build();
}
- public <T> ImmutableMap<DexType, T> rewriteTypeKeys(Map<DexType, T> map) {
- ImmutableMap.Builder<DexType, T> builder = ImmutableMap.builder();
- map.forEach((type, value) -> builder.put(lookupType(type), value));
- return builder.build();
+ public <T> Map<DexType, T> rewriteTypeKeys(Map<DexType, T> map, BiFunction<T, T, T> merge) {
+ Map<DexType, T> newMap = new IdentityHashMap<>();
+ map.forEach(
+ (type, value) -> {
+ DexType rewrittenType = lookupType(type);
+ T previousValue = newMap.get(rewrittenType);
+ newMap.put(
+ rewrittenType, previousValue != null ? merge.apply(value, previousValue) : value);
+ });
+ return Collections.unmodifiableMap(newMap);
}
public boolean verifyMappingToOriginalProgram(
@@ -695,7 +711,10 @@
}
@Override
- public final DexType lookupType(DexType type) {
+ public final DexType lookupType(DexType type, GraphLens applied) {
+ if (this == applied) {
+ return type;
+ }
if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) {
return type;
}
@@ -713,8 +732,11 @@
}
@Override
- public final DexType lookupClassType(DexType type) {
+ public final DexType lookupClassType(DexType type, GraphLens applied) {
assert type.isClassType() : "Expected class type, but was `" + type.toSourceString() + "`";
+ if (this == applied) {
+ return type;
+ }
return internalDescribeLookupClassType(getPrevious().lookupClassType(type));
}
@@ -816,12 +838,12 @@
}
@Override
- public DexType lookupType(DexType type) {
+ public DexType lookupType(DexType type, GraphLens applied) {
return type;
}
@Override
- public DexType lookupClassType(DexType type) {
+ public DexType lookupClassType(DexType type, GraphLens applied) {
assert type.isClassType();
return type;
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 4def125..3c01abb 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.synthesis.SyntheticMarker;
import com.android.tools.r8.utils.AsmUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
@@ -58,6 +59,7 @@
import java.util.function.Consumer;
import java.util.zip.CRC32;
import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
@@ -119,6 +121,7 @@
}
reader.accept(
new CreateDexClassVisitor<>(origin, classKind, reader.b, application, classConsumer),
+ new Attribute[] {SyntheticMarker.getMarkerAttributePrototype()},
parsingOptions);
// Read marker.
@@ -219,6 +222,7 @@
private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
private boolean hasReachabilitySensitiveMethod = false;
+ private SyntheticMarker syntheticMarker = null;
public CreateDexClassVisitor(
Origin origin,
@@ -235,6 +239,15 @@
}
@Override
+ public void visitAttribute(Attribute attribute) {
+ SyntheticMarker marker = SyntheticMarker.readMarkerAttribute(attribute);
+ if (marker != null) {
+ assert syntheticMarker == null;
+ syntheticMarker = marker;
+ }
+ }
+
+ @Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (outerName != null && innerName != null) {
String separator = DescriptorUtils.computeInnerClassSeparator(outerName, name, innerName);
@@ -452,7 +465,8 @@
directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
application.getFactory().getSkipNameValidationForTesting(),
- getChecksumSupplier(classKind));
+ getChecksumSupplier(classKind),
+ syntheticMarker);
InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass();
// A member class should not be a local or anonymous class.
if (innerClassAttribute != null && innerClassAttribute.getOuter() != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 686d7d5..457ef38 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.graph;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.shaking.GraphReporter;
@@ -461,9 +462,8 @@
assert false;
return;
}
- assert !instantiatedLambdas.containsKey(type);
// TODO(b/150277553): Rewrite lambda descriptor.
- instantiatedLambdas.put(type, lambdas);
+ instantiatedLambdas.computeIfAbsent(type, ignoreKey(ArrayList::new)).addAll(lambdas);
});
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 5bbc1ee..969ac7d 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -141,7 +141,7 @@
return newClass;
}
- private EnclosingMethodAttribute fixupEnclosingMethodAttribute(
+ protected EnclosingMethodAttribute fixupEnclosingMethodAttribute(
EnclosingMethodAttribute enclosingMethodAttribute) {
if (enclosingMethodAttribute == null) {
return null;
@@ -190,7 +190,7 @@
return dexItemFactory.createField(newHolder, newType, field.name);
}
- private List<InnerClassAttribute> fixupInnerClassAttributes(
+ protected List<InnerClassAttribute> fixupInnerClassAttributes(
List<InnerClassAttribute> innerClassAttributes) {
if (innerClassAttributes.isEmpty()) {
return innerClassAttributes;
@@ -240,13 +240,13 @@
fixupType(method.holder), fixupProto(method.proto), method.name);
}
- private NestHostClassAttribute fixupNestHost(NestHostClassAttribute nestHostClassAttribute) {
+ protected NestHostClassAttribute fixupNestHost(NestHostClassAttribute nestHostClassAttribute) {
return nestHostClassAttribute != null
? new NestHostClassAttribute(fixupType(nestHostClassAttribute.getNestHost()))
: null;
}
- private List<NestMemberClassAttribute> fixupNestMemberAttributes(
+ protected List<NestMemberClassAttribute> fixupNestMemberAttributes(
List<NestMemberClassAttribute> nestMemberAttributes) {
if (nestMemberAttributes.isEmpty()) {
return nestMemberAttributes;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 880890d..2b65950 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
@@ -29,12 +29,14 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerSynthesizedCode;
import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -61,6 +63,7 @@
private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Mode mode;
private final MergeGroup group;
private final DexItemFactory dexItemFactory;
private final ClassInitializerSynthesizedCode classInitializerSynthesizedCode;
@@ -75,12 +78,14 @@
private ClassMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ Mode mode,
HorizontalClassMergerGraphLens.Builder lensBuilder,
MergeGroup group,
Collection<VirtualMethodMerger> virtualMethodMergers,
Collection<ConstructorMerger> constructorMergers,
ClassInitializerSynthesizedCode classInitializerSynthesizedCode) {
this.appView = appView;
+ this.mode = mode;
this.lensBuilder = lensBuilder;
this.group = group;
this.virtualMethodMergers = virtualMethodMergers;
@@ -112,8 +117,7 @@
}
DexMethod newClinit = dexItemFactory.createClassInitializer(group.getTarget().getType());
-
- CfCode code = classInitializerSynthesizedCode.synthesizeCode(group.getTarget().getType());
+ Code code = classInitializerSynthesizedCode.getOrCreateCode(group.getTarget().getType());
if (!group.getTarget().hasClassInitializer()) {
classMethodsBuilder.addDirectMethod(
new DexEncodedMethod(
@@ -129,9 +133,11 @@
} else {
DexEncodedMethod clinit = group.getTarget().getClassInitializer();
clinit.setCode(code, appView);
- CfVersion cfVersion = classInitializerSynthesizedCode.getCfVersion();
- if (cfVersion != null) {
- clinit.upgradeClassFileVersion(cfVersion);
+ if (code.isCfCode()) {
+ CfVersion cfVersion = classInitializerSynthesizedCode.getCfVersion();
+ if (cfVersion != null) {
+ clinit.upgradeClassFileVersion(cfVersion);
+ }
}
classMethodsBuilder.addDirectMethod(clinit);
}
@@ -193,6 +199,7 @@
void appendClassIdField() {
assert appView.hasLiveness();
+ assert mode.isInitial();
boolean deprecated = false;
boolean d8R8Synthesized = true;
@@ -234,6 +241,24 @@
}
}
+ void fixNestMemberAttributes() {
+ if (group.getTarget().isInANest() && !group.getTarget().hasNestMemberAttributes()) {
+ for (DexProgramClass clazz : group.getSources()) {
+ if (clazz.hasNestMemberAttributes()) {
+ // The nest host has been merged into a nest member.
+ group.getTarget().clearNestHost();
+ group.getTarget().setNestMemberAttributes(clazz.getNestMembersClassAttributes());
+ group
+ .getTarget()
+ .removeNestMemberAttributes(
+ nestMemberAttribute ->
+ nestMemberAttribute.getNestMember() == group.getTarget().getType());
+ break;
+ }
+ }
+ }
+ }
+
private void mergeAnnotations() {
assert group.getClasses().stream().filter(DexDefinition::hasAnnotations).count() <= 1;
for (DexProgramClass clazz : group.getSources()) {
@@ -247,10 +272,25 @@
private void mergeInterfaces() {
DexTypeList previousInterfaces = group.getTarget().getInterfaces();
Set<DexType> interfaces = Sets.newLinkedHashSet(previousInterfaces);
- group.forEachSource(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
- if (interfaces.size() > previousInterfaces.size()) {
- group.getTarget().setInterfaces(new DexTypeList(interfaces));
+ if (group.isInterfaceGroup()) {
+ // Add all implemented interfaces from the merge group to the target class, ignoring
+ // implemented interfaces that are part of the merge group.
+ Set<DexType> groupTypes =
+ SetUtils.newImmutableSet(
+ builder -> group.forEach(clazz -> builder.accept(clazz.getType())));
+ group.forEachSource(
+ clazz -> {
+ for (DexType itf : clazz.getInterfaces()) {
+ if (!groupTypes.contains(itf)) {
+ interfaces.add(itf);
+ }
+ }
+ });
+ } else {
+ // Add all implemented interfaces from the merge group to the target class.
+ group.forEachSource(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
}
+ group.getTarget().setInterfaces(DexTypeList.create(interfaces));
}
void mergeInstanceFields() {
@@ -264,6 +304,7 @@
public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) {
fixAccessFlags();
+ fixNestMemberAttributes();
if (group.hasClassIdField()) {
appendClassIdField();
@@ -282,6 +323,7 @@
public static class Builder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private Mode mode;
private final MergeGroup group;
public Builder(AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
@@ -289,6 +331,11 @@
this.group = group;
}
+ Builder setMode(Mode mode) {
+ this.mode = mode;
+ return this;
+ }
+
private void selectTarget() {
Iterable<DexProgramClass> candidates = Iterables.filter(group, DexClass::isPublic);
if (IterableUtils.isEmpty(candidates)) {
@@ -401,6 +448,7 @@
return new ClassMerger(
appView,
+ mode,
lensBuilder,
group,
virtualMethodMergers,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index eff9460..1a88983 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -7,78 +7,73 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
-import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy;
-import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups;
-import com.android.tools.r8.horizontalclassmerging.policies.MinimizeFieldCasts;
-import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
-import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
-import com.android.tools.r8.horizontalclassmerging.policies.NoDeadEnumLiteMaps;
-import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
-import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
-import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
-import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
-import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
-import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
-import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
-import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
-import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
-import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
-import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
-import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
-import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoDifferentMainDexGroups;
-import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation;
-import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
-import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
-import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
-import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
-import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
-import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.PrunedItems;
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.shaking.RuntimeTypeCheckInfo;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
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.LinkedList;
import java.util.List;
public class HorizontalClassMerger {
- // TODO(b/181846319): Add 'FINAL' mode that runs after synthetic finalization.
public enum Mode {
- INITIAL;
+ INITIAL,
+ FINAL;
public boolean isInitial() {
return this == INITIAL;
}
+
+ public boolean isFinal() {
+ return this == FINAL;
+ }
}
private final AppView<? extends AppInfoWithClassHierarchy> appView;
- private final Mode mode = Mode.INITIAL;
+ private final Mode mode;
+ private final HorizontalClassMergerOptions options;
- public HorizontalClassMerger(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ private HorizontalClassMerger(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
this.appView = appView;
- assert appView.options().enableInlining;
+ this.mode = mode;
+ this.options = appView.options().horizontalClassMergerOptions();
}
- public HorizontalClassMergerResult run(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
- MergeGroup initialGroup = new MergeGroup(appView.appInfo().classesWithDeterministicOrder());
+ public static HorizontalClassMerger createForInitialClassMerging(
+ AppView<AppInfoWithLiveness> appView) {
+ return new HorizontalClassMerger(appView, Mode.INITIAL);
+ }
+ public static HorizontalClassMerger createForFinalClassMerging(
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return new HorizontalClassMerger(appView, Mode.FINAL);
+ }
+
+ public void runIfNecessary(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
+ if (options.isEnabled(mode)) {
+ timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
+ run(runtimeTypeCheckInfo, timing);
+ timing.end();
+ } else {
+ appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty(), mode);
+ }
+ }
+
+ private void run(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
// Run the policies on all program classes to produce a final grouping.
- List<Policy> policies = getPolicies(runtimeTypeCheckInfo);
- Collection<MergeGroup> groups =
- new PolicyExecutor().run(Collections.singletonList(initialGroup), policies, timing);
+ List<Policy> policies = PolicyScheduler.getPolicies(appView, mode, runtimeTypeCheckInfo);
+ Collection<MergeGroup> groups = new PolicyExecutor().run(getInitialGroups(), policies, timing);
// If there are no groups, then end horizontal class merging.
if (groups.isEmpty()) {
- appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
- return null;
+ appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty(), mode);
+ return;
}
HorizontalClassMergerGraphLens.Builder lensBuilder =
@@ -95,20 +90,38 @@
// Generate the graph lens.
HorizontallyMergedClasses mergedClasses =
HorizontallyMergedClasses.builder().addMergeGroups(groups).build();
- appView.setHorizontallyMergedClasses(mergedClasses);
- HorizontalClassMergerGraphLens lens =
+ appView.setHorizontallyMergedClasses(mergedClasses, mode);
+
+ HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
createLens(mergedClasses, lensBuilder, syntheticArgumentClass);
// Prune keep info.
- appView
- .getKeepInfo()
- .mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
+ KeepInfoCollection keepInfo = appView.getKeepInfo();
+ keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
- return new HorizontalClassMergerResult(createFieldAccessInfoCollectionModifier(groups), lens);
+ // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation
+ // sites, fields accesses, etc. are correctly transferred to the target classes.
+ appView.rewriteWithLensAndApplication(
+ horizontalClassMergerGraphLens, getNewApplication(mergedClasses));
+
+ // Record where the synthesized $r8$classId fields are read and written.
+ if (mode.isInitial()) {
+ createFieldAccessInfoCollectionModifier(groups).modify(appView.withLiveness());
+ } else {
+ assert groups.stream().noneMatch(MergeGroup::hasClassIdField);
+ }
+
+ appView.pruneItems(
+ PrunedItems.builder()
+ .setPrunedApp(appView.appInfo().app())
+ .addRemovedClasses(mergedClasses.getSources())
+ .addNoLongerSyntheticItems(mergedClasses.getSources())
+ .build());
}
private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier(
Collection<MergeGroup> groups) {
+ assert mode.isInitial();
FieldAccessInfoCollectionModifier.Builder builder =
new FieldAccessInfoCollectionModifier.Builder();
for (MergeGroup group : groups) {
@@ -126,47 +139,34 @@
return builder.build();
}
- private List<Policy> getPolicies(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- List<SingleClassPolicy> singleClassPolicies =
- ImmutableList.of(
- new NotMatchedByNoHorizontalClassMerging(appViewWithLiveness),
- new NoDeadEnumLiteMaps(appViewWithLiveness),
- new NoAnnotationClasses(),
- new NoEnums(appView),
- new NoInnerClasses(),
- new NoInstanceFieldAnnotations(),
- new NoInterfaces(),
- new NoClassInitializerWithObservableSideEffects(),
- new NoNativeMethods(),
- new NoKeepRules(appView),
- new NoKotlinMetadata(),
- new NoServiceLoaders(appView),
- new NotVerticallyMergedIntoSubtype(appView),
- new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
- new DontInlinePolicy(appViewWithLiveness));
- List<MultiClassPolicy> multiClassPolicies =
- ImmutableList.of(
- new SameInstanceFields(appView),
- new NoClassAnnotationCollisions(),
- new CheckAbstractClasses(appView),
- new SyntheticItemsPolicy(appView),
- new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
- new PreventMethodImplementation(appView),
- new PreventMergeIntoDifferentMainDexGroups(appView),
- new AllInstantiatedOrUninstantiated(appViewWithLiveness),
- new SameParentClass(),
- new SameNestHost(appView),
- new PreserveMethodCharacteristics(appViewWithLiveness),
- new SameFeatureSplit(appView),
- new RespectPackageBoundaries(appView),
- new DontMergeSynchronizedClasses(appViewWithLiveness),
- new MinimizeFieldCasts(),
- new LimitGroups(appView));
- return ImmutableList.<Policy>builder()
- .addAll(singleClassPolicies)
- .addAll(multiClassPolicies)
- .build();
+ private DirectMappedDexApplication getNewApplication(HorizontallyMergedClasses mergedClasses) {
+ // In the second round of class merging, we must forcefully remove the merged classes from the
+ // application, since we won't run tree shaking before writing the application.
+ DirectMappedDexApplication application = appView.appInfo().app().asDirect();
+ return mode.isInitial()
+ ? application
+ : application
+ .builder()
+ .removeProgramClasses(
+ clazz -> mergedClasses.hasBeenMergedIntoDifferentType(clazz.getType()))
+ .build();
+ }
+
+ private List<MergeGroup> getInitialGroups() {
+ MergeGroup initialClassGroup = new MergeGroup();
+ MergeGroup initialInterfaceGroup = new MergeGroup();
+ for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+ if (clazz.isInterface()) {
+ initialInterfaceGroup.add(clazz);
+ } else {
+ initialClassGroup.add(clazz);
+ }
+ }
+ List<MergeGroup> initialGroups = new LinkedList<>();
+ initialGroups.add(initialClassGroup);
+ initialGroups.add(initialInterfaceGroup);
+ initialGroups.removeIf(MergeGroup::isTrivial);
+ return initialGroups;
}
/**
@@ -176,15 +176,11 @@
private List<ClassMerger> initializeClassMergers(
HorizontalClassMergerGraphLens.Builder lensBuilder,
Collection<MergeGroup> groups) {
- List<ClassMerger> classMergers = new ArrayList<>();
-
- // TODO(b/166577694): Replace Collection<DexProgramClass> with MergeGroup
+ List<ClassMerger> classMergers = new ArrayList<>(groups.size());
for (MergeGroup group : groups) {
- assert !group.isEmpty();
- ClassMerger merger = new ClassMerger.Builder(appView, group).build(lensBuilder);
- classMergers.add(merger);
+ assert group.isNonTrivial();
+ classMergers.add(new ClassMerger.Builder(appView, group).setMode(mode).build(lensBuilder));
}
-
return classMergers;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
deleted file mode 100644
index 820c24a..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
+++ /dev/null
@@ -1,28 +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.horizontalclassmerging;
-
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-
-public class HorizontalClassMergerResult {
-
- private final FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier;
- private final HorizontalClassMergerGraphLens graphLens;
-
- HorizontalClassMergerResult(
- FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier,
- HorizontalClassMergerGraphLens graphLens) {
- this.fieldAccessInfoCollectionModifier = fieldAccessInfoCollectionModifier;
- this.graphLens = graphLens;
- }
-
- public FieldAccessInfoCollectionModifier getFieldAccessInfoCollectionModifier() {
- return fieldAccessInfoCollectionModifier;
- }
-
- public HorizontalClassMergerGraphLens getGraphLens() {
- return graphLens;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
index 927e629..9caf1a9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -32,6 +32,24 @@
return new HorizontallyMergedClasses(new EmptyBidirectionalOneToOneMap<>());
}
+ public HorizontallyMergedClasses extend(HorizontallyMergedClasses newHorizontallyMergedClasses) {
+ if (isEmpty()) {
+ return newHorizontallyMergedClasses;
+ }
+ if (newHorizontallyMergedClasses.isEmpty()) {
+ return this;
+ }
+ Builder builder = builder();
+ forEachMergeGroup(
+ (sources, target) -> {
+ DexType rewrittenTarget = newHorizontallyMergedClasses.getMergeTargetOrDefault(target);
+ sources.forEach(source -> builder.add(source, rewrittenTarget));
+ });
+ newHorizontallyMergedClasses.forEachMergeGroup(
+ (sources, target) -> sources.forEach(source -> builder.add(source, target)));
+ return builder.build();
+ }
+
@Override
public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) {
mergedClasses.forEachManyToOneMapping(consumer);
@@ -58,6 +76,10 @@
return mergedClasses.containsKey(type);
}
+ public boolean isEmpty() {
+ return mergedClasses.isEmpty();
+ }
+
@Override
public boolean isMergeTarget(DexType type) {
return mergedClasses.containsValue(type);
@@ -87,8 +109,13 @@
private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses =
BidirectionalManyToOneHashMap.newIdentityHashMap();
+ void add(DexType source, DexType target) {
+ assert !mergedClasses.containsKey(source);
+ mergedClasses.put(source, target);
+ }
+
void addMergeGroup(MergeGroup group) {
- group.forEachSource(clazz -> mergedClasses.put(clazz.getType(), group.getTarget().getType()));
+ group.forEachSource(clazz -> add(clazz.getType(), group.getTarget().getType()));
}
Builder addMergeGroups(Iterable<MergeGroup> groups) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index 4b9ad6a..ebedb0b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -6,8 +6,10 @@
package com.android.tools.r8.horizontalclassmerging;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.IteratorUtils;
import com.google.common.collect.Iterables;
import java.util.Collection;
@@ -16,7 +18,7 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
-public class MergeGroup implements Iterable<DexProgramClass> {
+public class MergeGroup implements Collection<DexProgramClass> {
public static class Metadata {}
@@ -35,9 +37,9 @@
add(clazz);
}
- public MergeGroup(Collection<DexProgramClass> classes) {
+ public MergeGroup(Iterable<DexProgramClass> classes) {
this();
- addAll(classes);
+ Iterables.addAll(this.classes, classes);
}
public void applyMetadataFrom(MergeGroup group) {
@@ -46,20 +48,33 @@
}
}
- public void add(DexProgramClass clazz) {
- classes.add(clazz);
+ @Override
+ public boolean add(DexProgramClass clazz) {
+ return classes.add(clazz);
}
- public void add(MergeGroup group) {
- classes.addAll(group.getClasses());
+ public boolean add(MergeGroup group) {
+ return classes.addAll(group.getClasses());
}
- public void addAll(Collection<DexProgramClass> classes) {
- this.classes.addAll(classes);
+ @Override
+ public boolean addAll(Collection<? extends DexProgramClass> classes) {
+ return this.classes.addAll(classes);
}
- public void addFirst(DexProgramClass clazz) {
- classes.addFirst(clazz);
+ @Override
+ public void clear() {
+ classes.clear();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return classes.contains(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> collection) {
+ return classes.containsAll(collection);
}
public void forEachSource(Consumer<DexProgramClass> consumer) {
@@ -110,28 +125,66 @@
return size() < 2;
}
+ public boolean isNonTrivial() {
+ return !isTrivial();
+ }
+
+ @Override
public boolean isEmpty() {
return classes.isEmpty();
}
+ public boolean isInterfaceGroup() {
+ assert !isEmpty();
+ assert IterableUtils.allIdentical(getClasses(), DexClass::isInterface);
+ return getClasses().getFirst().isInterface();
+ }
+
@Override
public Iterator<DexProgramClass> iterator() {
return classes.iterator();
}
+ @Override
public int size() {
return classes.size();
}
+ @Override
+ public boolean remove(Object o) {
+ return classes.remove(o);
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ return classes.removeAll(collection);
+ }
+
public DexProgramClass removeFirst(Predicate<DexProgramClass> predicate) {
return IteratorUtils.removeFirst(iterator(), predicate);
}
- public boolean removeIf(Predicate<DexProgramClass> predicate) {
+ @Override
+ public boolean removeIf(Predicate<? super DexProgramClass> predicate) {
return classes.removeIf(predicate);
}
public DexProgramClass removeLast() {
return classes.removeLast();
}
+
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ return collection.retainAll(collection);
+ }
+
+ @Override
+ public Object[] toArray() {
+ return classes.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] ts) {
+ return classes.toArray(ts);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
index b21f233..63420fe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -4,21 +4,11 @@
package com.android.tools.r8.horizontalclassmerging;
-import java.util.ArrayList;
import java.util.Collection;
public abstract class MultiClassPolicy extends Policy {
/**
- * Remove all groups containing no or only a single class, as there is no point in merging these.
- */
- protected Collection<MergeGroup> removeTrivialGroups(Collection<MergeGroup> groups) {
- assert !(groups instanceof ArrayList);
- groups.removeIf(MergeGroup::isTrivial);
- return groups;
- }
-
- /**
* Apply the multi class policy to a group of program classes.
*
* @param group This is a group of program classes which can currently still be merged.
@@ -27,4 +17,14 @@
* cannot be merged with any other classes they are returned as singleton lists.
*/
public abstract Collection<MergeGroup> apply(MergeGroup group);
+
+ @Override
+ public boolean isMultiClassPolicy() {
+ return true;
+ }
+
+ @Override
+ public MultiClassPolicy asMultiClassPolicy() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
new file mode 100644
index 0000000..067fcd5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
@@ -0,0 +1,33 @@
+// 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.horizontalclassmerging;
+
+import java.util.Collection;
+
+public abstract class MultiClassPolicyWithPreprocessing<T> extends Policy {
+
+ /**
+ * Apply the multi class policy to a group of program classes.
+ *
+ * @param group This is a group of program classes which can currently still be merged.
+ * @param data The result of calling {@link #preprocess(Collection)}.
+ * @return The same collection of program classes split into new groups of candidates which can be
+ * merged. If the policy detects no issues then `group` will be returned unchanged. If classes
+ * cannot be merged with any other classes they are returned as singleton lists.
+ */
+ public abstract Collection<MergeGroup> apply(MergeGroup group, T data);
+
+ public abstract T preprocess(Collection<MergeGroup> groups);
+
+ @Override
+ public boolean isMultiClassPolicyWithPreprocessing() {
+ return true;
+ }
+
+ @Override
+ public MultiClassPolicyWithPreprocessing<?> asMultiClassPolicyWithPreprocessing() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
index b984ed5..e5854fe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
@@ -4,19 +4,78 @@
package com.android.tools.r8.horizontalclassmerging;
+import java.util.ArrayList;
+import java.util.Collection;
+
/**
* The super class of all horizontal class merging policies. Most classes will either implement
* {@link SingleClassPolicy} or {@link MultiClassPolicy}.
*/
public abstract class Policy {
+
/** Counter keeping track of how many classes this policy has removed. For debugging only. */
public int numberOfRemovedClasses;
+ public int numberOfRemovedInterfaces;
+
public void clear() {}
public abstract String getName();
+ public boolean isSingleClassPolicy() {
+ return false;
+ }
+
+ public SingleClassPolicy asSingleClassPolicy() {
+ return null;
+ }
+
+ public boolean isMultiClassPolicy() {
+ return false;
+ }
+
+ public MultiClassPolicy asMultiClassPolicy() {
+ return null;
+ }
+
+ public boolean isMultiClassPolicyWithPreprocessing() {
+ return false;
+ }
+
+ public MultiClassPolicyWithPreprocessing<?> asMultiClassPolicyWithPreprocessing() {
+ return null;
+ }
+
public boolean shouldSkipPolicy() {
return false;
}
+
+ /**
+ * Remove all groups containing no or only a single class, as there is no point in merging these.
+ */
+ protected Collection<MergeGroup> removeTrivialGroups(Collection<MergeGroup> groups) {
+ assert !(groups instanceof ArrayList);
+ groups.removeIf(MergeGroup::isTrivial);
+ return groups;
+ }
+
+ boolean recordRemovedClassesForDebugging(
+ boolean isInterfaceGroup, int previousGroupSize, Collection<MergeGroup> newGroups) {
+ assert previousGroupSize >= 2;
+ int previousNumberOfRemovedClasses = previousGroupSize - 1;
+ int newNumberOfRemovedClasses = 0;
+ for (MergeGroup newGroup : newGroups) {
+ if (newGroup.isNonTrivial()) {
+ newNumberOfRemovedClasses += newGroup.size() - 1;
+ }
+ }
+ assert previousNumberOfRemovedClasses >= newNumberOfRemovedClasses;
+ int change = previousNumberOfRemovedClasses - newNumberOfRemovedClasses;
+ if (isInterfaceGroup) {
+ numberOfRemovedInterfaces += change;
+ } else {
+ numberOfRemovedClasses += change;
+ }
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
index 33b6f7f..c1c4f64 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.horizontalclassmerging;
-import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
@@ -17,15 +17,16 @@
*/
public class PolicyExecutor {
- // TODO(b/165506334): if performing mutable operation ensure that linked lists are used
private void applySingleClassPolicy(SingleClassPolicy policy, LinkedList<MergeGroup> groups) {
Iterator<MergeGroup> i = groups.iterator();
while (i.hasNext()) {
MergeGroup group = i.next();
- int previousNumberOfClasses = group.size();
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
group.removeIf(clazz -> !policy.canMerge(clazz));
- policy.numberOfRemovedClasses += previousNumberOfClasses - group.size();
- if (group.size() < 2) {
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, ImmutableList.of(group));
+ if (group.isTrivial()) {
i.remove();
}
}
@@ -37,11 +38,30 @@
LinkedList<MergeGroup> newGroups = new LinkedList<>();
groups.forEach(
group -> {
- int previousNumberOfClasses = group.size();
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
Collection<MergeGroup> policyGroups = policy.apply(group);
policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
- policy.numberOfRemovedClasses +=
- previousNumberOfClasses - IterableUtils.sumInt(policyGroups, MergeGroup::size);
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, policyGroups);
+ newGroups.addAll(policyGroups);
+ });
+ return newGroups;
+ }
+
+ private <T> LinkedList<MergeGroup> applyMultiClassPolicyWithPreprocessing(
+ MultiClassPolicyWithPreprocessing<T> policy, LinkedList<MergeGroup> groups) {
+ // For each group apply the multi class policy and add all the new groups together.
+ T data = policy.preprocess(groups);
+ LinkedList<MergeGroup> newGroups = new LinkedList<>();
+ groups.forEach(
+ group -> {
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
+ Collection<MergeGroup> policyGroups = policy.apply(group, data);
+ policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, policyGroups);
newGroups.addAll(policyGroups);
});
return newGroups;
@@ -68,11 +88,15 @@
}
timing.begin(policy.getName());
- if (policy instanceof SingleClassPolicy) {
- applySingleClassPolicy((SingleClassPolicy) policy, linkedGroups);
+ if (policy.isSingleClassPolicy()) {
+ applySingleClassPolicy(policy.asSingleClassPolicy(), linkedGroups);
+ } else if (policy.isMultiClassPolicy()) {
+ linkedGroups = applyMultiClassPolicy(policy.asMultiClassPolicy(), linkedGroups);
} else {
- assert policy instanceof MultiClassPolicy;
- linkedGroups = applyMultiClassPolicy((MultiClassPolicy) policy, linkedGroups);
+ assert policy.isMultiClassPolicyWithPreprocessing();
+ linkedGroups =
+ applyMultiClassPolicyWithPreprocessing(
+ policy.asMultiClassPolicyWithPreprocessing(), linkedGroups);
}
timing.end();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
new file mode 100644
index 0000000..01a85ee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -0,0 +1,211 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
+import com.android.tools.r8.horizontalclassmerging.policies.AtMostOneClassInitializer;
+import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups;
+import com.android.tools.r8.horizontalclassmerging.policies.MinimizeInstanceFieldCasts;
+import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
+import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
+import com.android.tools.r8.horizontalclassmerging.policies.NoConstructorCollisions;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDeadEnumLiteMaps;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDeadLocks;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodCollisions;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
+import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
+import com.android.tools.r8.horizontalclassmerging.policies.NoIllegalInlining;
+import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceInitializers;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
+import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
+import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
+import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
+import com.android.tools.r8.horizontalclassmerging.policies.NoNonPrivateVirtualMethods;
+import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
+import com.android.tools.r8.horizontalclassmerging.policies.NoVerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
+import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
+import com.android.tools.r8.horizontalclassmerging.policies.PreventClassMethodAndDefaultMethodCollisions;
+import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
+import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
+import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
+import com.android.tools.r8.horizontalclassmerging.policies.SameMainDexGroup;
+import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
+import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
+import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
+import com.android.tools.r8.horizontalclassmerging.policies.VerifyPolicyAlwaysSatisfied;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class PolicyScheduler {
+
+ public static List<Policy> getPolicies(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Mode mode,
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ return ImmutableList.<Policy>builder()
+ .addAll(getSingleClassPolicies(appView, mode, runtimeTypeCheckInfo))
+ .addAll(getMultiClassPolicies(appView, mode, runtimeTypeCheckInfo))
+ .build();
+ }
+
+ private static List<SingleClassPolicy> getSingleClassPolicies(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Mode mode,
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ ImmutableList.Builder<SingleClassPolicy> builder = ImmutableList.builder();
+
+ addRequiredSingleClassPolicies(appView, builder);
+
+ if (mode.isInitial()) {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ builder.add(
+ new NoDeadEnumLiteMaps(appViewWithLiveness, mode),
+ new NoIllegalInlining(appViewWithLiveness, mode),
+ new NoVerticallyMergedClasses(appViewWithLiveness, mode));
+ } else {
+ assert mode.isFinal();
+ // TODO(b/181846319): Allow constructors, as long as the constructor protos remain unchanged
+ // (in particular, we can't add nulls at constructor call sites).
+ // TODO(b/181846319): Allow virtual methods, as long as they do not require any merging.
+ builder.add(new NoInstanceInitializers(mode), new NoNonPrivateVirtualMethods(mode));
+ }
+
+ if (appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics()) {
+ assert verifySingleClassPoliciesIrrelevantForMergingSynthetics(appView, mode, builder);
+ } else {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ addSingleClassPoliciesForMergingNonSyntheticClasses(
+ appViewWithLiveness, mode, runtimeTypeCheckInfo, builder);
+ }
+
+ return builder.build();
+ }
+
+ private static void addRequiredSingleClassPolicies(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ ImmutableList.Builder<SingleClassPolicy> builder) {
+ builder.add(
+ new CheckSyntheticClasses(appView),
+ new NoKeepRules(appView),
+ new NoClassInitializerWithObservableSideEffects());
+ }
+
+ private static void addSingleClassPoliciesForMergingNonSyntheticClasses(
+ AppView<AppInfoWithLiveness> appView,
+ Mode mode,
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo,
+ ImmutableList.Builder<SingleClassPolicy> builder) {
+ builder.add(
+ new NotMatchedByNoHorizontalClassMerging(appView),
+ new NoAnnotationClasses(),
+ new NoDirectRuntimeTypeChecks(appView, mode, runtimeTypeCheckInfo),
+ new NoEnums(appView),
+ new NoInterfaces(appView, mode),
+ new NoInnerClasses(),
+ new NoInstanceFieldAnnotations(),
+ new NoKotlinMetadata(),
+ new NoNativeMethods(),
+ new NoServiceLoaders(appView));
+ }
+
+ private static boolean verifySingleClassPoliciesIrrelevantForMergingSynthetics(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Mode mode,
+ ImmutableList.Builder<SingleClassPolicy> builder) {
+ List<SingleClassPolicy> policies =
+ ImmutableList.of(
+ new NoAnnotationClasses(),
+ new NoDirectRuntimeTypeChecks(appView, mode),
+ new NoEnums(appView),
+ new NoInterfaces(appView, mode),
+ new NoInnerClasses(),
+ new NoInstanceFieldAnnotations(),
+ new NoKotlinMetadata(),
+ new NoNativeMethods(),
+ new NoServiceLoaders(appView));
+ policies.stream().map(VerifyPolicyAlwaysSatisfied::new).forEach(builder::add);
+ return true;
+ }
+
+ private static List<Policy> getMultiClassPolicies(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Mode mode,
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ ImmutableList.Builder<Policy> builder = ImmutableList.builder();
+
+ addRequiredMultiClassPolicies(appView, mode, builder);
+
+ if (!appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics()) {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ addMultiClassPoliciesForMergingNonSyntheticClasses(
+ appViewWithLiveness, runtimeTypeCheckInfo, builder);
+ }
+
+ if (mode.isInitial()) {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ builder.add(
+ new AllInstantiatedOrUninstantiated(appViewWithLiveness, mode),
+ new PreserveMethodCharacteristics(appViewWithLiveness, mode),
+ new MinimizeInstanceFieldCasts());
+ } else {
+ assert mode.isFinal();
+ // TODO(b/185472598): Add support for merging class initializers with dex code.
+ builder.add(new AtMostOneClassInitializer(mode), new NoConstructorCollisions(appView, mode));
+ }
+
+ addMultiClassPoliciesForInterfaceMerging(appView, mode, builder);
+
+ return builder.add(new LimitGroups(appView)).build();
+ }
+
+ private static void addRequiredMultiClassPolicies(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Mode mode,
+ ImmutableList.Builder<Policy> builder) {
+ builder.add(
+ new CheckAbstractClasses(appView),
+ new NoClassAnnotationCollisions(),
+ new SameFeatureSplit(appView),
+ new SameInstanceFields(appView, mode),
+ new SameMainDexGroup(appView),
+ new SameNestHost(appView),
+ new SameParentClass(),
+ new SyntheticItemsPolicy(appView, mode),
+ new RespectPackageBoundaries(appView),
+ new PreventClassMethodAndDefaultMethodCollisions(appView));
+ }
+
+ private static void addMultiClassPoliciesForMergingNonSyntheticClasses(
+ AppView<AppInfoWithLiveness> appView,
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo,
+ ImmutableList.Builder<Policy> builder) {
+ builder.add(
+ new NoDeadLocks(appView), new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo));
+ }
+
+ private static void addMultiClassPoliciesForInterfaceMerging(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Mode mode,
+ ImmutableList.Builder<Policy> builder) {
+ builder.add(
+ new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView, mode),
+ new NoDefaultInterfaceMethodMerging(appView, mode),
+ new NoDefaultInterfaceMethodCollisions(appView, mode));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
index b0757c5..db2d8eb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
@@ -13,4 +13,14 @@
* @return {@code false} if the class should not be merged, otherwise {@code true}.
*/
public abstract boolean canMerge(DexProgramClass program);
+
+ @Override
+ public boolean isSingleClassPolicy() {
+ return true;
+ }
+
+ @Override
+ public SingleClassPolicy asSingleClassPolicy() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 53ca8c8..442489d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.TreeFixerBase;
import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
import com.android.tools.r8.shaking.AnnotationFixer;
@@ -115,8 +117,8 @@
public HorizontalClassMergerGraphLens fixupTypeReferences() {
List<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
-
- classes.forEach(this::fixupProgramClassSuperType);
+ classes.forEach(this::fixupAttributes);
+ classes.forEach(this::fixupProgramClassSuperTypes);
SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView);
// TODO(b/170078037): parallelize this code segment.
for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
@@ -127,8 +129,24 @@
return lens;
}
- private void fixupProgramClassSuperType(DexProgramClass clazz) {
+ private void fixupAttributes(DexProgramClass clazz) {
+ if (clazz.hasEnclosingMethodAttribute()) {
+ EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute();
+ if (mergedClasses.hasBeenMergedIntoDifferentType(
+ enclosingMethodAttribute.getEnclosingType())) {
+ clazz.clearEnclosingMethodAttribute();
+ } else {
+ clazz.setEnclosingMethodAttribute(fixupEnclosingMethodAttribute(enclosingMethodAttribute));
+ }
+ }
+ clazz.setInnerClasses(fixupInnerClassAttributes(clazz.getInnerClasses()));
+ clazz.setNestHostAttribute(fixupNestHost(clazz.getNestHostClassAttribute()));
+ clazz.setNestMemberAttributes(fixupNestMemberAttributes(clazz.getNestMembersClassAttributes()));
+ }
+
+ private void fixupProgramClassSuperTypes(DexProgramClass clazz) {
clazz.superType = fixupType(clazz.superType);
+ clazz.setInterfaces(fixupInterfaces(clazz, clazz.getInterfaces()));
}
private BiMap<DexMethodSignature, DexMethodSignature> fixupProgramClass(
@@ -197,10 +215,6 @@
private void fixupInterfaceClass(DexProgramClass iface) {
Set<DexMethodSignature> newDirectMethods = new LinkedHashSet<>();
-
- assert iface.superType == dexItemFactory.objectType;
- iface.superType = mergedClasses.getMergeTargetOrDefault(iface.superType);
-
iface
.getMethodCollection()
.replaceDirectMethods(method -> fixupDirectMethod(newDirectMethods, method));
@@ -210,6 +224,16 @@
lensBuilder.commitPendingUpdates();
}
+ private DexTypeList fixupInterfaces(DexProgramClass clazz, DexTypeList interfaceTypes) {
+ Set<DexType> seen = Sets.newIdentityHashSet();
+ return interfaceTypes.map(
+ interfaceType -> {
+ DexType rewrittenInterfaceType = mapClassType(interfaceType);
+ assert rewrittenInterfaceType != clazz.getType();
+ return seen.add(rewrittenInterfaceType) ? rewrittenInterfaceType : null;
+ });
+ }
+
private DexEncodedMethod fixupProgramMethod(
DexMethod newMethodReference, DexEncodedMethod method) {
DexMethod originalMethodReference = method.getReference();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 7a13785..394c884 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -53,8 +53,8 @@
public static class Builder {
private final List<ProgramMethod> methods = new ArrayList<>();
- public Builder add(ProgramMethod constructor) {
- methods.add(constructor);
+ public Builder add(ProgramMethod method) {
+ methods.add(method);
return this;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java
index 99d8f48..1b8a318 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.CfVersionUtils;
@@ -58,7 +59,13 @@
}
}
- public CfCode synthesizeCode(DexType originalHolder) {
+ public Code getOrCreateCode(DexType originalHolder) {
+ assert !staticClassInitializers.isEmpty();
+
+ if (staticClassInitializers.size() == 1) {
+ return staticClassInitializers.get(0).getCode();
+ }
+
// Building the instructions will adjust maxStack and maxLocals. Build it here before invoking
// the CfCode constructor to ensure that the value passed in is the updated values.
List<CfInstruction> instructions = buildInstructions();
@@ -83,6 +90,11 @@
}
public CfVersion getCfVersion() {
+ if (staticClassInitializers.size() == 1) {
+ DexEncodedMethod method = staticClassInitializers.get(0);
+ return method.hasClassFileVersion() ? method.getClassFileVersion() : null;
+ }
+ assert staticClassInitializers.stream().allMatch(method -> method.getCode().isCfCode());
return CfVersionUtils.max(staticClassInitializers);
}
@@ -92,7 +104,6 @@
public void add(DexEncodedMethod method) {
assert method.isClassInitializer();
assert method.hasCode();
- assert method.getCode().isCfCode();
staticClassInitializers.add(method);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java
index 38150db..e0dc0fe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -13,7 +14,11 @@
private final AppView<AppInfoWithLiveness> appView;
- public AllInstantiatedOrUninstantiated(AppView<AppInfoWithLiveness> appView) {
+ public AllInstantiatedOrUninstantiated(AppView<AppInfoWithLiveness> appView, Mode mode) {
+ // This policy is only used to prevent that horizontal class merging regresses the
+ // uninstantiated type optimization. Since there won't be any IR processing after the final
+ // round of horizontal class merging, there is no need to use the policy.
+ assert mode.isInitial();
this.appView = appView;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.java
new file mode 100644
index 0000000..a2b5887
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+
+public class AtMostOneClassInitializer extends AtMostOneClassThatMatchesPolicy {
+
+ public AtMostOneClassInitializer(Mode mode) {
+ // TODO(b/182124475): Allow merging groups with multiple <clinit> methods in the final round of
+ // merging.
+ assert mode.isFinal();
+ }
+
+ @Override
+ boolean atMostOneOf(DexProgramClass clazz) {
+ return clazz.hasClassInitializer();
+ }
+
+ @Override
+ public String getName() {
+ return "AtMostOneClassInitializer";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassThatMatchesPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassThatMatchesPolicy.java
new file mode 100644
index 0000000..c73675f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassThatMatchesPolicy.java
@@ -0,0 +1,47 @@
+// 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.horizontalclassmerging.policies;
+
+import static com.android.tools.r8.utils.IteratorUtils.createCircularIterator;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public abstract class AtMostOneClassThatMatchesPolicy extends MultiClassPolicy {
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group) {
+ // Create a new merge group for each class that we want at most one of.
+ List<MergeGroup> newGroups = new LinkedList<>();
+ for (DexProgramClass clazz : group) {
+ if (atMostOneOf(clazz)) {
+ newGroups.add(new MergeGroup(clazz));
+ }
+ }
+
+ // If there were at most one class with that we could have at most one of, then just return the
+ // original merge group.
+ if (newGroups.size() <= 1) {
+ return ImmutableList.of(group);
+ }
+
+ // Otherwise, fill up the new merge groups with the remaining classes.
+ Iterator<MergeGroup> newGroupsIterator = createCircularIterator(newGroups);
+ for (DexProgramClass clazz : group) {
+ if (!atMostOneOf(clazz)) {
+ newGroupsIterator.next().add(clazz);
+ }
+ }
+ return removeTrivialGroups(newGroups);
+ }
+
+ abstract boolean atMostOneOf(DexProgramClass clazz);
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
new file mode 100644
index 0000000..8c08712
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
@@ -0,0 +1,39 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
+
+public class CheckSyntheticClasses extends SingleClassPolicy {
+
+ private final HorizontalClassMergerOptions options;
+ private final SyntheticItems syntheticItems;
+
+ public CheckSyntheticClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ this.options = appView.options().horizontalClassMergerOptions();
+ this.syntheticItems = appView.getSyntheticItems();
+ }
+
+ @Override
+ public boolean canMerge(DexProgramClass clazz) {
+ if (!options.isSyntheticMergingEnabled() && syntheticItems.isSyntheticClass(clazz)) {
+ return false;
+ }
+ if (options.isRestrictedToSynthetics() && !syntheticItems.isSyntheticClass(clazz)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "CheckSyntheticClasses";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeInstanceFieldCasts.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeInstanceFieldCasts.java
index 1ef2f6a..0242766 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeInstanceFieldCasts.java
@@ -17,7 +17,7 @@
import java.util.List;
import java.util.Map;
-public class MinimizeFieldCasts extends MultiClassPolicy {
+public class MinimizeInstanceFieldCasts extends MultiClassPolicy {
@Override
public final Collection<MergeGroup> apply(MergeGroup group) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
index 1a96901..482a0e0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
@@ -4,42 +4,13 @@
package com.android.tools.r8.horizontalclassmerging.policies;
-import static com.android.tools.r8.utils.IteratorUtils.createCircularIterator;
-
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
-import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import com.google.common.collect.ImmutableList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-public class NoClassAnnotationCollisions extends MultiClassPolicy {
+public class NoClassAnnotationCollisions extends AtMostOneClassThatMatchesPolicy {
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
- // Create a new merge group for each class that has annotations.
- List<MergeGroup> newGroups = new LinkedList<>();
- for (DexProgramClass clazz : group) {
- if (clazz.hasAnnotations()) {
- newGroups.add(new MergeGroup(clazz));
- }
- }
-
- // If there were at most one class with annotations, then just return the original merge group.
- if (newGroups.size() <= 1) {
- return ImmutableList.of(group);
- }
-
- // Otherwise, fill up the new merge groups with the classes that do not have annotations.
- Iterator<MergeGroup> newGroupsIterator = createCircularIterator(newGroups);
- for (DexProgramClass clazz : group) {
- if (!clazz.hasAnnotations()) {
- newGroupsIterator.next().add(clazz);
- }
- }
- return removeTrivialGroups(newGroups);
+ boolean atMostOneOf(DexProgramClass clazz) {
+ return clazz.hasAnnotations();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
new file mode 100644
index 0000000..5b058c9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
@@ -0,0 +1,150 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * In the final round, we're not allowed to resolve constructor collisions by appending null
+ * arguments to constructor calls.
+ *
+ * <p>As an example, if a class in the program declares the constructors {@code <init>(A)} and
+ * {@code <init>(B)}, the classes A and B must not be merged.
+ *
+ * <p>To avoid collisions of this kind, we run over all the classes in the program, and apply the
+ * current set of merge groups to the constructor signatures of each class. Then, in case of a
+ * collision, we extract all the mapped types from the constructor signatures, and prevent merging
+ * of these types.
+ */
+public class NoConstructorCollisions extends MultiClassPolicyWithPreprocessing<Set<DexType>> {
+
+ private final AppView<?> appView;
+ private final DexItemFactory dexItemFactory;
+
+ public NoConstructorCollisions(AppView<?> appView, Mode mode) {
+ assert mode.isFinal();
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ /**
+ * Removes the classes in {@param collisionResolution} from {@param group}, and returns the new
+ * filtered group.
+ */
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group, Set<DexType> collisionResolution) {
+ MergeGroup newGroup =
+ new MergeGroup(
+ Iterables.filter(group, clazz -> !collisionResolution.contains(clazz.getType())));
+ return newGroup.isTrivial() ? Collections.emptyList() : ListUtils.newLinkedList(newGroup);
+ }
+
+ /**
+ * Computes the set of classes that must not be merged, because the merging of these classes could
+ * lead to constructor collisions.
+ */
+ @Override
+ public Set<DexType> preprocess(Collection<MergeGroup> groups) {
+ // Build a mapping from types to groups.
+ Map<DexType, MergeGroup> groupsByType = new IdentityHashMap<>();
+ for (MergeGroup group : groups) {
+ for (DexProgramClass clazz : group) {
+ groupsByType.put(clazz.getType(), group);
+ }
+ }
+
+ // Find the set of types that must not be merged, because they could lead to a constructor
+ // collision.
+ Set<DexType> collisionResolution = Sets.newIdentityHashSet();
+ WorkList<DexProgramClass> workList = WorkList.newIdentityWorkList(appView.appInfo().classes());
+ while (workList.hasNext()) {
+ // Iterate over all the instance initializers of the current class. If the current class is in
+ // a merge group, we must include all constructors of the entire merge group.
+ DexProgramClass current = workList.next();
+ Iterable<DexProgramClass> group =
+ groupsByType.containsKey(current.getType())
+ ? groupsByType.get(current.getType())
+ : IterableUtils.singleton(current);
+ Set<DexMethod> seen = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : group) {
+ for (DexEncodedMethod method :
+ clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) {
+ // Rewrite the constructor reference using the current merge groups.
+ DexMethod newReference = rewriteReference(method.getReference(), groupsByType);
+ if (!seen.add(newReference)) {
+ // Found a collision. Block all referenced types from being merged.
+ for (DexType type : method.getProto().getBaseTypes(dexItemFactory)) {
+ if (type.isClassType() && groupsByType.containsKey(type)) {
+ collisionResolution.add(type);
+ }
+ }
+ }
+ }
+ }
+ workList.markAsSeen(group);
+ }
+ return collisionResolution;
+ }
+
+ private DexProto rewriteProto(DexProto proto, Map<DexType, MergeGroup> groups) {
+ DexType[] parameters =
+ ArrayUtils.map(
+ DexType[].class,
+ proto.getParameters().values,
+ parameter -> rewriteType(parameter, groups));
+ return dexItemFactory.createProto(rewriteType(proto.getReturnType(), groups), parameters);
+ }
+
+ private DexMethod rewriteReference(DexMethod method, Map<DexType, MergeGroup> groups) {
+ return dexItemFactory.createMethod(
+ rewriteType(method.getHolderType(), groups),
+ rewriteProto(method.getProto(), groups),
+ method.getName());
+ }
+
+ private DexType rewriteType(DexType type, Map<DexType, MergeGroup> groups) {
+ if (type.isArrayType()) {
+ DexType baseType = type.toBaseType(dexItemFactory);
+ DexType rewrittenBaseType = rewriteType(baseType, groups);
+ if (rewrittenBaseType == baseType) {
+ return type;
+ }
+ return type.replaceBaseType(rewrittenBaseType, dexItemFactory);
+ }
+ if (type.isClassType()) {
+ if (!groups.containsKey(type)) {
+ return type;
+ }
+ return groups.get(type).getClasses().getFirst().getType();
+ }
+ assert type.isPrimitiveType() || type.isVoidType();
+ return type;
+ }
+
+ @Override
+ public String getName() {
+ return "NoConstructorCollisions";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadEnumLiteMaps.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadEnumLiteMaps.java
index 3706d0a..8d1205d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadEnumLiteMaps.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadEnumLiteMaps.java
@@ -7,6 +7,7 @@
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.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -17,7 +18,10 @@
private final Set<DexType> deadEnumLiteMaps;
- public NoDeadEnumLiteMaps(AppView<AppInfoWithLiveness> appView) {
+ public NoDeadEnumLiteMaps(AppView<AppInfoWithLiveness> appView, Mode mode) {
+ // This policy is only relevant for the initial round of class merging, since the dead enum lite
+ // maps have been removed from the application when the final round of class merging runs.
+ assert mode.isInitial();
this.deadEnumLiteMaps =
appView.withProtoEnumShrinker(
EnumLiteProtoShrinker::getDeadEnumLiteMaps, Collections.emptySet());
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
similarity index 91%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
index e1cb9a7..576e0ef 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
@@ -14,10 +14,10 @@
import java.util.Iterator;
import java.util.LinkedList;
-public class DontMergeSynchronizedClasses extends MultiClassPolicy {
+public class NoDeadLocks extends MultiClassPolicy {
private final AppView<AppInfoWithLiveness> appView;
- public DontMergeSynchronizedClasses(AppView<AppInfoWithLiveness> appView) {
+ public NoDeadLocks(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
}
@@ -59,6 +59,6 @@
@Override
public String getName() {
- return "DontMergeSynchronizedClasses";
+ return "NoDeadLocks";
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
new file mode 100644
index 0000000..b091242
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
@@ -0,0 +1,343 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+
+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.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodCollisions.InterfaceInfo;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This policy prevents that interface merging changes semantics of invoke-interface/invoke-virtual
+ * instructions that dispatch to default interface methods.
+ *
+ * <p>As a simple example, consider the following snippet of code. If we merge interfaces I and K,
+ * then we effectively add the default interface method K.m() to I, which would change the semantics
+ * of calls to A.m().
+ *
+ * <pre>
+ * interface I {}
+ * interface J {
+ * default void m() { print("J"); }
+ * }
+ * interface K {
+ * default void m() { print("K"); }
+ * }
+ * class A implements I, J {}
+ * </pre>
+ *
+ * Note that we also cannot merge I with K, even if K does not declare any methods directly:
+ *
+ * <pre>
+ * interface K0 {
+ * default void m() { print("K"); }
+ * }
+ * interface K extends K0 {}
+ * </pre>
+ *
+ * Also, note that this is not a problem if class A overrides void m().
+ */
+public class NoDefaultInterfaceMethodCollisions
+ extends MultiClassPolicyWithPreprocessing<Map<DexType, InterfaceInfo>> {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Mode mode;
+
+ public NoDefaultInterfaceMethodCollisions(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+ this.appView = appView;
+ this.mode = mode;
+ }
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group, Map<DexType, InterfaceInfo> infos) {
+ if (!group.isInterfaceGroup()) {
+ return ImmutableList.of(group);
+ }
+
+ // For each interface I in the group, check that each (non-interface) subclass of I does not
+ // inherit a default method that is also declared by another interface J in the merge group.
+ //
+ // Note that the primary piece of work is carried out in the preprocess() method
+ //
+ // TODO(b/173990042): Consider forming multiple groups instead of just filtering. In practice,
+ // this rarely leads to much filtering, though, since the use of default methods is somewhat
+ // limited.
+ MergeGroup newGroup = new MergeGroup();
+ for (DexProgramClass clazz : group) {
+ Set<DexMethod> newDefaultMethodsAddedToClassByMerge =
+ computeNewDefaultMethodsAddedToClassByMerge(clazz, group, infos);
+ if (isSafeToAddDefaultMethodsToClass(clazz, newDefaultMethodsAddedToClassByMerge, infos)) {
+ newGroup.add(clazz);
+ }
+ }
+ return newGroup.isTrivial() ? Collections.emptyList() : ListUtils.newLinkedList(newGroup);
+ }
+
+ private Set<DexMethod> computeNewDefaultMethodsAddedToClassByMerge(
+ DexProgramClass clazz, MergeGroup group, Map<DexType, InterfaceInfo> infos) {
+ // Run through the other classes in the merge group, and add the default interface methods that
+ // they declare (or inherit from a super interface) to a set.
+ Set<DexMethod> newDefaultMethodsAddedToClassByMerge = Sets.newIdentityHashSet();
+ for (DexProgramClass other : group) {
+ if (other != clazz) {
+ Collection<Set<DexMethod>> inheritedDefaultMethodsFromOther =
+ infos.get(other.getType()).getInheritedDefaultMethods().values();
+ inheritedDefaultMethodsFromOther.forEach(newDefaultMethodsAddedToClassByMerge::addAll);
+ }
+ }
+ return newDefaultMethodsAddedToClassByMerge;
+ }
+
+ private boolean isSafeToAddDefaultMethodsToClass(
+ DexProgramClass clazz,
+ Set<DexMethod> newDefaultMethodsAddedToClassByMerge,
+ Map<DexType, InterfaceInfo> infos) {
+ // Check if there is a subclass of this interface, which inherits a default interface method
+ // that would also be added by to this interface by merging the interfaces in the group.
+ Map<DexMethodSignature, Set<DexMethod>> defaultMethodsInheritedBySubclassesOfClass =
+ infos.get(clazz.getType()).getDefaultMethodsInheritedBySubclasses();
+ for (DexMethod newDefaultMethodAddedToClassByMerge : newDefaultMethodsAddedToClassByMerge) {
+ Set<DexMethod> defaultMethodsInheritedBySubclassesOfClassWithSameSignature =
+ defaultMethodsInheritedBySubclassesOfClass.getOrDefault(
+ newDefaultMethodAddedToClassByMerge.getSignature(), emptySet());
+ // Look for a method different from the method we're adding.
+ for (DexMethod method : defaultMethodsInheritedBySubclassesOfClassWithSameSignature) {
+ if (method != newDefaultMethodAddedToClassByMerge) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Map<DexType, InterfaceInfo> preprocess(Collection<MergeGroup> groups) {
+ SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+ Collection<DexProgramClass> classesOfInterest = computeClassesOfInterest(subtypingInfo);
+ Map<DexType, DexMethodSignatureSet> inheritedClassMethodsPerClass =
+ computeInheritedClassMethodsPerProgramClass(classesOfInterest);
+ Map<DexType, Map<DexMethodSignature, Set<DexMethod>>> inheritedDefaultMethodsPerClass =
+ computeInheritedDefaultMethodsPerProgramType(
+ classesOfInterest, inheritedClassMethodsPerClass);
+
+ // Finally, do a bottom-up traversal, pushing the inherited default methods upwards.
+ Map<DexType, Map<DexMethodSignature, Set<DexMethod>>>
+ defaultMethodsInheritedBySubclassesPerClass =
+ computeDefaultMethodsInheritedBySubclassesPerProgramClass(
+ classesOfInterest, inheritedDefaultMethodsPerClass, subtypingInfo);
+
+ // Store the computed information for each interface that is subject to merging.
+ Map<DexType, InterfaceInfo> infos = new IdentityHashMap<>();
+ for (MergeGroup group : groups) {
+ if (group.isInterfaceGroup()) {
+ for (DexProgramClass clazz : group) {
+ infos.put(
+ clazz.getType(),
+ new InterfaceInfo(
+ inheritedDefaultMethodsPerClass.getOrDefault(clazz.getType(), emptyMap()),
+ defaultMethodsInheritedBySubclassesPerClass.getOrDefault(
+ clazz.getType(), emptyMap())));
+ }
+ }
+ }
+ return infos;
+ }
+
+ /** Returns the set of program classes that must be considered during preprocessing. */
+ private Collection<DexProgramClass> computeClassesOfInterest(SubtypingInfo subtypingInfo) {
+ // TODO(b/173990042): Limit result to the set of classes that are in the same as one of
+ // the interfaces that is subject to merging.
+ return appView.appInfo().classes();
+ }
+
+ /**
+ * For each class, computes the (transitive) set of virtual methods that is declared on the class
+ * itself or one of its (non-interface) super classes.
+ */
+ private Map<DexType, DexMethodSignatureSet> computeInheritedClassMethodsPerProgramClass(
+ Collection<DexProgramClass> classesOfInterest) {
+ Map<DexType, DexMethodSignatureSet> inheritedClassMethodsPerClass = new IdentityHashMap<>();
+ TopDownClassHierarchyTraversal.forAllClasses(appView)
+ .excludeInterfaces()
+ .visit(
+ classesOfInterest,
+ clazz -> {
+ DexMethodSignatureSet classMethods =
+ DexMethodSignatureSet.create(
+ inheritedClassMethodsPerClass.getOrDefault(
+ clazz.getSuperType(), DexMethodSignatureSet.empty()));
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ classMethods.add(method.getSignature());
+ }
+ inheritedClassMethodsPerClass.put(clazz.getType(), classMethods);
+ });
+ inheritedClassMethodsPerClass
+ .keySet()
+ .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
+ return inheritedClassMethodsPerClass;
+ }
+
+ /**
+ * For each class or interface, computes the (transitive) set of virtual methods that is declared
+ * on the class itself or one of its (non-interface) super classes.
+ */
+ private Map<DexType, Map<DexMethodSignature, Set<DexMethod>>>
+ computeInheritedDefaultMethodsPerProgramType(
+ Collection<DexProgramClass> classesOfInterest,
+ Map<DexType, DexMethodSignatureSet> inheritedClassMethodsPerClass) {
+ Map<DexType, Map<DexMethodSignature, Set<DexMethod>>> inheritedDefaultMethodsPerType =
+ new IdentityHashMap<>();
+ TopDownClassHierarchyTraversal.forAllClasses(appView)
+ .visit(
+ classesOfInterest,
+ clazz -> {
+ // Compute the set of default method signatures that this class inherits from its
+ // super class and interfaces.
+ Map<DexMethodSignature, Set<DexMethod>> inheritedDefaultMethods = new HashMap<>();
+ for (DexType supertype : clazz.allImmediateSupertypes()) {
+ Map<DexMethodSignature, Set<DexMethod>> inheritedDefaultMethodsFromSuperType =
+ inheritedDefaultMethodsPerType.getOrDefault(supertype, emptyMap());
+ inheritedDefaultMethodsFromSuperType.forEach(
+ (signature, methods) ->
+ inheritedDefaultMethods
+ .computeIfAbsent(signature, ignore -> Sets.newIdentityHashSet())
+ .addAll(methods));
+ }
+
+ // If this is an interface, also include the default methods it declares.
+ if (clazz.isInterface()) {
+ for (DexEncodedMethod method :
+ clazz.virtualMethods(DexEncodedMethod::isDefaultMethod)) {
+ inheritedDefaultMethods
+ .computeIfAbsent(method.getSignature(), ignore -> Sets.newIdentityHashSet())
+ .add(method.getReference());
+ }
+ }
+
+ // Remove all default methods that are declared as (non-interface) class methods on
+ // the current class.
+ inheritedDefaultMethods
+ .keySet()
+ .removeAll(
+ inheritedClassMethodsPerClass.getOrDefault(
+ clazz.getType(), DexMethodSignatureSet.empty()));
+
+ if (!inheritedDefaultMethods.isEmpty()) {
+ inheritedDefaultMethodsPerType.put(clazz.getType(), inheritedDefaultMethods);
+ }
+ });
+ inheritedDefaultMethodsPerType
+ .keySet()
+ .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
+ return inheritedDefaultMethodsPerType;
+ }
+
+ /**
+ * Performs a bottom-up traversal of the hierarchy, where the inherited default methods of each
+ * class are pushed upwards. This accumulates the set of default methods that are inherited by all
+ * subclasses of a given interface.
+ */
+ private Map<DexType, Map<DexMethodSignature, Set<DexMethod>>>
+ computeDefaultMethodsInheritedBySubclassesPerProgramClass(
+ Collection<DexProgramClass> classesOfInterest,
+ Map<DexType, Map<DexMethodSignature, Set<DexMethod>>> inheritedDefaultMethodsPerClass,
+ SubtypingInfo subtypingInfo) {
+ // Copy the map from classes to their inherited default methods.
+ Map<DexType, Map<DexMethodSignature, Set<DexMethod>>>
+ defaultMethodsInheritedBySubclassesPerClass =
+ MapUtils.clone(
+ inheritedDefaultMethodsPerClass,
+ new HashMap<>(),
+ outerValue ->
+ MapUtils.clone(outerValue, new HashMap<>(), SetUtils::newIdentityHashSet));
+ BottomUpClassHierarchyTraversal.forProgramClasses(appView, subtypingInfo)
+ .visit(
+ classesOfInterest,
+ clazz -> {
+ // Push the current class' default methods upwards to all super classes.
+ Map<DexMethodSignature, Set<DexMethod>> defaultMethodsToPropagate =
+ defaultMethodsInheritedBySubclassesPerClass.getOrDefault(
+ clazz.getType(), emptyMap());
+ for (DexType supertype : clazz.allImmediateSupertypes()) {
+ Map<DexMethodSignature, Set<DexMethod>>
+ defaultMethodsInheritedBySubclassesForSupertype =
+ defaultMethodsInheritedBySubclassesPerClass.computeIfAbsent(
+ supertype, ignore -> new HashMap<>());
+ defaultMethodsToPropagate.forEach(
+ (signature, methods) ->
+ defaultMethodsInheritedBySubclassesForSupertype
+ .computeIfAbsent(signature, ignore -> Sets.newIdentityHashSet())
+ .addAll(methods));
+ }
+ });
+ defaultMethodsInheritedBySubclassesPerClass
+ .keySet()
+ .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
+ return defaultMethodsInheritedBySubclassesPerClass;
+ }
+
+ @Override
+ public String getName() {
+ return "NoDefaultInterfaceMethodCollisions";
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return !appView.options().horizontalClassMergerOptions().isInterfaceMergingEnabled(mode);
+ }
+
+ static class InterfaceInfo {
+
+ // The set of default interface methods (grouped by signature) that this interface declares or
+ // inherits from one of its (transitive) super interfaces.
+ private final Map<DexMethodSignature, Set<DexMethod>> inheritedDefaultMethods;
+
+ // The set of default interface methods (grouped by signature) that subclasses of this interface
+ // inherits from one of its (transitively) implemented super interfaces.
+ private final Map<DexMethodSignature, Set<DexMethod>> defaultMethodsInheritedBySubclasses;
+
+ InterfaceInfo(
+ Map<DexMethodSignature, Set<DexMethod>> inheritedDefaultMethods,
+ Map<DexMethodSignature, Set<DexMethod>> defaultMethodsInheritedBySubclasses) {
+ this.inheritedDefaultMethods = inheritedDefaultMethods;
+ this.defaultMethodsInheritedBySubclasses = defaultMethodsInheritedBySubclasses;
+ }
+
+ Map<DexMethodSignature, Set<DexMethod>> getInheritedDefaultMethods() {
+ return inheritedDefaultMethods;
+ }
+
+ Map<DexMethodSignature, Set<DexMethod>> getDefaultMethodsInheritedBySubclasses() {
+ return defaultMethodsInheritedBySubclasses;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodMerging.java
new file mode 100644
index 0000000..ac73202
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodMerging.java
@@ -0,0 +1,84 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * For interfaces, we cannot introduce an instance field `int $r8$classId`. Therefore, we can't
+ * merge two interfaces that declare the same default interface method.
+ *
+ * <p>This policy attempts to split a merge group consisting of interfaces into smaller merge groups
+ * such that each pairs of interfaces in each merge group does not have conflicting default
+ * interface methods.
+ */
+public class NoDefaultInterfaceMethodMerging extends MultiClassPolicy {
+
+ private final Mode mode;
+ private final InternalOptions options;
+
+ public NoDefaultInterfaceMethodMerging(AppView<?> appView, Mode mode) {
+ this.mode = mode;
+ this.options = appView.options();
+ }
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group) {
+ if (!group.isInterfaceGroup()) {
+ return ListUtils.newLinkedList(group);
+ }
+
+ // Split the group into smaller groups such that no default methods collide.
+ Map<MergeGroup, DexMethodSignatureSet> newGroups = new LinkedHashMap<>();
+ for (DexProgramClass clazz : group) {
+ addClassToGroup(clazz, newGroups);
+ }
+
+ return removeTrivialGroups(Lists.newLinkedList(newGroups.keySet()));
+ }
+
+ private void addClassToGroup(
+ DexProgramClass clazz, Map<MergeGroup, DexMethodSignatureSet> newGroups) {
+ DexMethodSignatureSet classSignatures = DexMethodSignatureSet.create();
+ classSignatures.addAllMethods(clazz.virtualMethods(DexEncodedMethod::isDefaultMethod));
+
+ // Find a group that does not have any collisions with `clazz`.
+ for (Entry<MergeGroup, DexMethodSignatureSet> entry : newGroups.entrySet()) {
+ MergeGroup group = entry.getKey();
+ DexMethodSignatureSet groupSignatures = entry.getValue();
+ if (!groupSignatures.containsAnyOf(classSignatures)) {
+ groupSignatures.addAll(classSignatures);
+ group.add(clazz);
+ return;
+ }
+ }
+
+ // Else create a new group.
+ newGroups.put(new MergeGroup(clazz), classSignatures);
+ }
+
+ @Override
+ public String getName() {
+ return "NoDefaultInterfaceMethodMerging";
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return !options.horizontalClassMergerOptions().isInterfaceMergingEnabled(mode);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
index 17659f8..d330434 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
@@ -4,19 +4,39 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.InternalOptions;
public class NoDirectRuntimeTypeChecks extends SingleClassPolicy {
- private final RuntimeTypeCheckInfo runtimeTypeCheckInfo;
- public NoDirectRuntimeTypeChecks(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ private final InternalOptions options;
+ private final RuntimeTypeCheckInfo runtimeTypeCheckInfo;
+ private final SyntheticItems syntheticItems;
+
+ public NoDirectRuntimeTypeChecks(AppView<?> appView, Mode mode) {
+ this(appView, mode, null);
+ }
+
+ public NoDirectRuntimeTypeChecks(
+ AppView<?> appView, Mode mode, RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ assert runtimeTypeCheckInfo != null || mode.isFinal();
+ this.options = appView.options();
this.runtimeTypeCheckInfo = runtimeTypeCheckInfo;
+ this.syntheticItems = appView.getSyntheticItems();
}
@Override
public boolean canMerge(DexProgramClass clazz) {
+ if (runtimeTypeCheckInfo == null) {
+ assert syntheticItems.isSyntheticClass(clazz)
+ : "Expected synthetic, got: " + clazz.getTypeName();
+ return true;
+ }
return !runtimeTypeCheckInfo.isRuntimeCheckType(clazz);
}
@@ -24,4 +44,9 @@
public String getName() {
return "NoDirectRuntimeTypeChecks";
}
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return options.horizontalClassMergerOptions().isIgnoreRuntimeTypeChecksForTestingEnabled();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java
similarity index 80%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java
index e6e67e0..fce04fa 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java
@@ -9,16 +9,20 @@
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.Iterables;
-public class DontInlinePolicy extends SingleClassPolicy {
+public class NoIllegalInlining extends SingleClassPolicy {
private final AppView<AppInfoWithLiveness> appView;
- public DontInlinePolicy(AppView<AppInfoWithLiveness> appView) {
+ public NoIllegalInlining(AppView<AppInfoWithLiveness> appView, Mode mode) {
+ // This policy is only relevant for the first round of horizontal class merging, since the final
+ // round of horizontal class merging may not require any inlining.
+ assert mode.isInitial();
this.appView = appView;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
index 12b6ba4..bd5b1da 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
@@ -54,7 +54,8 @@
cache.put(type, true);
return true;
}
- if (runtimeTypeCheckInfo.isRuntimeCheckType(clazz.asProgramClass())) {
+ if (runtimeTypeCheckInfo == null
+ || runtimeTypeCheckInfo.isRuntimeCheckType(clazz.asProgramClass())) {
cache.put(type, true);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java
new file mode 100644
index 0000000..0acfe13
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java
@@ -0,0 +1,29 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+
+public class NoInstanceInitializers extends SingleClassPolicy {
+
+ public NoInstanceInitializers(Mode mode) {
+ // TODO(b/181846319): Allow constructors, as long as the constructor protos remain unchanged
+ // (in particular, we can't add nulls at constructor call sites).
+ assert mode.isFinal();
+ }
+
+ @Override
+ public boolean canMerge(DexProgramClass clazz) {
+ return !clazz.getMethodCollection().hasDirectMethods(DexEncodedMethod::isInstanceInitializer);
+ }
+
+ @Override
+ public String getName() {
+ return "NoInstanceInitializers";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
index d6032e7..79b6601 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
@@ -4,14 +4,31 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
public class NoInterfaces extends SingleClassPolicy {
+ private final Mode mode;
+ private final HorizontalClassMergerOptions options;
+
+ public NoInterfaces(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+ this.mode = mode;
+ this.options = appView.options().horizontalClassMergerOptions();
+ }
+
@Override
- public boolean canMerge(DexProgramClass program) {
- return !program.isInterface();
+ public boolean canMerge(DexProgramClass clazz) {
+ return !clazz.isInterface();
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return options.isInterfaceMergingEnabled(mode);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
index a13a445..82b76d9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
@@ -29,20 +28,20 @@
appView.appInfo().classes().forEach(this::processClass);
}
- private void processClass(DexProgramClass programClass) {
- DexType type = programClass.getType();
- boolean pinProgramClass = keepInfo.isPinned(type, appView);
- for (DexEncodedMember<?, ?> member : programClass.members()) {
- DexMember<?, ?> reference = member.getReference();
- if (keepInfo.isPinned(reference, appView)) {
- pinProgramClass = true;
+ private void processClass(DexProgramClass clazz) {
+ DexType type = clazz.getType();
+ boolean pinHolder = keepInfo.getClassInfo(clazz).isPinned();
+ for (DexEncodedMember<?, ?> member : clazz.members()) {
+ if (keepInfo.getMemberInfo(member, clazz).isPinned()) {
+ pinHolder = true;
Iterables.addAll(
dontMergeTypes,
Iterables.filter(
- reference.getReferencedBaseTypes(appView.dexItemFactory()), DexType::isClassType));
+ member.getReference().getReferencedBaseTypes(appView.dexItemFactory()),
+ DexType::isClassType));
}
}
- if (pinProgramClass) {
+ if (pinHolder) {
dontMergeTypes.add(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.java
new file mode 100644
index 0000000..81358f1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+
+public class NoNonPrivateVirtualMethods extends SingleClassPolicy {
+
+ public NoNonPrivateVirtualMethods(Mode mode) {
+ // TODO(b/181846319): Allow virtual methods as long as they do not require any merging.
+ assert mode.isFinal();
+ }
+
+ @Override
+ public boolean canMerge(DexProgramClass clazz) {
+ return clazz.isInterface() || !clazz.getMethodCollection().hasVirtualMethods();
+ }
+
+ @Override
+ public String getName() {
+ return "NoNonPrivateVirtualMethods";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
index 30bee86..c33b868 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
@@ -17,8 +17,7 @@
public NoServiceLoaders(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
-
- allServiceImplementations = appView.appServices().computeAllServiceImplementations();
+ this.allServiceImplementations = appView.appServices().computeAllServiceImplementations();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVerticallyMergedClasses.java
new file mode 100644
index 0000000..c7534b3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVerticallyMergedClasses.java
@@ -0,0 +1,35 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class NoVerticallyMergedClasses extends SingleClassPolicy {
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoVerticallyMergedClasses(AppView<AppInfoWithLiveness> appView, Mode mode) {
+ // This policy is only relevant for the initial round, since all vertically merged classes have
+ // been removed from the application in the final round of horizontal class merging.
+ assert mode.isInitial();
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(DexProgramClass program) {
+ if (appView.verticallyMergedClasses() == null) {
+ return true;
+ }
+ return !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(program.type);
+ }
+
+ @Override
+ public String getName() {
+ return "NotVerticallyMergedIntoSubtype";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
index 86ad07c..2daa302 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
@@ -18,8 +18,8 @@
}
@Override
- public boolean canMerge(DexProgramClass program) {
- return !appView.appInfo().isNoHorizontalClassMergingOfType(program.getType());
+ public boolean canMerge(DexProgramClass clazz) {
+ return !appView.appInfo().isNoHorizontalClassMergingOfType(clazz.getType());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
deleted file mode 100644
index 062e695..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
+++ /dev/null
@@ -1,31 +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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-
-public class NotVerticallyMergedIntoSubtype extends SingleClassPolicy {
- private final AppView<? extends AppInfoWithClassHierarchy> appView;
-
- public NotVerticallyMergedIntoSubtype(AppView<? extends AppInfoWithClassHierarchy> appView) {
- this.appView = appView;
- }
-
- @Override
- public boolean canMerge(DexProgramClass program) {
- if (appView.verticallyMergedClasses() == null) {
- return true;
- }
- return !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(program.type);
- }
-
- @Override
- public String getName() {
- return "NotVerticallyMergedIntoSubtype";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
new file mode 100644
index 0000000..d9e6cd1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+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.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * This policy ensures that we do not create cycles in the class hierarchy as a result of interface
+ * merging.
+ *
+ * <p>Example: Consider that we have the following three interfaces:
+ *
+ * <pre>
+ * interface I extends ... {}
+ * interface J extends I, ... {}
+ * interface K extends J, ... {}
+ * </pre>
+ *
+ * <p>In this case, it would be possible to merge the groups {I, J}, {J, K}, and {I, J, K}. Common
+ * to these merge groups is that each interface in the merge group can reach all other interfaces in
+ * the same merge group in the class hierarchy, without visiting any interfaces outside the merge
+ * group.
+ *
+ * <p>The group {I, K} cannot safely be merged, as this would lead to a cycle in the class
+ * hierarchy:
+ *
+ * <pre>
+ * interface IK extends J, ... {}
+ * interface J extends IK, ... {}
+ * </pre>
+ */
+public class OnlyDirectlyConnectedOrUnrelatedInterfaces extends MultiClassPolicy {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Mode mode;
+
+ public OnlyDirectlyConnectedOrUnrelatedInterfaces(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+ this.appView = appView;
+ this.mode = mode;
+ }
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group) {
+ if (!group.isInterfaceGroup()) {
+ return ImmutableList.of(group);
+ }
+
+ Set<DexProgramClass> classes = new LinkedHashSet<>(group.getClasses());
+ Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging =
+ computeIneligibleForMergingGraph(classes);
+ if (ineligibleForMerging.isEmpty()) {
+ return ImmutableList.of(group);
+ }
+
+ // Extract sub-merge groups from the graph in such a way that all pairs of interfaces in each
+ // merge group are not connected by an edge in the graph.
+ List<MergeGroup> newGroups = new LinkedList<>();
+ while (!classes.isEmpty()) {
+ Iterator<DexProgramClass> iterator = classes.iterator();
+ MergeGroup newGroup = new MergeGroup(iterator.next());
+ Iterators.addAll(
+ newGroup,
+ Iterators.filter(
+ iterator,
+ candidate -> !isConnectedToGroup(candidate, newGroup, ineligibleForMerging)));
+ if (!newGroup.isTrivial()) {
+ newGroups.add(newGroup);
+ }
+ classes.removeAll(newGroup.getClasses());
+ }
+ return newGroups;
+ }
+
+ /**
+ * Computes an undirected graph, where the nodes are the interfaces from the merge group, and an
+ * edge I <-> J represents that I and J are not eligible for merging.
+ *
+ * <p>We will insert an edge I <-> J, if interface I inherits from interface J, and the path from
+ * I to J in the class hierarchy includes an interface K that is outside the merge group. Note
+ * that if I extends J directly we will not insert an edge I <-> J (unless there are multiple
+ * paths in the class hierarchy from I to J, and one of the paths goes through an interface
+ * outside the merge group).
+ */
+ private Map<DexProgramClass, Set<DexProgramClass>> computeIneligibleForMergingGraph(
+ Set<DexProgramClass> classes) {
+ Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging = new IdentityHashMap<>();
+ for (DexProgramClass clazz : classes) {
+ forEachIndirectlyReachableInterfaceInMergeGroup(
+ clazz,
+ classes,
+ other ->
+ ineligibleForMerging
+ .computeIfAbsent(clazz, ignore -> Sets.newIdentityHashSet())
+ .add(other));
+ }
+ return ineligibleForMerging;
+ }
+
+ private void forEachIndirectlyReachableInterfaceInMergeGroup(
+ DexProgramClass clazz, Set<DexProgramClass> classes, Consumer<DexProgramClass> consumer) {
+ // First find the set of interfaces that can be reached via paths in the class hierarchy from
+ // the given interface, without visiting any interfaces outside the merge group.
+ WorkList<DexType> workList = WorkList.newIdentityWorkList(clazz.getInterfaces());
+ while (workList.hasNext()) {
+ DexProgramClass directlyReachableInterface =
+ asProgramClassOrNull(appView.definitionFor(workList.next()));
+ if (directlyReachableInterface == null) {
+ continue;
+ }
+ // If the implemented interface is a member of the merge group, then include it's interfaces.
+ if (classes.contains(directlyReachableInterface)) {
+ workList.addIfNotSeen(directlyReachableInterface.getInterfaces());
+ }
+ }
+
+ // Initialize a new worklist with the first layer of indirectly reachable interface types.
+ Set<DexType> directlyReachableInterfaceTypes = workList.getSeenSet();
+ workList = WorkList.newIdentityWorkList();
+ for (DexType directlyReachableInterfaceType : directlyReachableInterfaceTypes) {
+ DexProgramClass directlyReachableInterface =
+ asProgramClassOrNull(appView.definitionFor(directlyReachableInterfaceType));
+ if (directlyReachableInterface != null) {
+ workList.addIfNotSeen(directlyReachableInterface.getInterfaces());
+ }
+ }
+
+ // Report all interfaces from the merge group that are reachable in the class hierarchy from the
+ // worklist.
+ while (workList.hasNext()) {
+ DexProgramClass indirectlyReachableInterface =
+ asProgramClassOrNull(appView.definitionFor(workList.next()));
+ if (indirectlyReachableInterface == null) {
+ continue;
+ }
+ if (classes.contains(indirectlyReachableInterface)) {
+ consumer.accept(indirectlyReachableInterface);
+ }
+ workList.addIfNotSeen(indirectlyReachableInterface.getInterfaces());
+ }
+ }
+
+ private boolean isConnectedToGroup(
+ DexProgramClass clazz,
+ MergeGroup group,
+ Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging) {
+ for (DexProgramClass member : group) {
+ if (ineligibleForMerging.getOrDefault(clazz, Collections.emptySet()).contains(member)
+ || ineligibleForMerging.getOrDefault(member, Collections.emptySet()).contains(clazz)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return "OnlyDirectlyConnectedOrUnrelatedInterfaces";
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return !appView.options().horizontalClassMergerOptions().isInterfaceMergingEnabled(mode);
+ }
+}
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 c8bb5c6..ab305a3 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
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -90,7 +91,10 @@
private final AppView<AppInfoWithLiveness> appView;
- public PreserveMethodCharacteristics(AppView<AppInfoWithLiveness> appView) {
+ public PreserveMethodCharacteristics(AppView<AppInfoWithLiveness> appView, Mode mode) {
+ // This policy checks that method merging does invalidate various properties. Thus there is no
+ // reason to run this policy if method merging is not allowed.
+ assert mode.isInitial();
this.appView = appView;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
index 5bffb33..9ec219c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
@@ -48,7 +49,7 @@
*
* <p>See: https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-5.html#jvms-5.4.3.3)
*/
-public class PreventMethodImplementation extends MultiClassPolicy {
+public class PreventClassMethodAndDefaultMethodCollisions extends MultiClassPolicy {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final SubtypingForrestForClasses subtypingForrestForClasses;
@@ -62,7 +63,7 @@
@Override
public String getName() {
- return "PreventMethodImplementation";
+ return "PreventClassMethodAndDefaultMethodCollisions";
}
private abstract static class SignaturesCache<C extends DexClass> {
@@ -124,7 +125,8 @@
}
}
- public PreventMethodImplementation(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ public PreventClassMethodAndDefaultMethodCollisions(
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
this.subtypingForrestForClasses = new SubtypingForrestForClasses(appView);
}
@@ -150,6 +152,11 @@
@Override
public Collection<MergeGroup> apply(MergeGroup group) {
+ // This policy is specific to issues that may arise from merging (non-interface) classes.
+ if (group.isInterfaceGroup()) {
+ return ImmutableList.of(group);
+ }
+
DexMethodSignatureSet signatures = DexMethodSignatureSet.createLinked();
for (DexProgramClass clazz : group) {
signatures.addAllMethods(clazz.methods());
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
index 391fb75..8e57e87 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
@@ -11,6 +11,7 @@
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.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo;
import com.google.common.collect.HashMultiset;
@@ -20,16 +21,24 @@
public class SameInstanceFields extends MultiClassSameReferencePolicy<Multiset<InstanceFieldInfo>> {
private final DexItemFactory dexItemFactory;
+ private final Mode mode;
- public SameInstanceFields(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ public SameInstanceFields(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
this.dexItemFactory = appView.dexItemFactory();
+ this.mode = mode;
}
@Override
public Multiset<InstanceFieldInfo> getMergeKey(DexProgramClass clazz) {
Multiset<InstanceFieldInfo> fields = HashMultiset.create();
for (DexEncodedField field : clazz.instanceFields()) {
- fields.add(InstanceFieldInfo.createRelaxed(field, dexItemFactory));
+ // We do not allow merging fields with different types in the final round of horizontal class
+ // merging, since that requires inserting check-cast instructions at reads.
+ InstanceFieldInfo instanceFieldInfo =
+ mode.isInitial()
+ ? InstanceFieldInfo.createRelaxed(field, dexItemFactory)
+ : InstanceFieldInfo.createExact(field);
+ fields.add(instanceFieldInfo);
}
return fields;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
similarity index 80%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
index 9d529d3..6df343a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
@@ -12,14 +12,12 @@
import com.android.tools.r8.shaking.MainDexInfo.MainDexGroup;
import com.android.tools.r8.synthesis.SyntheticItems;
-public class PreventMergeIntoDifferentMainDexGroups
- extends MultiClassSameReferencePolicy<MainDexGroup> {
+public class SameMainDexGroup extends MultiClassSameReferencePolicy<MainDexGroup> {
private final MainDexInfo mainDexInfo;
private final SyntheticItems synthetics;
- public PreventMergeIntoDifferentMainDexGroups(
- AppView<? extends AppInfoWithClassHierarchy> appView) {
+ public SameMainDexGroup(AppView<? extends AppInfoWithClassHierarchy> appView) {
mainDexInfo = appView.appInfo().getMainDexInfo();
synthetics = appView.getSyntheticItems();
}
@@ -33,6 +31,6 @@
@Override
public String getName() {
- return "PreventMergeIntoDifferentMainDexGroups";
+ return "SameMainDexGroup";
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
index 753ac5d..f40b722 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy.ClassKind;
import com.android.tools.r8.synthesis.SyntheticItems;
@@ -18,24 +19,23 @@
NOT_SYNTHETIC
}
- private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Mode mode;
+ private final SyntheticItems syntheticItems;
- public SyntheticItemsPolicy(AppView<? extends AppInfoWithClassHierarchy> appView) {
- this.appView = appView;
+ public SyntheticItemsPolicy(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+ this.mode = mode;
+ this.syntheticItems = appView.getSyntheticItems();
}
@Override
public ClassKind getMergeKey(DexProgramClass clazz) {
- SyntheticItems syntheticItems = appView.getSyntheticItems();
-
- // Allow merging non-synthetics with non-synthetics.
- if (!syntheticItems.isSyntheticClass(clazz)) {
- return ClassKind.NOT_SYNTHETIC;
+ // Allow merging non-synthetics with non-synthetics, and synthetics with synthetics.
+ if (syntheticItems.isSyntheticClass(clazz)) {
+ return syntheticItems.isEligibleForClassMerging(clazz, mode)
+ ? ClassKind.SYNTHETIC
+ : ineligibleForClassMerging();
}
-
- return syntheticItems.isEligibleForClassMerging(clazz)
- ? ClassKind.SYNTHETIC
- : ineligibleForClassMerging();
+ return ClassKind.NOT_SYNTHETIC;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java
new file mode 100644
index 0000000..1fc559c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+
+public class VerifyPolicyAlwaysSatisfied extends SingleClassPolicy {
+
+ private final SingleClassPolicy policy;
+
+ public VerifyPolicyAlwaysSatisfied(SingleClassPolicy policy) {
+ this.policy = policy;
+ }
+
+ @Override
+ public boolean canMerge(DexProgramClass program) {
+ assert policy.canMerge(program);
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "VerifyAlwaysSatisfied(" + policy.getName() + ")";
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return policy.shouldSkipPolicy();
+ }
+}
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 2cbebcd..785fb64 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
@@ -367,6 +367,11 @@
D8NestBasedAccessDesugaring::reportDesugarDependencies);
}
+ private void clearNestAttributes() {
+ instructionDesugaring.withD8NestBasedAccessDesugaring(
+ D8NestBasedAccessDesugaring::clearNestAttributes);
+ }
+
private void staticizeClasses(
OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
throws ExecutionException {
@@ -424,6 +429,7 @@
convertClasses(executor);
reportNestDesugarDependencies();
+ clearNestAttributes();
if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
appView.setAppInfo(
@@ -581,6 +587,7 @@
} else {
NeedsIRDesugarUseRegistry useRegistry =
new NeedsIRDesugarUseRegistry(
+ method,
appView,
desugaredLibraryRetargeter,
interfaceMethodRewriter,
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 750ffb4..bbc0bcb 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
@@ -15,6 +15,7 @@
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.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
@@ -24,16 +25,19 @@
class NeedsIRDesugarUseRegistry extends UseRegistry {
private boolean needsDesugaring = false;
+ private final ProgramMethod context;
private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
private final InterfaceMethodRewriter interfaceMethodRewriter;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
public NeedsIRDesugarUseRegistry(
+ ProgramMethod method,
AppView<?> appView,
DesugaredLibraryRetargeter desugaredLibraryRetargeter,
InterfaceMethodRewriter interfaceMethodRewriter,
DesugaredLibraryAPIConverter desugaredLibraryAPIConverter) {
super(appView.dexItemFactory());
+ this.context = method;
this.desugaredLibraryRetargeter = desugaredLibraryRetargeter;
this.interfaceMethodRewriter = interfaceMethodRewriter;
this.desugaredLibraryAPIConverter = desugaredLibraryAPIConverter;
@@ -70,7 +74,7 @@
if (!needsDesugaring) {
needsDesugaring =
interfaceMethodRewriter != null
- && interfaceMethodRewriter.needsRewriting(method, invokeType);
+ && interfaceMethodRewriter.needsRewriting(method, invokeType, context);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index f5845d4..e0c6d41 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -244,7 +244,8 @@
},
virtualMethods,
factory.getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType);
+ DexProgramClass::checksumFromType,
+ null);
}
private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 6a878f0..b0a4ffa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -226,7 +226,12 @@
return emulatedInterfaces.containsKey(itf);
}
- public boolean needsRewriting(DexMethod method, Type invokeType) {
+ public boolean needsRewriting(DexMethod method, Type invokeType, ProgramMethod context) {
+ return !isSyntheticMethodThatShouldNotBeDoubleProcessed(context)
+ && invokeNeedsRewriting(method, invokeType);
+ }
+
+ private boolean invokeNeedsRewriting(DexMethod method, Type invokeType) {
if (invokeType == SUPER || invokeType == STATIC || invokeType == DIRECT) {
DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
if (clazz != null && clazz.isInterface()) {
@@ -267,7 +272,10 @@
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
ProgramMethod context = code.context();
- if (synthesizedMethods.contains(context)) {
+ if (isSyntheticMethodThatShouldNotBeDoubleProcessed(code.context())) {
+ // As the synthetics for dispatching to static interface methods are not desugared again
+ // this can leave a static invoke to a static method on an interface.
+ leavingStaticInvokeToInterface(context.asProgramMethod());
return;
}
@@ -332,6 +340,10 @@
assert code.isConsistentSSA();
}
+ private boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
+ return appView.getSyntheticItems().isSyntheticMethodThatShouldNotBeDoubleProcessed(method);
+ }
+
private void rewriteInvokeCustom(InvokeCustom invoke, ProgramMethod context) {
// Check that static interface methods are not referenced from invoke-custom instructions via
// method handles.
@@ -375,7 +387,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);
+ assert invokeNeedsRewriting(method, DIRECT);
instructions.replaceCurrentInstruction(
new InvokeStatic(
directTarget.getDefinition().isPrivateMethod()
@@ -389,7 +401,7 @@
appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
if (virtualTarget != null) {
// This is a invoke-direct call to a virtual method.
- assert needsRewriting(method, DIRECT);
+ assert invokeNeedsRewriting(method, DIRECT);
instructions.replaceCurrentInstruction(
new InvokeStatic(
defaultAsMethodOfCompanionClass(virtualTarget),
@@ -472,16 +484,14 @@
.setStaticTarget(invokedMethod, true)
.setStaticSource(m)
.build()));
- assert needsRewriting(invokedMethod, STATIC);
+ assert invokeNeedsRewriting(invokedMethod, STATIC);
instructions.replaceCurrentInstruction(
new InvokeStatic(
newProgramMethod.getReference(), invoke.outValue(), invoke.arguments()));
- synchronized (synthesizedMethods) {
- // The synthetic dispatch class has static interface method invokes, so set
- // the class file version accordingly.
- newProgramMethod.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
- synthesizedMethods.add(newProgramMethod);
- }
+ // The synthetic dispatch class has static interface method invokes, so set
+ // the class file version accordingly.
+ newProgramMethod.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
+ synthesizedMethods.add(newProgramMethod);
} else {
// When leaving static interface method invokes upgrade the class file version.
context.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
@@ -505,13 +515,13 @@
blocksToRemove,
methodProcessor,
methodProcessingContext)) {
- assert needsRewriting(invoke.getInvokedMethod(), STATIC);
+ assert invokeNeedsRewriting(invoke.getInvokedMethod(), STATIC);
return;
}
assert resolutionResult != null;
assert resolutionResult.getResolvedMethod().isStatic();
- assert needsRewriting(invokedMethod, STATIC);
+ assert invokeNeedsRewriting(invokedMethod, STATIC);
instructions.replaceCurrentInstruction(
new InvokeStatic(
@@ -553,7 +563,7 @@
blocksToRemove,
methodProcessor,
methodProcessingContext)) {
- assert needsRewriting(invoke.getInvokedMethod(), SUPER);
+ assert invokeNeedsRewriting(invoke.getInvokedMethod(), SUPER);
return;
}
@@ -567,7 +577,7 @@
//
// WARNING: This may result in incorrect code on older platforms!
// Retarget call to an appropriate method of companion class.
- assert needsRewriting(invokedMethod, SUPER);
+ assert invokeNeedsRewriting(invokedMethod, SUPER);
DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
instructions.replaceCurrentInstruction(
new InvokeStatic(
@@ -583,7 +593,7 @@
if (target != null && target.getDefinition().isDefaultMethod()) {
DexClass holder = target.getHolder();
if (holder.isLibraryClass() && holder.isInterface()) {
- assert needsRewriting(invokedMethod, SUPER);
+ assert invokeNeedsRewriting(invokedMethod, SUPER);
instructions.replaceCurrentInstruction(
new InvokeStatic(
defaultAsMethodOfCompanionClass(target),
@@ -613,11 +623,11 @@
factory.protoWithDifferentFirstParameter(
originalCompanionMethod.proto, emulatedItf),
originalCompanionMethod.name);
- assert needsRewriting(invokedMethod, SUPER);
+ assert invokeNeedsRewriting(invokedMethod, SUPER);
instructions.replaceCurrentInstruction(
new InvokeStatic(companionMethod, invoke.outValue(), invoke.arguments()));
} else {
- assert needsRewriting(invokedMethod, SUPER);
+ assert invokeNeedsRewriting(invokedMethod, SUPER);
instructions.replaceCurrentInstruction(
new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
index b622057..430d8ad 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -55,6 +55,17 @@
});
}
+ public void clearNestAttributes() {
+ forEachNest(
+ nest -> {
+ nest.getHostClass().clearNestMembers();
+ nest.getMembers().forEach(DexClass::clearNestHost);
+ },
+ classWithoutHost -> {
+ // Do Nothing
+ });
+ }
+
public void synthesizeBridgesForNestBasedAccessesOnClasspath(
MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException {
List<DexClasspathClass> classpathClassesInNests = new ArrayList<>();
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 41a298a..a6f246f 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
@@ -141,6 +141,11 @@
return !singleTarget.getDefinition().getOptimizationInfo().forceInline();
}
+ if (!appView.testing().allowInliningOfSynthetics
+ && appView.getSyntheticItems().isSyntheticClass(singleTarget.getHolder())) {
+ return true;
+ }
+
return false;
}
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 014337c..7092012 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
@@ -15,7 +15,6 @@
import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
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;
@@ -245,8 +244,10 @@
killNonFinalActiveFields(staticPut);
ExistingValue value = new ExistingValue(staticPut.value());
if (isFinal(field)) {
- assert !field.getDefinition().isFinal()
- || method.getDefinition().isClassInitializer();
+ assert appView.checkForTesting(
+ () ->
+ !field.getDefinition().isFinal()
+ || method.getDefinition().isClassInitializer());
activeState.putFinalStaticField(reference, value);
} else {
activeState.putNonFinalStaticField(reference, value);
@@ -328,11 +329,9 @@
private boolean verifyWasInstanceInitializer() {
VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
- HorizontallyMergedClasses horizontallyMergedClasses = appView.horizontallyMergedClasses();
assert verticallyMergedClasses != null;
- assert horizontallyMergedClasses != null;
assert verticallyMergedClasses.isMergeTarget(method.getHolderType())
- || horizontallyMergedClasses.isMergeTarget(method.getHolderType());
+ || appView.horizontallyMergedClasses().isMergeTarget(method.getHolderType());
assert appView
.dexItemFactory()
.isConstructor(appView.graphLens().getOriginalMethodSignature(method.getReference()));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index f7b79aa..c3c2307 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -300,6 +300,12 @@
if (clazz.classInitializationMayHaveSideEffects(appView)) {
return EligibilityStatus.NOT_ELIGIBLE;
}
+
+ if (!appView.testing().allowClassInliningOfSynthetics
+ && appView.getSyntheticItems().isSyntheticClass(clazz)) {
+ return EligibilityStatus.NOT_ELIGIBLE;
+ }
+
return EligibilityStatus.ELIGIBLE;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 03eee35..0d2e43b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -145,6 +145,7 @@
instanceInitializerInfoCollection = template.instanceInitializerInfoCollection;
nonNullParamOrThrow = template.nonNullParamOrThrow;
nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
+ classInlinerConstraint = template.classInlinerConstraint;
}
public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 5f23e34..0206e81 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -199,6 +199,7 @@
}
assert SyntheticNaming.verifyNotInternalSynthetic(name);
writer.visit(version.raw(), access, name, signature, superName, interfaces);
+ appView.getSyntheticItems().writeAttributeIfIntermediateSyntheticClass(writer, clazz, appView);
writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations);
ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
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 63d0098..62c04bb 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -131,7 +131,7 @@
try {
return Files.readAllLines(Paths.get(stackTracePath), Charsets.UTF_8);
} catch (IOException e) {
- diagnostics.error(new StringDiagnostic("Could not find stack trace file: " + stackTracePath));
+ diagnostics.error(new ExceptionDiagnostic(e));
throw new RetraceAbortException();
}
}
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 69c27c5..c4ebfe7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
@@ -1055,7 +1056,7 @@
return new AppInfoWithLiveness(
committedItems,
getClassToFeatureSplitMap().rewrittenWithLens(lens),
- getMainDexInfo().rewrittenWithLens(lens),
+ getMainDexInfo().rewrittenWithLens(getSyntheticItems(), lens),
deadProtoTypes,
getMissingClasses().commitSyntheticItems(committedItems),
lens.rewriteTypes(liveTypes),
@@ -1094,7 +1095,26 @@
prunedTypes,
lens.rewriteFieldKeys(switchMaps),
lens.rewriteTypes(lockCandidates),
- lens.rewriteTypeKeys(initClassReferences));
+ rewriteInitClassReferences(lens));
+ }
+
+ public Map<DexType, Visibility> rewriteInitClassReferences(GraphLens lens) {
+ return lens.rewriteTypeKeys(
+ initClassReferences,
+ (minimumRequiredVisibilityForCurrentMethod,
+ otherMinimumRequiredVisibilityForCurrentMethod) -> {
+ assert !minimumRequiredVisibilityForCurrentMethod.isPrivate();
+ assert !otherMinimumRequiredVisibilityForCurrentMethod.isPrivate();
+ if (minimumRequiredVisibilityForCurrentMethod.isPublic()
+ || otherMinimumRequiredVisibilityForCurrentMethod.isPublic()) {
+ return Visibility.PUBLIC;
+ }
+ if (minimumRequiredVisibilityForCurrentMethod.isProtected()
+ || otherMinimumRequiredVisibilityForCurrentMethod.isProtected()) {
+ return Visibility.PROTECTED;
+ }
+ return Visibility.PACKAGE_PRIVATE;
+ });
}
/**
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
index 5be14a3..f3f52fd 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -276,7 +276,7 @@
}
}
- public MainDexInfo rewrittenWithLens(GraphLens lens) {
+ public MainDexInfo rewrittenWithLens(SyntheticItems syntheticItems, GraphLens lens) {
Set<DexType> modifiedClassList = Sets.newIdentityHashSet();
classList.forEach(
type -> rewriteAndApplyIfNotPrimitiveType(lens, type, modifiedClassList::add));
@@ -289,6 +289,9 @@
// Synthetic finalization is allowed to merge identical classes into the same class. The
// rewritten type of a traced dependency can therefore be finalized with a traced root.
rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependencyIfNotRoot);
+ } else if (syntheticItems.isFinalized()) {
+ rewriteAndApplyIfNotPrimitiveType(
+ lens, type, builder.addDependencyAllowSyntheticRoot(syntheticItems));
} else {
rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependency);
}
@@ -342,6 +345,13 @@
dependencies.add(type);
}
+ public Consumer<DexType> addDependencyAllowSyntheticRoot(SyntheticItems syntheticItems) {
+ return type -> {
+ assert !roots.contains(type) || syntheticItems.isCommittedSynthetic(type);
+ addDependencyIfNotRoot(type);
+ };
+ }
+
public void addDependencyIfNotRoot(DexType type) {
if (roots.contains(type)) {
return;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index 0dcf1eb..e47ff67 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -152,9 +152,6 @@
} else if (!innerClasses && enclosingMethod) {
throw new CompilationError("Attribute EnclosingMethod requires InnerClasses attribute. "
+ "Check -keepattributes directive.");
- } else if (signature && !innerClasses) {
- throw new CompilationError("Attribute Signature requires InnerClasses attribute. Check "
- + "-keepattributes directive.");
}
if (forceProguardCompatibility && localVariableTable && !lineNumberTable) {
// If locals are kept, assume line numbers should be kept too.
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
index fb3868a..fc2756a 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
@@ -33,18 +34,25 @@
implements EnqueuerInstanceOfAnalysis,
EnqueuerCheckCastAnalysis,
EnqueuerExceptionGuardAnalysis {
+
+ private final GraphLens appliedGraphLens;
private final DexItemFactory factory;
private final Set<DexType> instanceOfTypes = Sets.newIdentityHashSet();
private final Set<DexType> checkCastTypes = Sets.newIdentityHashSet();
private final Set<DexType> exceptionGuardTypes = Sets.newIdentityHashSet();
- public Builder(DexItemFactory factory) {
- this.factory = factory;
+ public Builder(AppView<?> appView) {
+ this.appliedGraphLens = appView.graphLens();
+ this.factory = appView.dexItemFactory();
}
- public RuntimeTypeCheckInfo build() {
- return new RuntimeTypeCheckInfo(instanceOfTypes, checkCastTypes, exceptionGuardTypes);
+ public RuntimeTypeCheckInfo build(GraphLens graphLens) {
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo =
+ new RuntimeTypeCheckInfo(instanceOfTypes, checkCastTypes, exceptionGuardTypes);
+ return graphLens.isNonIdentityLens() && graphLens != appliedGraphLens
+ ? runtimeTypeCheckInfo.rewriteWithLens(graphLens.asNonIdentityLens())
+ : runtimeTypeCheckInfo;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index c81ad1c..ef3b4d2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -160,6 +160,7 @@
directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
factory.getSkipNameValidationForTesting(),
- c -> checksum);
+ c -> checksum,
+ 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 cbdf447..55b5e00 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
@@ -27,7 +28,9 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
@@ -150,7 +153,10 @@
appView
.appInfo()
.rebuildWithMainDexInfo(
- appView.appInfo().getMainDexInfo().rewrittenWithLens(result.lens)));
+ appView
+ .appInfo()
+ .getMainDexInfo()
+ .rewrittenWithLens(appView.getSyntheticItems(), result.lens)));
appView.setGraphLens(result.lens);
}
appView.pruneItems(result.prunedItems);
@@ -167,7 +173,10 @@
appView
.appInfo()
.rebuildWithMainDexInfo(
- appView.appInfo().getMainDexInfo().rewrittenWithLens(result.lens)));
+ appView
+ .appInfo()
+ .getMainDexInfo()
+ .rewrittenWithLens(appView.getSyntheticItems(), result.lens)));
}
appView.pruneItems(result.prunedItems);
}
@@ -185,7 +194,7 @@
}
Result computeFinalSynthetics(AppView<?> appView) {
- assert verifyNoNestedSynthetics();
+ assert verifyNoNestedSynthetics(appView.dexItemFactory());
assert verifyOneSyntheticPerSyntheticClass();
DexApplication application;
Builder lensBuilder = new Builder();
@@ -285,12 +294,19 @@
return !committed.containsNonLegacyType(type);
}
- private boolean verifyNoNestedSynthetics() {
- // Check that a context is never itself synthetic class.
+ private boolean verifyNoNestedSynthetics(DexItemFactory dexItemFactory) {
+ // Check that the prefix of each synthetic is never itself synthetic.
committed.forEachNonLegacyItem(
item -> {
- assert isNotSyntheticType(item.getContext().getSynthesizingContextType())
- || item.getKind().allowSyntheticContext();
+ if (item.getKind().allowSyntheticContext()) {
+ return;
+ }
+ String prefix =
+ SyntheticNaming.getPrefixForExternalSyntheticType(item.getKind(), item.getHolder());
+ assert !prefix.contains(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
+ DexType context =
+ dexItemFactory.createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
+ assert isNotSyntheticType(context);
});
return true;
}
@@ -321,6 +337,13 @@
return true;
}
+ private static void ensureSourceFile(
+ DexProgramClass externalSyntheticClass, DexString syntheticSourceFileName) {
+ if (externalSyntheticClass.getSourceFile() == null) {
+ externalSyntheticClass.setSourceFile(syntheticSourceFileName);
+ }
+ }
+
private static DexApplication buildLensAndProgram(
AppView<?> appView,
Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
@@ -362,8 +385,7 @@
SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
SynthesizingContext context = representative.getContext();
context.registerPrefixRewriting(syntheticType, appView);
- addSyntheticMarker(
- representative.getKind(), representative.getHolder(), context, appView);
+ addSyntheticMarker(representative.getKind(), representative.getHolder(), appView);
if (syntheticGroup.isDerivedFromMainDexList(mainDexInfo)) {
derivedMainDexSynthetics.add(syntheticType);
}
@@ -379,8 +401,7 @@
SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
SynthesizingContext context = representative.getContext();
context.registerPrefixRewriting(syntheticType, appView);
- addSyntheticMarker(
- representative.getKind(), representative.getHolder(), context, appView);
+ addSyntheticMarker(representative.getKind(), representative.getHolder(), appView);
if (syntheticGroup.isDerivedFromMainDexList(mainDexInfo)) {
derivedMainDexSynthetics.add(syntheticType);
}
@@ -417,6 +438,11 @@
application = builder.build();
}
+ DexString syntheticSourceFileName =
+ appView.enableWholeProgramOptimizations()
+ ? appView.dexItemFactory().createString("R8$$SyntheticClass")
+ : appView.dexItemFactory().createString("D8$$SyntheticClass");
+
// Add the synthesized from after repackaging which changed class definitions.
final DexApplication appForLookup = application;
syntheticClassGroups.forEach(
@@ -424,6 +450,7 @@
DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
assert externalSyntheticClass != null
: "Expected definition for " + syntheticType.getTypeName();
+ ensureSourceFile(externalSyntheticClass, syntheticSourceFileName);
SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
addFinalSyntheticClass.accept(
externalSyntheticClass,
@@ -435,6 +462,7 @@
syntheticMethodGroups.forEach(
(syntheticType, syntheticGroup) -> {
DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+ ensureSourceFile(externalSyntheticClass, syntheticSourceFileName);
SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
assert externalSyntheticClass.getMethodCollection().size() == 1;
assert externalSyntheticClass.getMethodCollection().hasDirectMethods();
@@ -474,24 +502,16 @@
private static void addSyntheticMarker(
SyntheticKind kind,
DexProgramClass externalSyntheticClass,
- SynthesizingContext context,
AppView<?> appView) {
if (shouldAnnotateSynthetics(appView.options())) {
- SyntheticMarker.addMarkerToClass(
- externalSyntheticClass,
- kind,
- context,
- appView.dexItemFactory(),
- appView.options().forceAnnotateSynthetics);
+ SyntheticMarker.addMarkerToClass(externalSyntheticClass, kind, appView.options());
}
}
private static boolean shouldAnnotateSynthetics(InternalOptions options) {
// Only intermediate builds have annotated synthetics to allow later sharing.
- // This is currently also disabled on non-L8 CF to CF desugaring to avoid missing class
- // references to the annotated classes.
- // TODO(b/147485959): Find an alternative encoding for synthetics to avoid missing-class refs.
- return options.intermediate && (!options.cfToCfDesugar || options.forceAnnotateSynthetics);
+ // Also, CF builds are marked in the writer using an attribute.
+ return options.intermediate && options.isGeneratingDex();
}
private <T extends SyntheticDefinition<?, T, ?>>
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 f709533..78c046f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.IterableUtils;
@@ -30,6 +31,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -38,6 +40,7 @@
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
+import org.objectweb.asm.ClassWriter;
public class SyntheticItems implements SyntheticDefinitionsProvider {
@@ -119,12 +122,8 @@
assert synthetics.nextSyntheticId == 0;
assert synthetics.committed.isEmpty();
assert synthetics.pending.isEmpty();
- if (appView.options().intermediate) {
- // If the compilation is in intermediate mode the synthetics should just be passed through.
- return;
- }
CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
- // TODO(b/158159959): Consider identifying synthetics in the input reader to speed this up.
+ // TODO(b/158159959): Consider populating the input synthetics when identified.
for (DexProgramClass clazz : appView.appInfo().classes()) {
SyntheticMarker marker = SyntheticMarker.stripMarkerFromClass(clazz, appView);
if (marker.isSyntheticMethods()) {
@@ -184,6 +183,10 @@
return baseDefinitionFor.apply(type);
}
+ public boolean isFinalized() {
+ return nextSyntheticId == INVALID_ID_AFTER_SYNTHETIC_FINALIZATION;
+ }
+
public boolean hasPendingSyntheticClasses() {
return !pending.isEmpty();
}
@@ -192,7 +195,7 @@
return pending.getAllProgramClasses();
}
- private boolean isCommittedSynthetic(DexType type) {
+ public boolean isCommittedSynthetic(DexType type) {
return committed.containsType(type);
}
@@ -200,6 +203,10 @@
return committed.containsLegacyType(type);
}
+ private boolean isNonLegacyCommittedSynthetic(DexType type) {
+ return committed.containsNonLegacyType(type);
+ }
+
public boolean isPendingSynthetic(DexType type) {
return pending.containsType(type);
}
@@ -208,6 +215,10 @@
return pending.legacyClasses.containsKey(type);
}
+ private boolean isNonLegacyPendingSynthetic(DexType type) {
+ return pending.nonLegacyDefinitions.containsKey(type);
+ }
+
public boolean isLegacySyntheticClass(DexType type) {
return isLegacyCommittedSynthetic(type) || isLegacyPendingSynthetic(type);
}
@@ -221,15 +232,14 @@
}
public boolean isNonLegacySynthetic(DexType type) {
- return isCommittedSynthetic(type) || isPendingSynthetic(type);
+ return isNonLegacyCommittedSynthetic(type) || isNonLegacyPendingSynthetic(type);
}
- public boolean isEligibleForClassMerging(DexProgramClass clazz) {
+ public boolean isEligibleForClassMerging(DexProgramClass clazz, HorizontalClassMerger.Mode mode) {
assert isSyntheticClass(clazz);
- return isSyntheticLambda(clazz);
+ return mode.isFinal() || isSyntheticLambda(clazz);
}
- // TODO(b/186211926): Allow merging of legacy synthetics.
private boolean isSyntheticLambda(DexProgramClass clazz) {
if (!isNonLegacySynthetic(clazz)) {
return false;
@@ -335,6 +345,23 @@
Set<DexReference> getSynthesizingContexts(DexProgramClass clazz);
}
+ public boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
+ for (SyntheticMethodReference reference :
+ committed
+ .getNonLegacyMethods()
+ .getOrDefault(method.getHolderType(), Collections.emptyList())) {
+ if (reference.getKind() == SyntheticKind.STATIC_INTERFACE_CALL) {
+ return true;
+ }
+ }
+ SyntheticDefinition<?, ?, ?> definition =
+ pending.nonLegacyDefinitions.get(method.getHolderType());
+ if (definition != null) {
+ return definition.getKind() == SyntheticKind.STATIC_INTERFACE_CALL;
+ }
+ return false;
+ }
+
// The compiler should not inspect the kind of a synthetic, so this provided only as a assertion
// utility.
public boolean verifySyntheticLambdaProperty(
@@ -661,6 +688,23 @@
return true;
}
+ public void writeAttributeIfIntermediateSyntheticClass(
+ ClassWriter writer, DexProgramClass clazz, AppView<?> appView) {
+ if (!appView.options().intermediate || !appView.options().isGeneratingClassFiles()) {
+ return;
+ }
+ Iterator<SyntheticReference<?, ?, ?>> it =
+ committed.getNonLegacyItems(clazz.getType()).iterator();
+ if (it.hasNext()) {
+ SyntheticKind kind = it.next().getKind();
+ // When compiling intermediates there should not be any mergings as they may invalidate the
+ // single kind of a synthetic which is required for marking synthetics. This check could be
+ // relaxed to ensure that all kinds are equivalent if merging is possible.
+ assert !it.hasNext();
+ SyntheticMarker.writeMarkerAttribute(writer, kind);
+ }
+ }
+
// Finalization of synthetic items.
Result computeFinalSynthetics(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index 1d9b5b9..b5fc30d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -13,27 +13,92 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.InternalOptions;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ByteVector;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
public class SyntheticMarker {
+ private static final String SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME = "R8SynthesizedClass";
+
+ public static Attribute getMarkerAttributePrototype() {
+ return MarkerAttribute.PROTOTYPE;
+ }
+
+ public static void writeMarkerAttribute(ClassWriter writer, SyntheticKind kind) {
+ writer.visitAttribute(new MarkerAttribute(kind));
+ }
+
+ public static SyntheticMarker readMarkerAttribute(Attribute attribute) {
+ if (attribute instanceof MarkerAttribute) {
+ MarkerAttribute marker = (MarkerAttribute) attribute;
+ return new SyntheticMarker(marker.kind, null);
+ }
+ return null;
+ }
+
+ private static class MarkerAttribute extends Attribute {
+
+ private static final MarkerAttribute PROTOTYPE = new MarkerAttribute(null);
+
+ private SyntheticKind kind;
+
+ public MarkerAttribute(SyntheticKind kind) {
+ super(SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME);
+ this.kind = kind;
+ }
+
+ @Override
+ protected Attribute read(
+ ClassReader classReader,
+ int offset,
+ int length,
+ char[] charBuffer,
+ int codeAttributeOffset,
+ Label[] labels) {
+ short id = classReader.readShort(offset);
+ assert id >= 0;
+ SyntheticKind kind = SyntheticKind.fromId(id);
+ return new MarkerAttribute(kind);
+ }
+
+ @Override
+ protected ByteVector write(
+ ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
+ ByteVector byteVector = new ByteVector();
+ assert 0 <= kind.id && kind.id <= Short.MAX_VALUE;
+ byteVector.putShort(kind.id);
+ return byteVector;
+ }
+ }
+
public static void addMarkerToClass(
- DexProgramClass clazz,
- SyntheticKind kind,
- SynthesizingContext context,
- DexItemFactory factory,
- boolean dontRecordSynthesizingContext) {
+ DexProgramClass clazz, SyntheticKind kind, InternalOptions options) {
+ // TODO(b/158159959): Consider moving this to the dex writer similar to the CF case.
+ assert !options.isGeneratingClassFiles();
clazz.setAnnotations(
clazz
.annotations()
.getWithAddedOrReplaced(
- DexAnnotation.createAnnotationSynthesizedClass(
- kind,
- dontRecordSynthesizingContext ? null : context.getSynthesizingContextType(),
- factory)));
+ DexAnnotation.createAnnotationSynthesizedClass(kind, options.itemFactory)));
}
public static SyntheticMarker stripMarkerFromClass(DexProgramClass clazz, AppView<?> appView) {
+ if (clazz.originatesFromClassResource()) {
+ SyntheticMarker marker = clazz.stripSyntheticInputMarker();
+ if (marker == null) {
+ return NO_MARKER;
+ }
+ assert marker.getContext() == null;
+ DexType contextType =
+ getSyntheticContextType(clazz.type, marker.kind, appView.dexItemFactory());
+ SynthesizingContext context =
+ SynthesizingContext.fromSyntheticInputClass(clazz, contextType, appView);
+ return new SyntheticMarker(marker.kind, context);
+ }
SyntheticMarker marker = internalStripMarkerFromClass(clazz, appView);
assert marker != NO_MARKER
|| !DexAnnotation.hasSynthesizedClassAnnotation(
@@ -50,15 +115,13 @@
if (!flags.isSynthetic() || flags.isAbstract() || flags.isEnum()) {
return NO_MARKER;
}
- Pair<SyntheticKind, DexType> info =
- DexAnnotation.getSynthesizedClassAnnotationContextType(
+ SyntheticKind kind =
+ DexAnnotation.getSynthesizedClassAnnotationInfo(
clazz.annotations(), appView.dexItemFactory());
- if (info == null) {
+ if (kind == null) {
return NO_MARKER;
}
assert clazz.annotations().size() == 1;
- SyntheticKind kind = info.getFirst();
- DexType context = info.getSecond();
if (kind.isSingleSyntheticMethod) {
if (!clazz.interfaces.isEmpty()) {
return NO_MARKER;
@@ -70,22 +133,17 @@
}
}
clazz.setAnnotations(DexAnnotationSet.empty());
- if (context == null) {
- // If the class is marked as synthetic but has no synthesizing context, then we read the
- // context type as the prefix. This happens for desugared library builds where the context of
- // the generated
- // synthetics becomes themselves. Using the original context could otherwise have referenced
- // a type in the non-rewritten library and cause an non-rewritten output type.
- String prefix = SyntheticNaming.getPrefixForExternalSyntheticType(kind, clazz.type);
- context =
- appView
- .dexItemFactory()
- .createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
- }
+ DexType context = getSyntheticContextType(clazz.type, kind, appView.dexItemFactory());
return new SyntheticMarker(
kind, SynthesizingContext.fromSyntheticInputClass(clazz, context, appView));
}
+ private static DexType getSyntheticContextType(
+ DexType type, SyntheticKind kind, DexItemFactory factory) {
+ String prefix = SyntheticNaming.getPrefixForExternalSyntheticType(kind, type);
+ return factory.createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
+ }
+
private static final SyntheticMarker NO_MARKER = new SyntheticMarker(null, null);
private final SyntheticKind kind;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index b6bb218..80e4e19 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -58,9 +58,8 @@
// If the reference has been non-trivially rewritten the compiler has changed it and it can no
// longer be considered a synthetic. The context may or may not have changed.
if (method != rewritten && !lens.isSimpleRenaming(method, rewritten)) {
- // If the referenced item is rewritten, it should be moved to another holder as the
- // synthetic holder is no longer part of the synthetic collection.
- assert method.holder != rewritten.holder : "The synthetic method reference should have moved";
+ // If the referenced item is rewritten, it should be moved to a non-internal synthetic holder
+ // as the synthetic holder is no longer part of the synthetic collection.
assert SyntheticNaming.verifyNotInternalSynthetic(rewritten.holder);
return null;
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 8b19177..ce1647b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -23,47 +23,53 @@
*/
public enum SyntheticKind {
// Class synthetics.
- RECORD_TAG("", false, true, true),
- COMPANION_CLASS("-CC", false, true),
- EMULATED_INTERFACE_CLASS("-EL", false, true),
- LAMBDA("Lambda", false),
- INIT_TYPE_ARGUMENT("-IA", false, true),
- HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", false, true),
- HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", false, true),
- HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", false, true),
+ RECORD_TAG("", 1, false, true, true),
+ COMPANION_CLASS("-CC", 2, false, true),
+ EMULATED_INTERFACE_CLASS("-EL", 3, false, true),
+ LAMBDA("Lambda", 4, false),
+ INIT_TYPE_ARGUMENT("-IA", 5, false, true),
+ HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
+ HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
+ HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
// Method synthetics.
- RECORD_HELPER("Record", true),
- BACKPORT("Backport", true),
- STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
- TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
- THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true),
- THROW_IAE("ThrowIAE", true),
- THROW_ICCE("ThrowICCE", true),
- THROW_NSME("ThrowNSME", true),
- TWR_CLOSE_RESOURCE("TwrCloseResource", true),
- SERVICE_LOADER("ServiceLoad", true),
- OUTLINE("Outline", true);
+ RECORD_HELPER("Record", 9, true),
+ BACKPORT("Backport", 10, true),
+ STATIC_INTERFACE_CALL("StaticInterfaceCall", 11, true),
+ TO_STRING_IF_NOT_NULL("ToStringIfNotNull", 12, true),
+ THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", 13, true),
+ THROW_IAE("ThrowIAE", 14, true),
+ THROW_ICCE("ThrowICCE", 15, true),
+ THROW_NSME("ThrowNSME", 16, true),
+ TWR_CLOSE_RESOURCE("TwrCloseResource", 17, true),
+ SERVICE_LOADER("ServiceLoad", 18, true),
+ OUTLINE("Outline", 19, true);
public final String descriptor;
+ public final int id;
public final boolean isSingleSyntheticMethod;
public final boolean isFixedSuffixSynthetic;
public final boolean mayOverridesNonProgramType;
- SyntheticKind(String descriptor, boolean isSingleSyntheticMethod) {
- this(descriptor, isSingleSyntheticMethod, false);
- }
-
- SyntheticKind(
- String descriptor, boolean isSingleSyntheticMethod, boolean isFixedSuffixSynthetic) {
- this(descriptor, isSingleSyntheticMethod, isFixedSuffixSynthetic, false);
+ SyntheticKind(String descriptor, int id, boolean isSingleSyntheticMethod) {
+ this(descriptor, id, isSingleSyntheticMethod, false);
}
SyntheticKind(
String descriptor,
+ int id,
+ boolean isSingleSyntheticMethod,
+ boolean isFixedSuffixSynthetic) {
+ this(descriptor, id, isSingleSyntheticMethod, isFixedSuffixSynthetic, false);
+ }
+
+ SyntheticKind(
+ String descriptor,
+ int id,
boolean isSingleSyntheticMethod,
boolean isFixedSuffixSynthetic,
boolean mayOverridesNonProgramType) {
this.descriptor = descriptor;
+ this.id = id;
this.isSingleSyntheticMethod = isSingleSyntheticMethod;
this.isFixedSuffixSynthetic = isFixedSuffixSynthetic;
this.mayOverridesNonProgramType = mayOverridesNonProgramType;
@@ -81,6 +87,15 @@
}
return null;
}
+
+ public static SyntheticKind fromId(int id) {
+ for (SyntheticKind kind : values()) {
+ if (kind.id == id) {
+ return kind;
+ }
+ }
+ return null;
+ }
}
private static final String SYNTHETIC_CLASS_SEPARATOR = "$$";
@@ -199,7 +214,7 @@
return clazz.getBinaryName().endsWith(kind.descriptor);
}
String separator = getPhaseSeparator(phase);
- int i = typeName.indexOf(separator);
+ int i = typeName.lastIndexOf(separator);
return i >= 0 && checkMatchFrom(kind, typeName, i, separator, phase == Phase.EXTERNAL);
}
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 5a4ee11..aae7abe 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -93,10 +93,21 @@
* @param clazz target type's Class to cast
* @param original an array of original elements
* @param mapper a mapper that rewrites an original element to a new one, maybe `null`
- * @param <T> target type
* @return an array with written elements
*/
public static <T> T[] map(Class<T[]> clazz, T[] original, Function<T, T> mapper) {
+ return map(original, mapper, clazz.cast(Array.newInstance(clazz.getComponentType(), 0)));
+ }
+
+ /**
+ * Rewrites the input array based on the given function.
+ *
+ * @param original an array of original elements
+ * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
+ * @param emptyArray an empty array
+ * @return an array with written elements
+ */
+ public static <T> T[] map(T[] original, Function<T, T> mapper, T[] emptyArray) {
ArrayList<T> results = null;
for (int i = 0; i < original.length; i++) {
T oldOne = original[i];
@@ -117,11 +128,7 @@
}
}
}
- if (results == null) {
- return original;
- }
- return results.toArray(
- clazz.cast(Array.newInstance(clazz.getComponentType(), results.size())));
+ return results != null ? results.toArray(emptyArray) : original;
}
public static int[] createIdentityArray(int size) {
diff --git a/src/main/java/com/android/tools/r8/utils/AssertionUtils.java b/src/main/java/com/android/tools/r8/utils/AssertionUtils.java
index c185096..bb1e59f 100644
--- a/src/main/java/com/android/tools/r8/utils/AssertionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AssertionUtils.java
@@ -4,10 +4,16 @@
package com.android.tools.r8.utils;
+import java.util.function.Supplier;
+
public class AssertionUtils {
public static boolean assertNotNull(Object o) {
assert o != null;
return true;
}
+
+ public static boolean forTesting(InternalOptions options, Supplier<Boolean> test) {
+ return options.testing.enableTestAssertions ? test.get() : true;
+ }
}
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 94b8404..12126a4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.code.IRCode;
@@ -50,6 +51,7 @@
import com.android.tools.r8.naming.MapVersion;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration;
import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration;
@@ -209,7 +211,6 @@
enableClassInlining = false;
enableClassStaticizer = false;
enableDevirtualization = false;
- horizontalClassMergerOptions.disable();
enableVerticalClassMerging = false;
enableEnumUnboxing = false;
enableUninstantiatedTypeOptimization = false;
@@ -219,6 +220,7 @@
enableSideEffectAnalysis = false;
enableTreeShakingOfLibraryMethodOverrides = false;
callSiteOptimizationOptions.disableOptimization();
+ horizontalClassMergerOptions.setRestrictToSynthetics();
}
public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
@@ -321,8 +323,6 @@
// TODO(b/138917494): Disable until we have numbers on potential performance penalties.
public boolean enableRedundantConstNumberOptimization = false;
- public boolean enablePcDebugInfoOutput = false;
-
public String synthesizedClassPrefix = "";
// Number of threads to use while processing the dex files.
@@ -349,6 +349,9 @@
// Contain the contents of the build properties file from the compiler command.
public DumpOptions dumpOptions;
+ // A mapping from methods to the api-level introducing them.
+ public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
+
// Hidden marker for classes.dex
private boolean hasMarker = false;
private Marker marker;
@@ -560,6 +563,10 @@
private final boolean enableTreeShaking;
private final boolean enableMinification;
+ public boolean isOptimizing() {
+ return hasProguardConfiguration() && getProguardConfiguration().isOptimizing();
+ }
+
public boolean isRelease() {
return !debug;
}
@@ -616,8 +623,18 @@
* If any non-static class merging is enabled, information about types referred to by instanceOf
* and check cast instructions needs to be collected.
*/
- public boolean isClassMergingExtensionRequired() {
- return horizontalClassMergerOptions.isEnabled() || enableVerticalClassMerging;
+ public boolean isClassMergingExtensionRequired(Enqueuer.Mode mode) {
+ if (mode.isInitialTreeShaking()) {
+ return (horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL)
+ && !horizontalClassMergerOptions.isRestrictedToSynthetics())
+ || enableVerticalClassMerging;
+ }
+ if (mode.isFinalTreeShaking()) {
+ return horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL)
+ && !horizontalClassMergerOptions.isRestrictedToSynthetics();
+ }
+ assert false;
+ return false;
}
@Override
@@ -1192,13 +1209,16 @@
}
}
- public static class HorizontalClassMergerOptions {
+ public class HorizontalClassMergerOptions {
// TODO(b/138781768): Set enable to true when this bug is resolved.
- public boolean enable =
+ private boolean enable =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
- public boolean enableConstructorMerging = true;
+ private boolean enableInterfaceMerging = false;
+ private boolean enableSyntheticMerging = true;
+ private boolean ignoreRuntimeTypeChecksForTesting = false;
+ private boolean restrictToSynthetics = false;
public int maxGroupSize = 30;
@@ -1206,6 +1226,10 @@
enable = false;
}
+ public void disableSyntheticMerging() {
+ enableSyntheticMerging = false;
+ }
+
public void enable() {
enable = true;
}
@@ -1214,20 +1238,56 @@
this.enable = enable;
}
+ public void enableInterfaceMerging() {
+ enableInterfaceMerging = true;
+ }
+
public int getMaxGroupSize() {
return maxGroupSize;
}
public boolean isConstructorMergingEnabled() {
- return enableConstructorMerging;
+ return true;
}
- public boolean isDisabled() {
- return !isEnabled();
+ public boolean isEnabled(HorizontalClassMerger.Mode mode) {
+ if (!enable || debug || intermediate) {
+ return false;
+ }
+ if (mode.isInitial()) {
+ return enableInlining && isShrinking();
+ }
+ assert mode.isFinal();
+ return true;
}
- public boolean isEnabled() {
- return enable;
+ public boolean isIgnoreRuntimeTypeChecksForTestingEnabled() {
+ return ignoreRuntimeTypeChecksForTesting;
+ }
+
+ public boolean isInterfaceMergingEnabled() {
+ assert !isInterfaceMergingEnabled(HorizontalClassMerger.Mode.INITIAL);
+ return isInterfaceMergingEnabled(HorizontalClassMerger.Mode.FINAL);
+ }
+
+ public boolean isSyntheticMergingEnabled() {
+ return enableSyntheticMerging;
+ }
+
+ public boolean isInterfaceMergingEnabled(HorizontalClassMerger.Mode mode) {
+ return enableInterfaceMerging && mode.isFinal();
+ }
+
+ public boolean isRestrictedToSynthetics() {
+ return restrictToSynthetics || !isOptimizing() || !isShrinking();
+ }
+
+ public void setIgnoreRuntimeTypeChecksForTesting() {
+ ignoreRuntimeTypeChecksForTesting = true;
+ }
+
+ public void setRestrictToSynthetics() {
+ restrictToSynthetics = true;
}
}
@@ -1318,7 +1378,9 @@
public boolean addCallEdgesForLibraryInvokes = false;
public boolean allowCheckDiscardedErrors = false;
+ public boolean allowClassInliningOfSynthetics = true;
public boolean allowInjectedAnnotationMethods = false;
+ public boolean allowInliningOfSynthetics = true;
public boolean allowTypeErrors =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
@@ -1366,6 +1428,9 @@
public boolean checkForNotExpandingMainDexTracingResult = false;
public Set<String> allowedUnusedDontWarnPatterns = new HashSet<>();
public boolean repackageWithNoMinification = false;
+ public boolean enableTestAssertions =
+ System.getProperty("com.android.tools.r8.enableTestAssertions") != null;
+ public boolean testEnableTestAssertions = false;
public boolean allowConflictingSyntheticTypes = false;
@@ -1541,7 +1606,7 @@
}
public boolean canUseDexPcAsDebugInformation() {
- return enablePcDebugInfoOutput && !debug && hasMinApi(AndroidApiLevel.O);
+ return !debug && hasMinApi(AndroidApiLevel.O);
}
public boolean isInterfaceMethodDesugaringEnabled() {
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index 1a61b44..89063c4 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -19,6 +19,25 @@
public class IterableUtils {
+ public static <S, T> boolean allIdentical(Iterable<S> iterable) {
+ return allIdentical(iterable, Function.identity());
+ }
+
+ public static <S, T> boolean allIdentical(Iterable<S> iterable, Function<S, T> fn) {
+ Iterator<S> iterator = iterable.iterator();
+ if (!iterator.hasNext()) {
+ return true;
+ }
+ T first = fn.apply(iterator.next());
+ while (iterator.hasNext()) {
+ T other = fn.apply(iterator.next());
+ if (other != first) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static <S, T> boolean any(
Iterable<S> iterable, Function<S, T> transform, Predicate<T> predicate) {
for (S element : iterable) {
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 3663ec2..da825d1 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -358,7 +358,7 @@
if (code != null) {
if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
if (appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1) {
- optimizeDexCodePositionsForPc(method, kotlinRemapper, mappedPositions);
+ optimizeDexCodePositionsForPc(method, appView, kotlinRemapper, mappedPositions);
} else {
optimizeDexCodePositions(
method, appView, kotlinRemapper, mappedPositions, identityMapping);
@@ -761,6 +761,7 @@
private static void optimizeDexCodePositionsForPc(
DexEncodedMethod method,
+ AppView<?> appView,
PositionRemapper positionRemapper,
List<MappedPosition> mappedPositions) {
// Do the actual processing for each method.
@@ -770,7 +771,9 @@
Pair<Integer, Position> lastPosition = new Pair<>();
DexDebugEventVisitor visitor =
- new DexDebugPositionState(debugInfo.startLine, method.getReference()) {
+ new DexDebugPositionState(
+ debugInfo.startLine,
+ appView.graphLens().getOriginalMethodSignature(method.getReference())) {
@Override
public void visit(Default defaultEvent) {
super.visit(defaultEvent);
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 7b4976c..1c260e3 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -7,6 +7,7 @@
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
@@ -50,6 +51,11 @@
return list.get(0);
}
+ public static <T> T firstMatching(List<T> list, Predicate<T> tester) {
+ int i = firstIndexMatching(list, tester);
+ return i >= 0 ? list.get(i) : null;
+ }
+
public static <T> int firstIndexMatching(List<T> list, Predicate<T> tester) {
for (int i = 0; i < list.size(); i++) {
if (tester.test(list.get(i))) {
@@ -153,6 +159,18 @@
return builder.build();
}
+ public static <T> LinkedList<T> newLinkedList(T element) {
+ LinkedList<T> list = new LinkedList<>();
+ list.add(element);
+ return list;
+ }
+
+ public static <T> LinkedList<T> newLinkedList(ForEachable<T> forEachable) {
+ LinkedList<T> list = new LinkedList<>();
+ forEachable.forEach(list::add);
+ return list;
+ }
+
public static <T> Optional<T> removeFirstMatch(List<T> list, Predicate<T> element) {
int index = firstIndexMatching(list, element);
if (index >= 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index abae424..63e881e 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -9,9 +9,16 @@
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
+import java.util.function.Supplier;
public class MapUtils {
+ public static <K, V> Map<K, V> clone(
+ Map<K, V> mapToClone, Map<K, V> newMap, Function<V, V> valueCloner) {
+ mapToClone.forEach((key, value) -> newMap.put(key, valueCloner.apply(value)));
+ return newMap;
+ }
+
public static <K, V> K firstKey(Map<K, V> map) {
return map.keySet().iterator().next();
}
@@ -20,6 +27,10 @@
return map.values().iterator().next();
}
+ public static <T, R> Function<T, R> ignoreKey(Supplier<R> supplier) {
+ return ignore -> supplier.get();
+ }
+
public static <K, V> Map<K, V> map(
Map<K, V> map,
IntFunction<Map<K, V>> factory,
diff --git a/src/main/java/com/android/tools/r8/utils/StreamUtils.java b/src/main/java/com/android/tools/r8/utils/StreamUtils.java
index 8d4ebc3..53a3010 100644
--- a/src/main/java/com/android/tools/r8/utils/StreamUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StreamUtils.java
@@ -8,6 +8,7 @@
import java.io.InputStream;
public class StreamUtils {
+
/**
* Read all data from the stream into a byte[], close the stream and return the bytes.
* @return The bytes of the stream
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
index 5a4bc89..8ef5506 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
@@ -8,13 +8,19 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodSignature;
+import com.google.common.collect.Iterables;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Function;
-public class DexMethodSignatureSet implements Iterable<DexMethodSignature> {
+public class DexMethodSignatureSet implements Collection<DexMethodSignature> {
+
+ private static final DexMethodSignatureSet EMPTY =
+ new DexMethodSignatureSet(Collections.emptySet());
private final Set<DexMethodSignature> backing;
@@ -34,6 +40,11 @@
return new DexMethodSignatureSet(new LinkedHashSet<>());
}
+ public static DexMethodSignatureSet empty() {
+ return EMPTY;
+ }
+
+ @Override
public boolean add(DexMethodSignature signature) {
return backing.add(signature);
}
@@ -50,8 +61,9 @@
return add(method.getReference());
}
- public void addAll(Iterable<DexMethodSignature> signatures) {
- signatures.forEach(this::add);
+ @Override
+ public boolean addAll(Collection<? extends DexMethodSignature> collection) {
+ return backing.addAll(collection);
}
public void addAllMethods(Iterable<DexEncodedMethod> methods) {
@@ -64,19 +76,53 @@
public <T> void addAll(Iterable<T> elements, Function<T, Iterable<DexMethodSignature>> fn) {
for (T element : elements) {
- addAll(fn.apply(element));
+ Iterables.addAll(this, fn.apply(element));
}
}
+ @Override
+ public void clear() {
+ backing.clear();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return backing.contains(o);
+ }
+
public boolean contains(DexMethodSignature signature) {
return backing.contains(signature);
}
@Override
+ public boolean containsAll(Collection<?> collection) {
+ return backing.containsAll(collection);
+ }
+
+ public boolean containsAnyOf(Iterable<DexMethodSignature> signatures) {
+ for (DexMethodSignature signature : signatures) {
+ if (contains(signature)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return backing.isEmpty();
+ }
+
+ @Override
public Iterator<DexMethodSignature> iterator() {
return backing.iterator();
}
+ @Override
+ public boolean remove(Object o) {
+ return backing.remove(o);
+ }
+
public boolean remove(DexMethodSignature signature) {
return backing.remove(signature);
}
@@ -85,11 +131,32 @@
return remove(method.getSignature());
}
- public void removeAll(Iterable<DexMethodSignature> signatures) {
- signatures.forEach(this::remove);
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ return backing.removeAll(collection);
}
public void removeAllMethods(Iterable<DexEncodedMethod> methods) {
methods.forEach(this::remove);
}
+
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ return backing.retainAll(collection);
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return backing.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] ts) {
+ return backing.toArray(ts);
+ }
}
diff --git a/src/test/examples/shaking1/print-mapping-dex.ref b/src/test/examples/shaking1/print-mapping-dex.ref
index 602e5f8..7ba8ee8 100644
--- a/src/test/examples/shaking1/print-mapping-dex.ref
+++ b/src/test/examples/shaking1/print-mapping-dex.ref
@@ -1,5 +1,5 @@
shaking1.Shaking -> shaking1.Shaking:
shaking1.Used -> a.a:
java.lang.String method() -> a
- 1:1:void main(java.lang.String[]):8:8 -> main
- 1:1:void <init>(java.lang.String):12:12 -> <init>
+ 0:16:void main(java.lang.String[]):8:8 -> main
+ 0:3:void <init>(java.lang.String):12:12 -> <init>
diff --git a/src/test/examplesAndroidApi/softverificationerror/ApiCallerInlined.java b/src/test/examplesAndroidApi/softverificationerror/ApiCallerInlined.java
new file mode 100644
index 0000000..0ef2b5c
--- /dev/null
+++ b/src/test/examplesAndroidApi/softverificationerror/ApiCallerInlined.java
@@ -0,0 +1,41 @@
+// 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.example.softverificationerror;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Build;
+
+public class ApiCallerInlined {
+
+ public static void callApi(android.content.Context context) {
+ // Create the NotificationChannel, but only on API 26+ because
+ // the NotificationChannel class is new and not in the support library
+ if (Build.VERSION.SDK_INT >= 26) {
+ constructUnknownObjectAndCallUnknownMethod(context);
+ }
+ }
+
+ public static void constructUnknownObject(android.content.Context context) {
+ NotificationChannel channel =
+ new NotificationChannel("CHANNEL_ID", "FOO", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription("This is a test channel");
+ }
+
+ public static void callUnknownMethod(android.content.Context context) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(null);
+ }
+
+ public static void constructUnknownObjectAndCallUnknownMethod(android.content.Context context) {
+ NotificationChannel channel =
+ new NotificationChannel("CHANNEL_ID", "FOO", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription("This is a test channel");
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(channel);
+ }
+}
diff --git a/src/test/examplesAndroidApi/softverificationerror/ApiCallerOutlined.java b/src/test/examplesAndroidApi/softverificationerror/ApiCallerOutlined.java
new file mode 100644
index 0000000..47c3e61
--- /dev/null
+++ b/src/test/examplesAndroidApi/softverificationerror/ApiCallerOutlined.java
@@ -0,0 +1,16 @@
+// 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.example.softverificationerror;
+
+import android.os.Build;
+
+public class ApiCallerOutlined {
+
+ public static void callApi(android.content.Context context) {
+ if (Build.VERSION.SDK_INT >= 26) {
+ ApiCallerInlined.constructUnknownObjectAndCallUnknownMethod(context);
+ }
+ }
+}
diff --git a/src/test/examplesAndroidApi/softverificationerror/Main.java b/src/test/examplesAndroidApi/softverificationerror/Main.java
new file mode 100644
index 0000000..f2a4376
--- /dev/null
+++ b/src/test/examplesAndroidApi/softverificationerror/Main.java
@@ -0,0 +1,13 @@
+// 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.example.softverificationerror;
+
+public class Main {
+
+ public static void test(android.content.Context context) {
+ ApiCallerInlined.callApi(context);
+ ApiCallerOutlined.callApi(context);
+ }
+}
diff --git a/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging.java b/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging.java
deleted file mode 100644
index 2362165..0000000
--- a/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging.java
+++ /dev/null
@@ -1,43 +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 horizontalclassmerging;
-
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
-
-public class BasicNestHostHorizontalClassMerging {
- // Prevent merging with BasicNestHostHorizontalClassMerging2.
- private String name;
-
- private BasicNestHostHorizontalClassMerging(String name) {
- this.name = name;
- }
-
- @NeverInline
- private void print(String v) {
- System.out.println(name + ": " + v);
- }
-
- public static void main(String[] args) {
- BasicNestHostHorizontalClassMerging host = new BasicNestHostHorizontalClassMerging("1");
- new A(host);
- new B(host);
- BasicNestHostHorizontalClassMerging2.main(args);
- }
-
- @NeverClassInline
- public static class A {
- public A(BasicNestHostHorizontalClassMerging parent) {
- parent.print("a");
- }
- }
-
- @NeverClassInline
- public static class B {
- public B(BasicNestHostHorizontalClassMerging parent) {
- parent.print("b");
- }
- }
-}
diff --git a/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging2.java b/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging2.java
deleted file mode 100644
index 78718e1..0000000
--- a/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging2.java
+++ /dev/null
@@ -1,35 +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 horizontalclassmerging;
-
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
-
-public class BasicNestHostHorizontalClassMerging2 {
- @NeverInline
- public static void main(String[] args) {
- new A();
- new B();
- }
-
- @NeverInline
- private static void print(String v) {
- System.out.println("2: " + v);
- }
-
- @NeverClassInline
- public static class A {
- public A() {
- print("a");
- }
- }
-
- @NeverClassInline
- public static class B {
- public B() {
- print("b");
- }
- }
-}
diff --git a/src/test/examplesJava11/horizontalclassmerging/NestClassMergingTest.java b/src/test/examplesJava11/horizontalclassmerging/NestClassMergingTest.java
new file mode 100644
index 0000000..b8ef472
--- /dev/null
+++ b/src/test/examplesJava11/horizontalclassmerging/NestClassMergingTest.java
@@ -0,0 +1,17 @@
+// 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 horizontalclassmerging;
+
+public class NestClassMergingTest {
+
+ public static void main(String[] args) {
+ NestHostA hostA = new NestHostA();
+ new NestHostA.NestMemberA();
+ new NestHostA.NestMemberB(hostA);
+ NestHostB hostB = new NestHostB();
+ new NestHostB.NestMemberA();
+ new NestHostB.NestMemberB(hostB);
+ }
+}
diff --git a/src/test/examplesJava11/horizontalclassmerging/NestHostA.java b/src/test/examplesJava11/horizontalclassmerging/NestHostA.java
new file mode 100644
index 0000000..13973b6
--- /dev/null
+++ b/src/test/examplesJava11/horizontalclassmerging/NestHostA.java
@@ -0,0 +1,36 @@
+// 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 horizontalclassmerging;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
+public class NestHostA {
+
+ @NeverInline
+ private void privatePrint(String v) {
+ System.out.println(v);
+ }
+
+ @NeverInline
+ private static void privateStaticPrint(String v) {
+ System.out.println(v);
+ }
+
+ @NeverClassInline
+ public static class NestMemberA {
+ public NestMemberA() {
+ NestHostA.privateStaticPrint("NestHostA$NestMemberA");
+ }
+ }
+
+ @NeverClassInline
+ public static class NestMemberB {
+ public NestMemberB(NestHostA host) {
+ host.privatePrint("NestHostA$NestMemberB");
+ }
+ }
+}
diff --git a/src/test/examplesJava11/horizontalclassmerging/NestHostB.java b/src/test/examplesJava11/horizontalclassmerging/NestHostB.java
new file mode 100644
index 0000000..99ac10b
--- /dev/null
+++ b/src/test/examplesJava11/horizontalclassmerging/NestHostB.java
@@ -0,0 +1,36 @@
+// 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 horizontalclassmerging;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
+public class NestHostB {
+
+ @NeverInline
+ private void privatePrint(String v) {
+ System.out.println(v);
+ }
+
+ @NeverInline
+ private static void privateStaticPrint(String v) {
+ System.out.println(v);
+ }
+
+ @NeverClassInline
+ public static class NestMemberA {
+ public NestMemberA() {
+ NestHostB.privateStaticPrint("NestHostB$NestMemberA");
+ }
+ }
+
+ @NeverClassInline
+ public static class NestMemberB {
+ public NestMemberB(NestHostB host) {
+ host.privatePrint("NestHostB$NestMemberB");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 637a88b..d78af9d 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -61,6 +61,8 @@
private List<String> applyMappingMaps = new ArrayList<>();
private final List<Path> features = new ArrayList<>();
+ private boolean createDefaultProguardMapConsumer = true;
+
@Override
R8TestCompileResult internalCompile(
Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
@@ -69,21 +71,23 @@
builder.addProguardConfiguration(keepRules, Origin.unknown());
}
builder.addMainDexRulesFiles(mainDexRulesFiles);
- StringBuilder proguardMapBuilder = new StringBuilder();
builder.setDisableTreeShaking(!enableTreeShaking);
builder.setDisableMinification(!enableMinification);
- builder.setProguardMapConsumer(
- new StringConsumer() {
- @Override
- public void accept(String string, DiagnosticsHandler handler) {
- proguardMapBuilder.append(string);
- }
+ StringBuilder proguardMapBuilder = new StringBuilder();
+ if (createDefaultProguardMapConsumer) {
+ builder.setProguardMapConsumer(
+ new StringConsumer() {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ proguardMapBuilder.append(string);
+ }
- @Override
- public void finished(DiagnosticsHandler handler) {
- // Nothing to do.
- }
- });
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ // Nothing to do.
+ }
+ });
+ }
if (!applyMappingMaps.isEmpty()) {
try {
@@ -118,7 +122,7 @@
app.get(),
box.proguardConfiguration,
box.syntheticProguardRules,
- proguardMapBuilder.toString(),
+ createDefaultProguardMapConsumer ? proguardMapBuilder.toString() : null,
graphConsumer,
minApiLevel,
features);
@@ -440,6 +444,11 @@
return self();
}
+ public T noClassInliningOfSynthetics() {
+ return addOptionsModification(
+ options -> options.testing.allowClassInliningOfSynthetics = false);
+ }
+
public T noClassStaticizing() {
return noClassStaticizing(true);
}
@@ -463,8 +472,21 @@
}
public T noHorizontalClassMerging(Class<?> clazz) {
- return addKeepRules(
- "-" + NoHorizontalClassMergingRule.RULE_NAME + " class " + clazz.getTypeName());
+ return noHorizontalClassMerging(clazz.getTypeName());
+ }
+
+ public T noHorizontalClassMerging(String typeName) {
+ return addKeepRules("-" + NoHorizontalClassMergingRule.RULE_NAME + " class " + typeName)
+ .enableProguardTestOptions();
+ }
+
+ public T noHorizontalClassMergingOfSynthetics() {
+ return addOptionsModification(
+ options -> options.horizontalClassMergerOptions().disableSyntheticMerging());
+ }
+
+ public T noInliningOfSynthetics() {
+ return addOptionsModification(options -> options.testing.allowInliningOfSynthetics = false);
}
public T enableNoUnusedInterfaceRemovalAnnotations() {
@@ -489,6 +511,13 @@
return addInternalKeepRules("-nohorizontalclassmerging class " + clazz);
}
+ public T addNoHorizontalClassMergingRule(String... classes) {
+ for (String clazz : classes) {
+ addNoHorizontalClassMergingRule(clazz);
+ }
+ return self();
+ }
+
public T enableMemberValuePropagationAnnotations() {
return enableMemberValuePropagationAnnotations(true);
}
@@ -661,4 +690,9 @@
features.add(path);
return self();
}
+
+ public T noDefaultProguardMapConsumer() {
+ createDefaultProguardMapConsumer = false;
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/SoftVerificationErrorJarGenerator.java b/src/test/java/com/android/tools/r8/SoftVerificationErrorJarGenerator.java
new file mode 100644
index 0000000..50f6e05
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/SoftVerificationErrorJarGenerator.java
@@ -0,0 +1,347 @@
+// 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;
+
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.function.Supplier;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class SoftVerificationErrorJarGenerator {
+
+ public enum ApiCallerName {
+ CONSTRUCT_UNKNOWN("constructUnknownObject"),
+ CALL_UNKNOWN("callUnknownMethod"),
+ CONSTRUCT_AND_CALL_UNKNOWN("constructUnknownObjectAndCallUnknownMethod");
+
+ private String apiCallerMethodName;
+
+ ApiCallerName(String apiCallerMethodName) {
+ this.apiCallerMethodName = apiCallerMethodName;
+ }
+
+ public String getApiCallerMethodName() {
+ return apiCallerMethodName;
+ }
+ }
+
+ public static String NEW_API_CLASS_NAME = "android/app/NotificationChannel";
+ public static String NEW_API_CLASS_METHOD_NAME = "setDescription";
+ public static String EXISTING_API_METHOD_NAME = "createNotificationChannel";
+
+ public static void createJar(
+ Path archive,
+ int numberOfClasses,
+ boolean isOutlined,
+ ApiCallerName callerName,
+ String newApiClassName,
+ String newApiClassMethodName,
+ String existingApiNewMethodName)
+ throws IOException {
+ OpenOption[] options =
+ new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+ try (ZipOutputStream out =
+ new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(archive, options)))) {
+ IntBox intBox = new IntBox(0);
+ Pair<byte[], String> callerPair =
+ isOutlined
+ ? Dumps.dumpApiCallerInlined(
+ -1,
+ callerName.getApiCallerMethodName(),
+ newApiClassName,
+ newApiClassMethodName,
+ existingApiNewMethodName)
+ : null;
+ if (callerPair != null) {
+ // The outlined code will call the apiCallerName on the callerPair code.
+ ZipUtils.writeToZipStream(
+ out, callerPair.getSecond() + ".class", callerPair.getFirst(), ZipEntry.STORED);
+ }
+ byte[] mainBytes =
+ Dumps.dumpMain(
+ () -> {
+ if (intBox.get() > numberOfClasses) {
+ return null;
+ }
+ Pair<byte[], String> classData =
+ isOutlined
+ ? Dumps.dumpApiCallerOutlined(
+ intBox.getAndIncrement(),
+ callerPair.getSecond(),
+ callerName.getApiCallerMethodName())
+ : Dumps.dumpApiCallerInlined(
+ intBox.getAndIncrement(),
+ callerName.getApiCallerMethodName(),
+ newApiClassName,
+ newApiClassMethodName,
+ existingApiNewMethodName);
+ try {
+ ZipUtils.writeToZipStream(
+ out, classData.getSecond() + ".class", classData.getFirst(), ZipEntry.STORED);
+ } catch (IOException exception) {
+ throw new RuntimeException(exception);
+ }
+ return classData.getSecond();
+ });
+ ZipUtils.writeToZipStream(
+ out, "com/example/softverificationsample/TestRunner.class", mainBytes, ZipEntry.STORED);
+ }
+ }
+
+ public static class Dumps implements Opcodes {
+
+ public static byte[] dumpMain(Supplier<String> targetSupplier) {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/example/softverificationsample/TestRunner",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "run", "(Landroid/content/Context;)V", null, null);
+ methodVisitor.visitCode();
+ String target = targetSupplier.get();
+ while (target != null) {
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, target, "callApi", "(Landroid/content/Context;)V", false);
+ target = targetSupplier.get();
+ }
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ public static Pair<byte[], String> dumpApiCallerOutlined(
+ int index, String apiCallerName, String apiMethodCaller) {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ String binaryName =
+ "com/example/softverificationsample/ApiCallerOutlined" + (index > -1 ? index : "");
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, binaryName, null, "java/lang/Object", null);
+
+ classWriter.visitInnerClass(
+ "android/os/Build$VERSION", "android/os/Build", "VERSION", ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "callApi", "(Landroid/content/Context;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "android/os/Build$VERSION", "SDK_INT", "I");
+ methodVisitor.visitIntInsn(BIPUSH, 26);
+ Label label0 = new Label();
+ methodVisitor.visitJumpInsn(IF_ICMPLT, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, apiCallerName, apiMethodCaller, "(Landroid/content/Context;)V", false);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return Pair.create(classWriter.toByteArray(), binaryName);
+ }
+
+ public static Pair<byte[], String> dumpApiCallerInlined(
+ int index,
+ String apiMethodCaller,
+ String newApiClassName,
+ String newApiClassMethodName,
+ String existingApiNewMethodName) {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ String binaryName =
+ "com/example/softverificationsample/ApiCallerInlined" + (index > -1 ? index : "");
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, binaryName, null, "java/lang/Object", null);
+
+ classWriter.visitInnerClass(
+ "android/os/Build$VERSION", "android/os/Build", "VERSION", ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "callApi", "(Landroid/content/Context;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "android/os/Build$VERSION", "SDK_INT", "I");
+ methodVisitor.visitIntInsn(BIPUSH, 26);
+ Label label0 = new Label();
+ methodVisitor.visitJumpInsn(IF_ICMPLT, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, binaryName, apiMethodCaller, "(Landroid/content/Context;)V", false);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "constructUnknownObject",
+ "(Landroid/content/Context;)V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, newApiClassName);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitLdcInsn("CHANNEL_ID");
+ methodVisitor.visitLdcInsn("FOO");
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "android/app/NotificationManager", "IMPORTANCE_DEFAULT", "I");
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ newApiClassName,
+ "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;I)V",
+ false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitLdcInsn("This is a test channel");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, newApiClassName, newApiClassMethodName, "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(5, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "callUnknownMethod",
+ "(Landroid/content/Context;)V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLdcInsn(Type.getType("Landroid/app/NotificationManager;"));
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "android/content/Context",
+ "getSystemService",
+ "(Ljava/lang/Class;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "android/app/NotificationManager");
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "android/app/NotificationManager",
+ existingApiNewMethodName,
+ "(L" + newApiClassName + ";)V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "constructUnknownObjectAndCallUnknownMethod",
+ "(Landroid/content/Context;)V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, newApiClassName);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitLdcInsn("CHANNEL_ID");
+ methodVisitor.visitLdcInsn("FOO");
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "android/app/NotificationManager", "IMPORTANCE_DEFAULT", "I");
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ newApiClassName,
+ "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;I)V",
+ false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitLdcInsn("This is a test channel");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, newApiClassName, newApiClassMethodName, "(Ljava/lang/String;)V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLdcInsn(Type.getType("Landroid/app/NotificationManager;"));
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "android/content/Context",
+ "getSystemService",
+ "(Ljava/lang/Class;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "android/app/NotificationManager");
+ methodVisitor.visitVarInsn(ASTORE, 2);
+ methodVisitor.visitVarInsn(ALOAD, 2);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "android/app/NotificationManager",
+ existingApiNewMethodName,
+ "(L" + newApiClassName + ";)V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(5, 3);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return Pair.create(classWriter.toByteArray(), binaryName);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/SoftVerificationErrorJarRunner.java b/src/test/java/com/android/tools/r8/SoftVerificationErrorJarRunner.java
new file mode 100644
index 0000000..80a2879
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/SoftVerificationErrorJarRunner.java
@@ -0,0 +1,118 @@
+// 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;
+
+import static com.android.tools.r8.SoftVerificationErrorJarGenerator.EXISTING_API_METHOD_NAME;
+import static com.android.tools.r8.SoftVerificationErrorJarGenerator.NEW_API_CLASS_METHOD_NAME;
+import static com.android.tools.r8.SoftVerificationErrorJarGenerator.NEW_API_CLASS_NAME;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.SoftVerificationErrorJarGenerator.ApiCallerName;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ApkUtils;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class SoftVerificationErrorJarRunner extends TestBase {
+
+ public static Path DUMP_PATH =
+ Paths.get("third_party", "api-outlining", "simple-app-dump", "simple-app-dump.zip");
+ public static Path APK_PATH =
+ Paths.get("third_party", "api-outlining", "simple-app-dump", "app-release-unsigned.apk");
+
+ private final int numberOfClasses;
+ private final boolean isOutlined;
+
+ public SoftVerificationErrorJarRunner(int numberOfClasses, boolean isOutlined) {
+ this.numberOfClasses = numberOfClasses;
+ this.isOutlined = isOutlined;
+ }
+
+ public static void main(String[] args) throws Exception {
+ new SoftVerificationErrorJarRunner(1000, false).runTest();
+ }
+
+ public void runTest() throws Exception {
+
+ temp.create();
+
+ Path tempFolder = temp.newFolder().toPath();
+ Path outlineJar = tempFolder.resolve("outlined.jar");
+
+ SoftVerificationErrorJarGenerator.createJar(
+ outlineJar,
+ numberOfClasses,
+ isOutlined,
+ ApiCallerName.CONSTRUCT_AND_CALL_UNKNOWN,
+ NEW_API_CLASS_NAME,
+ NEW_API_CLASS_METHOD_NAME,
+ EXISTING_API_METHOD_NAME);
+
+ ZipUtils.unzip(DUMP_PATH.toString(), tempFolder.toFile());
+
+ File filteredProgramFolder = temp.newFolder();
+ BooleanBox seenTestRunner = new BooleanBox();
+ ZipUtils.unzip(
+ tempFolder.resolve("program.jar").toFile().toString(),
+ filteredProgramFolder,
+ zipEntry -> {
+ if (zipEntry.getName().equals("com/example/softverificationsample/TestRunner.class")) {
+ seenTestRunner.set();
+ return false;
+ }
+ return true;
+ });
+
+ assertTrue(seenTestRunner.get());
+
+ Path filteredProgramJar = tempFolder.resolve("filtered_program.jar");
+ ZipUtils.zip(filteredProgramJar, filteredProgramFolder.toPath());
+
+ // Build the app with R8.
+ Path output =
+ testForR8(Backend.DEX)
+ .addProgramFiles(outlineJar, filteredProgramJar)
+ .addClasspathFiles(tempFolder.resolve("classpath.jar"))
+ .addLibraryFiles(tempFolder.resolve("library.jar"))
+ // TODO(b/187496508): Modify keep rules to allow inlining and keep test code.
+ .addKeepRuleFiles(tempFolder.resolve("proguard.config"))
+ .addKeepRules("-keep class com.example.softverificationsample.* { *; }")
+ .setMinApi(AndroidApiLevel.M)
+ .allowUnusedProguardConfigurationRules()
+ .allowUnusedDontWarnPatterns()
+ .allowDiagnosticInfoMessages()
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+
+ Path finalApk = tempFolder.resolve("app-release-final.apk");
+ ProcessResult processResult = ApkUtils.apkMasseur(APK_PATH, output, finalApk);
+ // TODO(mkroghj): Figure out to have this command succeed when installing the apk
+ assertEquals(0, processResult.exitCode);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ String name =
+ "com.example.softverificationsample."
+ + (isOutlined ? "ApiCallerOutlined" : "ApiCallerInlined")
+ + (numberOfClasses - 1);
+ ClassSubject clazz = inspector.clazz(name);
+ assertThat(clazz, isPresent());
+ if (isOutlined) {
+ ClassSubject apiCallerInlined =
+ inspector.clazz("com.example.softverificationsample.ApiCallerInlined");
+ assertThat(apiCallerInlined, isPresent());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a6895ce..b24d16a 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1715,8 +1715,16 @@
return clazz.getTypeName();
}
- public static String examplesTypeName(Class<? extends ExamplesClass> clazz) throws Exception {
- return ReflectiveBuildPathUtils.resolveClassName(clazz);
+ public static ClassReference examplesClassReference(Class<? extends ExamplesClass> clazz) {
+ return Reference.classFromTypeName(examplesTypeName(clazz));
+ }
+
+ public static String examplesTypeName(Class<? extends ExamplesClass> clazz) {
+ try {
+ return ReflectiveBuildPathUtils.resolveClassName(clazz);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
public static AndroidApiLevel apiLevelWithDefaultInterfaceMethodsSupport() {
@@ -1746,6 +1754,10 @@
return AndroidApiLevel.K;
}
+ public static AndroidApiLevel apiLevelWithPcAsLineNumberSupport() {
+ return AndroidApiLevel.O;
+ }
+
public static boolean canUseJavaUtilObjects(TestParameters parameters) {
return parameters.isCfRuntime()
|| parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 121e7c4..3c5b479 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
@@ -48,6 +47,7 @@
public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
options -> {
+ options.testing.enableTestAssertions = true;
options.testing.allowUnusedDontWarnRules = false;
options.testing.allowUnnecessaryDontWarnWildcards = false;
options.testing.reportUnusedProguardConfigurationRules = true;
@@ -438,7 +438,7 @@
}
public T enableCoreLibraryDesugaring(
- AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+ AndroidApiLevel minApiLevel, StringConsumer keepRuleConsumer) {
return enableCoreLibraryDesugaring(
minApiLevel,
keepRuleConsumer,
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 5a7423a..1df89b1 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -43,6 +43,11 @@
.isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
}
+ public boolean canUseNestBasedAccesses() {
+ assert isCfRuntime() || isDexRuntime();
+ return isCfRuntime() && getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11);
+ }
+
// Convenience predicates.
public boolean isDexRuntime() {
return runtime.isDex();
diff --git a/src/test/java/com/android/tools/r8/TestingAssertionsTest.java b/src/test/java/com/android/tools/r8/TestingAssertionsTest.java
new file mode 100644
index 0000000..b2b64ec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestingAssertionsTest.java
@@ -0,0 +1,65 @@
+// 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;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+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 TestingAssertionsTest extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public TestingAssertionsTest(TestParameters parameters) {}
+
+ @Test(expected = CompilationFailedException.class)
+ public void testR8() throws Exception {
+ testForR8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .setMinApi(AndroidApiLevel.B)
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> {
+ assertTrue(options.testing.enableTestAssertions);
+ options.testing.testEnableTestAssertions = true;
+ })
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorThatMatches(diagnosticException(AssertionError.class));
+ });
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8() throws Exception {
+ testForD8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .setMinApi(AndroidApiLevel.B)
+ .addOptionsModification(
+ options -> {
+ assertTrue(options.testing.enableTestAssertions);
+ options.testing.testEnableTestAssertions = true;
+ })
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorThatMatches(diagnosticException(AssertionError.class));
+ });
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println("Hello World");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java
new file mode 100644
index 0000000..7620ae4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java
@@ -0,0 +1,87 @@
+// 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.apioutlining;
+
+import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.verifyThat;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test()
+ public void testR8() throws Exception {
+ Method apiLevel21 = A.class.getDeclaredMethod("apiLevel21");
+ Method apiLevel22 = B.class.getDeclaredMethod("apiLevel22");
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .apply(setMockApiLevelForMethod(apiLevel21, AndroidApiLevel.L))
+ .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A::apiLevel21", "B::apiLevel22");
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
+ runResult.inspect(
+ verifyThat(parameters, apiLevel22)
+ .inlinedIntoFromApiLevel(apiLevel21, AndroidApiLevel.L_MR1));
+ } else {
+ // TODO(b/188388130): Should only inline on minApi >= 22.
+ assertThrows(
+ AssertionError.class,
+ () ->
+ runResult.inspect(
+ verifyThat(parameters, apiLevel22)
+ .inlinedIntoFromApiLevel(apiLevel21, AndroidApiLevel.L_MR1)));
+ }
+ }
+
+ public static class B {
+ public static void apiLevel22() {
+ System.out.println("B::apiLevel22");
+ }
+ }
+
+ public static class A {
+
+ @NeverInline
+ public static void apiLevel21() {
+ System.out.println("A::apiLevel21");
+ B.apiLevel22();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.apiLevel21();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java
new file mode 100644
index 0000000..44263ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java
@@ -0,0 +1,86 @@
+// 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.apioutlining;
+
+import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.verifyThat;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiOutliningNoInliningOfHigherApiLevelTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public ApiOutliningNoInliningOfHigherApiLevelTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method minApi = A.class.getDeclaredMethod("minApi");
+ Method apiLevel22 = B.class.getDeclaredMethod("apiLevel22");
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A::minApi", "B::apiLevel22");
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
+ runResult.inspect(
+ verifyThat(parameters, apiLevel22)
+ .inlinedIntoFromApiLevel(minApi, AndroidApiLevel.L_MR1));
+ } else {
+ // TODO(b/188388130): Should only inline on minApi >= 22.
+ assertThrows(
+ AssertionError.class,
+ () ->
+ runResult.inspect(
+ verifyThat(parameters, apiLevel22)
+ .inlinedIntoFromApiLevel(minApi, AndroidApiLevel.L_MR1)));
+ }
+ }
+
+ public static class B {
+ public static void apiLevel22() {
+ System.out.println("B::apiLevel22");
+ }
+ }
+
+ public static class A {
+
+ @NeverInline
+ public static void minApi() {
+ System.out.println("A::minApi");
+ B.apiLevel22();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.minApi();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java
new file mode 100644
index 0000000..f10a6e9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java
@@ -0,0 +1,78 @@
+// 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.apioutlining;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Method;
+
+public abstract class ApiOutliningTestHelper {
+
+ static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+ ThrowableConsumer<T> setMockApiLevelForMethod(Method method, AndroidApiLevel apiLevel) {
+ return compilerBuilder -> {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options.methodApiMapping.put(Reference.methodFromMethod(method), apiLevel);
+ });
+ };
+ }
+
+ static ApiOutliningMethodVerificationHelper verifyThat(TestParameters parameters, Method method) {
+ return new ApiOutliningMethodVerificationHelper(parameters, method);
+ }
+
+ public static class ApiOutliningMethodVerificationHelper {
+
+ private final Method methodOfInterest;
+ private final TestParameters parameters;
+
+ public ApiOutliningMethodVerificationHelper(
+ TestParameters parameters, Method methodOfInterest) {
+ this.methodOfInterest = methodOfInterest;
+ this.parameters = parameters;
+ }
+
+ protected ThrowingConsumer<CodeInspector, Exception> inlinedIntoFromApiLevel(
+ Method method, AndroidApiLevel apiLevel) {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevel)
+ ? inlinedInto(method)
+ : notInlinedInto(method);
+ }
+
+ private ThrowingConsumer<CodeInspector, Exception> notInlinedInto(Method method) {
+ return inspector -> {
+ MethodSubject candidate = inspector.method(methodOfInterest);
+ assertThat(candidate, isPresent());
+ MethodSubject target = inspector.method(method);
+ assertThat(target, isPresent());
+ assertThat(target, CodeMatchers.invokesMethod(candidate));
+ };
+ }
+
+ private ThrowingConsumer<CodeInspector, Exception> inlinedInto(Method method) {
+ return inspector -> {
+ MethodSubject candidate = inspector.method(methodOfInterest);
+ if (!candidate.isPresent()) {
+ return;
+ }
+ MethodSubject target = inspector.method(method);
+ assertThat(target, isPresent());
+ assertThat(target, not(CodeMatchers.invokesMethod(candidate)));
+ };
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
index 5484fd8..817c786 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
@@ -30,6 +30,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -37,27 +39,25 @@
.assertSuccessWithOutputLines("42", "13", "21", "39", "print a", "print b")
.inspect(
codeInspector -> {
- ClassSubject aClassSubject = codeInspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
- FieldSubject classIdFieldSubject =
- aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
- assertThat(classIdFieldSubject, isPresent());
+ ClassSubject aClassSubject = codeInspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ FieldSubject classIdFieldSubject =
+ aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+ assertThat(classIdFieldSubject, isPresent());
- MethodSubject firstInitSubject = aClassSubject.init("int");
- assertThat(firstInitSubject, isPresent());
- assertThat(
- firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+ MethodSubject firstInitSubject = aClassSubject.init("int");
+ assertThat(firstInitSubject, isPresent());
+ assertThat(firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
- MethodSubject otherInitSubject = aClassSubject.init("long", "int");
- assertThat(otherInitSubject, isPresent());
- assertThat(
- otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+ MethodSubject otherInitSubject = aClassSubject.init("long", "int");
+ assertThat(otherInitSubject, isPresent());
+ assertThat(otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
- MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
- assertThat(printSubject, isPresent());
- assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
+ MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
+ assertThat(printSubject, isPresent());
+ assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
- assertThat(codeInspector.clazz(B.class), not(isPresent()));
+ assertThat(codeInspector.clazz(B.class), not(isPresent()));
});
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
new file mode 100644
index 0000000..bf50ab1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
@@ -0,0 +1,269 @@
+// 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.classmerging.horizontal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.R.horizontalclassmerging.NestClassMergingTest;
+import com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.R.horizontalclassmerging.NestHostA;
+import com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.R.horizontalclassmerging.NestHostB;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesClass;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesJava11RootPackage;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesPackage;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Streams;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+public class NestClassMergingTestRunner extends HorizontalClassMergingTestBase {
+
+ public static class R extends ExamplesJava11RootPackage {
+ public static class horizontalclassmerging extends ExamplesPackage {
+ public static class NestClassMergingTest extends ExamplesClass {}
+
+ public static class NestHostA extends ExamplesClass {
+ public static class NestMemberA extends ExamplesClass {}
+
+ public static class NestMemberB extends ExamplesClass {}
+ }
+
+ public static class NestHostB extends ExamplesClass {
+ public static class NestMemberA extends ExamplesClass {}
+
+ public static class NestMemberB extends ExamplesClass {}
+ }
+ }
+ }
+
+ public NestClassMergingTestRunner(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ runTest(
+ builder ->
+ builder.addHorizontallyMergedClassesInspector(
+ inspector -> {
+ if (parameters.canUseNestBasedAccesses()) {
+ inspector
+ .assertIsCompleteMergeGroup(
+ classRef(NestHostA.class),
+ classRef(NestHostA.NestMemberA.class),
+ classRef(NestHostA.NestMemberB.class))
+ .assertIsCompleteMergeGroup(
+ classRef(NestHostB.class),
+ classRef(NestHostB.NestMemberA.class),
+ classRef(NestHostB.NestMemberB.class));
+ } else {
+ inspector.assertIsCompleteMergeGroup(
+ classRef(NestHostA.class),
+ classRef(NestHostA.NestMemberA.class),
+ classRef(NestHostA.NestMemberB.class),
+ classRef(NestHostB.class),
+ classRef(NestHostB.NestMemberA.class),
+ classRef(NestHostB.NestMemberB.class));
+ }
+ }));
+ }
+
+ @Test
+ public void testMergeHostIntoNestMemberA() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ runTest(
+ builder ->
+ builder
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(
+ classRef(NestHostA.class), classRef(NestHostA.NestMemberA.class))
+ .assertIsCompleteMergeGroup(
+ classRef(NestHostB.class), classRef(NestHostB.NestMemberA.class))
+ .assertClassReferencesNotMerged(
+ classRef(NestHostA.NestMemberB.class),
+ classRef(NestHostB.NestMemberB.class)))
+ .addNoHorizontalClassMergingRule(
+ examplesTypeName(NestHostA.NestMemberB.class),
+ examplesTypeName(NestHostB.NestMemberB.class))
+ .addOptionsModification(
+ options -> {
+ options.testing.horizontalClassMergingTarget =
+ (canditates, target) -> {
+ Set<ClassReference> candidateClassReferences =
+ Streams.stream(canditates)
+ .map(DexClass::getClassReference)
+ .collect(Collectors.toSet());
+ if (candidateClassReferences.contains(classRef(NestHostA.class))) {
+ assertEquals(
+ ImmutableSet.of(
+ classRef(NestHostA.class),
+ classRef(NestHostA.NestMemberA.class)),
+ candidateClassReferences);
+ } else {
+ assertEquals(
+ ImmutableSet.of(
+ classRef(NestHostB.class),
+ classRef(NestHostB.NestMemberA.class)),
+ candidateClassReferences);
+ }
+ return Iterables.find(
+ canditates,
+ candidate -> {
+ ClassReference classReference = candidate.getClassReference();
+ return classReference.equals(
+ classRef(NestHostA.NestMemberA.class))
+ || classReference.equals(
+ classRef(NestHostB.NestMemberA.class));
+ });
+ };
+ }));
+ }
+
+ @Test
+ public void testMergeHostIntoNestMemberB() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ runTest(
+ builder ->
+ builder
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(
+ classRef(NestHostA.class), classRef(NestHostA.NestMemberB.class))
+ .assertIsCompleteMergeGroup(
+ classRef(NestHostB.class), classRef(NestHostB.NestMemberB.class))
+ .assertClassReferencesNotMerged(
+ classRef(NestHostA.NestMemberA.class),
+ classRef(NestHostB.NestMemberA.class)))
+ .addNoHorizontalClassMergingRule(
+ examplesTypeName(NestHostA.NestMemberA.class),
+ examplesTypeName(NestHostB.NestMemberA.class))
+ .addOptionsModification(
+ options -> {
+ options.testing.horizontalClassMergingTarget =
+ (canditates, target) -> {
+ Set<ClassReference> candidateClassReferences =
+ Streams.stream(canditates)
+ .map(DexClass::getClassReference)
+ .collect(Collectors.toSet());
+ if (candidateClassReferences.contains(classRef(NestHostA.class))) {
+ assertEquals(
+ ImmutableSet.of(
+ classRef(NestHostA.class),
+ classRef(NestHostA.NestMemberB.class)),
+ candidateClassReferences);
+ } else {
+ assertEquals(
+ ImmutableSet.of(
+ classRef(NestHostB.class),
+ classRef(NestHostB.NestMemberB.class)),
+ candidateClassReferences);
+ }
+ return Iterables.find(
+ canditates,
+ candidate -> {
+ ClassReference classReference = candidate.getClassReference();
+ return classReference.equals(
+ classRef(NestHostA.NestMemberB.class))
+ || classReference.equals(
+ classRef(NestHostB.NestMemberB.class));
+ });
+ };
+ }));
+ }
+
+ @Test
+ public void testMergeMemberAIntoNestHost() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ runTest(
+ builder ->
+ builder
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(
+ classRef(NestHostA.class), classRef(NestHostA.NestMemberA.class))
+ .assertIsCompleteMergeGroup(
+ classRef(NestHostB.class), classRef(NestHostB.NestMemberA.class))
+ .assertClassReferencesNotMerged(
+ classRef(NestHostA.NestMemberB.class),
+ classRef(NestHostB.NestMemberB.class)))
+ .addNoHorizontalClassMergingRule(
+ examplesTypeName(NestHostA.NestMemberB.class),
+ examplesTypeName(NestHostB.NestMemberB.class))
+ .addOptionsModification(
+ options -> {
+ options.testing.horizontalClassMergingTarget =
+ (canditates, target) -> {
+ Set<ClassReference> candidateClassReferences =
+ Streams.stream(canditates)
+ .map(DexClass::getClassReference)
+ .collect(Collectors.toSet());
+ if (candidateClassReferences.contains(classRef(NestHostA.class))) {
+ assertEquals(
+ ImmutableSet.of(
+ classRef(NestHostA.class),
+ classRef(NestHostA.NestMemberA.class)),
+ candidateClassReferences);
+ } else {
+ assertEquals(
+ ImmutableSet.of(
+ classRef(NestHostB.class),
+ classRef(NestHostB.NestMemberA.class)),
+ candidateClassReferences);
+ }
+ return Iterables.find(
+ canditates,
+ candidate -> {
+ ClassReference classReference = candidate.getClassReference();
+ return classReference.equals(classRef(NestHostA.class))
+ || classReference.equals(classRef(NestHostB.class));
+ });
+ };
+ }));
+ }
+
+ private void runTest(ThrowableConsumer<R8FullTestBuilder> configuration) throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(examplesTypeName(NestClassMergingTest.class))
+ .addExamplesProgramFiles(R.class)
+ .apply(configuration)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), examplesTypeName(NestClassMergingTest.class))
+ .assertSuccessWithOutputLines(
+ "NestHostA$NestMemberA",
+ "NestHostA$NestMemberB",
+ "NestHostB$NestMemberA",
+ "NestHostB$NestMemberB");
+ }
+
+ private static ClassReference classRef(Class<? extends ExamplesClass> clazz) {
+ return examplesClassReference(clazz);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java
deleted file mode 100644
index 215ce7a..0000000
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java
+++ /dev/null
@@ -1,104 +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.classmerging.horizontal;
-
-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.isPrivate;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.Jdk9TestUtils;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.classmerging.horizontal.NestClassTest.R.horizontalclassmerging.BasicNestHostHorizontalClassMerging;
-import com.android.tools.r8.classmerging.horizontal.NestClassTest.R.horizontalclassmerging.BasicNestHostHorizontalClassMerging2;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesClass;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesJava11RootPackage;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesPackage;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Test;
-import org.junit.runners.Parameterized;
-
-public class NestClassTest extends HorizontalClassMergingTestBase {
- public static class R extends ExamplesJava11RootPackage {
- public static class horizontalclassmerging extends ExamplesPackage {
- public static class BasicNestHostHorizontalClassMerging extends ExamplesClass {
- public static class A extends ExamplesClass {}
-
- public static class B extends ExamplesClass {}
- }
-
- public static class BasicNestHostHorizontalClassMerging2 extends ExamplesClass {
- public static class A extends ExamplesClass {}
-
- public static class B extends ExamplesClass {}
- }
- }
- }
-
- public NestClassTest(TestParameters parameters) {
- super(parameters);
- }
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build();
- }
-
- @Test
- public void testR8() throws Exception {
- testForR8(parameters.getBackend())
- .addKeepMainRule(examplesTypeName(BasicNestHostHorizontalClassMerging.class))
- .addExamplesProgramFiles(R.class)
- .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp))
- .enableInliningAnnotations()
- .enableNeverClassInliningAnnotations()
- .compile()
- .run(parameters.getRuntime(), examplesTypeName(BasicNestHostHorizontalClassMerging.class))
- .assertSuccessWithOutputLines("1: a", "1: b", "2: a", "2: b")
- .inspect(
- codeInspector -> {
- ClassSubject class1A =
- codeInspector.clazz(
- examplesTypeName(BasicNestHostHorizontalClassMerging.A.class));
- ClassSubject class2A =
- codeInspector.clazz(
- examplesTypeName(BasicNestHostHorizontalClassMerging2.A.class));
- ClassSubject class1 =
- codeInspector.clazz(examplesTypeName(BasicNestHostHorizontalClassMerging.class));
- ClassSubject class2 =
- codeInspector.clazz(examplesTypeName(BasicNestHostHorizontalClassMerging2.class));
- assertThat(class1, isPresent());
- assertThat(class2, isPresent());
- assertThat(class1A, isPresent());
- assertThat(class2A, isPresent());
-
- MethodSubject printClass1MethodSubject =
- class1.method("void", "print", String.class.getTypeName());
- assertThat(printClass1MethodSubject, isPresent());
- assertThat(printClass1MethodSubject, isPrivate());
-
- MethodSubject printClass2MethodSubject =
- class2.method("void", "print", String.class.getTypeName());
- assertThat(printClass2MethodSubject, isPresent());
- assertThat(printClass2MethodSubject, isPrivate());
- assertThat(printClass2MethodSubject, isStatic());
-
- assertThat(
- codeInspector.clazz(
- examplesTypeName(BasicNestHostHorizontalClassMerging.B.class)),
- isAbsent());
- assertThat(
- codeInspector.clazz(
- examplesTypeName(BasicNestHostHorizontalClassMerging2.B.class)),
- isAbsent());
-
- // TODO(b/165517236): Explicitly check 1.B is merged into 1.A, and 2.B into 2.A.
- });
- }
-}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
index d74ae0a..580598b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import org.junit.Test;
public class OverrideDefaultMethodTest extends HorizontalClassMergingTestBase {
@@ -30,7 +30,17 @@
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ inspector.assertNoClassesMerged();
+ } else {
+ inspector
+ .assertClassesNotMerged(A.class, B.class)
+ .assertIsCompleteMergeGroup(
+ SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+ SyntheticItemsTestUtils.syntheticCompanionClass(J.class));
+ }
+ })
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("I", "B", "J")
.inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index 95dc561..65ca772 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -13,7 +13,11 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.A;
+import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.B;
+import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.I;
+import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.J;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import org.junit.Test;
public class OverrideDefaultOnSuperMethodTest extends HorizontalClassMergingTestBase {
@@ -32,7 +36,17 @@
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ inspector.assertNoClassesMerged();
+ } else {
+ inspector
+ .assertClassesNotMerged(A.class, B.class)
+ .assertIsCompleteMergeGroup(
+ SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+ SyntheticItemsTestUtils.syntheticCompanionClass(J.class));
+ }
+ })
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("I", "B", "J")
.inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java
new file mode 100644
index 0000000..650d804
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+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.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassHierarchyCycleAfterMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassHierarchyCycleAfterMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // I and J are not eligible for merging since that would lead to a cycle in the class
+ // hierarchy.
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+ assertThat(mainClassSubject, isImplementing(inspector.clazz(K.class)));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class Main implements K {
+
+ public static void main(String[] args) {}
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {}
+
+ @NoHorizontalClassMerging
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J extends I {}
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface K extends J {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
index 862c386..1c5d915 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -36,6 +37,7 @@
this.parameters = parameters;
}
+ // TODO(b/173990042): Disallow merging of A and B in the first round of class merging.
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
@@ -45,11 +47,25 @@
// contributes the default method K.m() to A, and the merging of J into I would contribute
// the default method J.m() to A.
.addHorizontallyMergedClassesInspector(
- inspector ->
+ inspector -> {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
inspector
.assertIsCompleteMergeGroup(A.class, B.class)
.assertMergedInto(B.class, A.class)
- .assertClassesNotMerged(I.class, J.class, K.class))
+ .assertClassesNotMerged(I.class, J.class, K.class);
+ } else {
+ inspector
+ .assertIsCompleteMergeGroup(A.class, B.class)
+ .assertMergedInto(B.class, A.class)
+ .assertIsCompleteMergeGroup(I.class, J.class)
+ .assertClassesNotMerged(K.class);
+ }
+ })
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -66,7 +82,13 @@
ClassSubject bClassSubject = inspector.clazz(C.class);
assertThat(bClassSubject, isPresent());
- assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
+ assertThat(
+ bClassSubject,
+ isImplementing(
+ inspector.clazz(
+ parameters.canUseDefaultAndStaticInterfaceMethods()
+ ? J.class
+ : I.class)));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("A", "K", "J");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
index 538f7fe..d4f14bd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -17,7 +18,6 @@
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.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -44,7 +44,18 @@
// I and J are not eligible for merging, since class A (implements I) inherits a default m()
// method from K, which is also on J.
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ inspector.assertNoClassesMerged();
+ } else {
+ inspector.assertIsCompleteMergeGroup(I.class, J.class);
+ }
+ })
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -61,7 +72,13 @@
ClassSubject bClassSubject = inspector.clazz(B.class);
assertThat(bClassSubject, isPresent());
- assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
+ assertThat(
+ bClassSubject,
+ isImplementing(
+ inspector.clazz(
+ parameters.canUseDefaultAndStaticInterfaceMethods()
+ ? J.class
+ : I.class)));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("K", "J");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index 6fcd094..f9e85ba 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -16,8 +17,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -44,7 +45,19 @@
// I and J are not eligible for merging, since the lambda that implements I & J inherits a
// default m() method from K, which is also on J.
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> {
+ if (parameters.isCfRuntime()) {
+ inspector.assertIsCompleteMergeGroup(I.class, J.class);
+ } else {
+ inspector.assertNoClassesMerged();
+ }
+ })
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ options.horizontalClassMergerOptions().setIgnoreRuntimeTypeChecksForTesting();
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -56,10 +69,22 @@
inspector -> {
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
- assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
+ if (parameters.isCfRuntime()) {
+ assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+ } else {
+ assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
+ }
})
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("K", "J");
+ // TODO(b/173990042): Should succeed with "K", "J".
+ .applyIf(
+ parameters.isCfRuntime(),
+ builder ->
+ builder.assertFailureWithErrorThatThrows(
+ parameters.isCfRuntime(CfVm.JDK11)
+ ? AbstractMethodError.class
+ : IncompatibleClassChangeError.class),
+ builder -> builder.assertSuccessWithOutputLines("K", "J"));
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
index 7c647b4..7ca9cc5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
+import static org.junit.Assert.assertFalse;
+
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -33,9 +35,17 @@
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
- inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
+ .noClassInliningOfSynthetics()
+ .noHorizontalClassMergingOfSynthetics()
+ .noInliningOfSynthetics()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
index 31aac13..dad75a4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
+import static org.junit.Assert.assertFalse;
+
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -32,11 +34,18 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/173990042): We should be able to merge I and J.
.addHorizontallyMergedClassesInspector(
- inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
+ .noClassInliningOfSynthetics()
+ .noHorizontalClassMergingOfSynthetics()
+ .noInliningOfSynthetics()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
@@ -46,7 +55,7 @@
static class Main {
public static void main(String[] args) {
- Object o = (I & J) () -> "I & J";
+ Object o = (I & J) () -> System.currentTimeMillis() > 0 ? "I & J" : null;
System.out.println(((I) o).f());
System.out.println(((J) o).f());
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
index 683ce85..e58ca16 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
+import static org.junit.Assert.assertFalse;
+
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -34,11 +36,18 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/173990042): We should be able to merge I and J.
.addHorizontallyMergedClassesInspector(
- inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
+ .noClassInliningOfSynthetics()
+ .noHorizontalClassMergingOfSynthetics()
+ .noInliningOfSynthetics()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
index 8cb2212..a877121 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
+import static org.junit.Assert.assertFalse;
+
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -34,11 +36,18 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/173990042): We should be able to merge I and J.
.addHorizontallyMergedClassesInspector(
- inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
+ .noClassInliningOfSynthetics()
+ .noHorizontalClassMergingOfSynthetics()
+ .noInliningOfSynthetics()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
@@ -48,8 +57,8 @@
static class Main {
public static void main(String[] args) {
- System.out.println(((I) () -> "I").f());
- System.out.println(((J) () -> "J").f());
+ System.out.println(((I) () -> System.currentTimeMillis() > 0 ? "I" : null).f());
+ System.out.println(((J) () -> System.currentTimeMillis() > 0 ? "J" : null).f());
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
index c9a0d19..0807547 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -16,7 +17,6 @@
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.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -40,9 +40,13 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/173990042): I and J should be merged.
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
@@ -54,7 +58,6 @@
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
- assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("I", "J");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
index 9a9ccc3..5f30eff 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -16,7 +17,6 @@
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.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -40,9 +40,13 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/173990042): I and J should be merged.
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
@@ -54,7 +58,6 @@
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
- assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("I", "J");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
index 74aa943..24e5de8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -14,7 +15,6 @@
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.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -38,9 +38,13 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/173990042): I and J should be merged.
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -50,7 +54,6 @@
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
- assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
index 96b8ea9..2d264db 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
+import static org.junit.Assert.assertFalse;
+
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -32,11 +34,18 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/173990042): I and J should be merged.
.addHorizontallyMergedClassesInspector(
- inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
+ .noClassInliningOfSynthetics()
+ .noHorizontalClassMergingOfSynthetics()
+ .noInliningOfSynthetics()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
index cd691d1..23ed1ea 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
+import static org.junit.Assert.assertFalse;
+
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -32,11 +34,17 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // TODO(b/173990042): I and J should be merged.
.addHorizontallyMergedClassesInspector(
- inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
+ .noClassInliningOfSynthetics()
+ .noInliningOfSynthetics()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
index 6a1b373..6ad6ef1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -17,7 +18,6 @@
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.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -43,7 +43,18 @@
.addKeepMainRule(Main.class)
// I and J are not eligible for merging, since they declare the same default method.
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ inspector.assertNoClassesMerged();
+ } else {
+ inspector.assertIsCompleteMergeGroup(I.class, J.class);
+ }
+ })
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
+ options.horizontalClassMergerOptions().enableInterfaceMerging();
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -59,7 +70,13 @@
ClassSubject bClassSubject = inspector.clazz(B.class);
assertThat(bClassSubject, isPresent());
- assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
+ assertThat(
+ bClassSubject,
+ isImplementing(
+ inspector.clazz(
+ parameters.canUseDefaultAndStaticInterfaceMethods()
+ ? J.class
+ : I.class)));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("I", "J");
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 53f5ae2..1c33206 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
@@ -5,6 +5,7 @@
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.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -12,9 +13,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
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;
@@ -37,32 +37,41 @@
@Test
public void test() throws Exception {
String expectedOutput = StringUtils.lines("In I.a()", "In J.b()", "In A.c()");
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ // TODO(b/173990042): Extend horizontal class merging to interfaces.
+ .addHorizontallyMergedClassesInspector(
+ inspector -> {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ inspector.assertNoClassesMerged();
+ } else {
+ inspector
+ .assertClassReferencesMerged(
+ SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+ SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
+ .assertClassesNotMerged(I.class, J.class);
+ }
+ })
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspect(
+ inspector -> {
+ // We do not allow horizontal class merging of interfaces and classes. Therefore, A
+ // should remain in the output.
+ assertThat(inspector.clazz(A.class), isPresent());
- CodeInspector inspector =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addKeepMainRule(TestClass.class)
- // TODO(b/173990042): Extend horizontal class merging to interfaces.
- .addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
- .enableInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(expectedOutput)
- .inspector();
-
- // 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(syntheticCompanionClass(I.class)), isPresent());
- assertThat(inspector.clazz(syntheticCompanionClass(I.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(syntheticCompanionClass(I.class)), isPresent());
+ assertThat(inspector.clazz(syntheticCompanionClass(J.class)), isAbsent());
+ }
+ });
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java
new file mode 100644
index 0000000..be8211b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.vertical;
+
+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;
+
+@RunWith(Parameterized.class)
+public class MergeSynthesizingContextIntoSyntheticLambdaTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public MergeSynthesizingContextIntoSyntheticLambdaTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // Disable inlining to ensure that the synthetic lambdas remain in the residual
+ // program.
+ .addOptionsModification(options -> options.enableInlining = false)
+ .addVerticallyMergedClassesInspector(
+ inspector -> {
+ if (parameters.isCfRuntime()) {
+ inspector.assertNoClassesMerged();
+ } else {
+ inspector.assertMergedIntoSubtype(I.class);
+ }
+ })
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I", "J");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ I i = () -> System.out.println("I");
+ i.f();
+ i.g().h();
+ }
+ }
+
+ interface I {
+
+ void f();
+
+ default J g() {
+ // Has synthesizing context I. After vertical class merging, this synthesizing context is
+ // rewritten into the synthetic lambda defined in main().
+ return () -> System.out.println("J");
+ }
+ }
+
+ interface J {
+
+ void h();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index 319327d..965b610 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -3,23 +3,17 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debug;
+import static org.junit.Assume.assumeTrue;
-import static com.android.tools.r8.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_EMPTY_MAP;
-
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
-import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.naming.ClassNameMapper.MissingFileAction;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,85 +24,61 @@
@RunWith(Parameterized.class)
public class DebugInfoWhenInliningTest extends DebugTestBase {
- public enum Config {
- CF,
- DEX_NO_FORCE_JUMBO,
- DEX_FORCE_JUMBO
- };
-
private static final String CLASS_NAME = "Inlining1";
private static final String SOURCE_FILE = "Inlining1.java";
private DebugTestConfig makeConfig(
- LineNumberOptimization lineNumberOptimization,
- boolean writeProguardMap,
- RuntimeKind runtimeKind)
- throws Exception {
- DebugTestConfig config = null;
- Path outdir = temp.newFolder().toPath();
- Path outjar = outdir.resolve("r8_compiled.jar");
- R8Command.Builder builder =
- R8Command.builder()
+ LineNumberOptimization lineNumberOptimization, boolean writeProguardMap) throws Exception {
+ R8TestCompileResult result =
+ testForR8(parameters.getBackend())
.addProgramFiles(DEBUGGEE_JAR)
- .setMode(CompilationMode.RELEASE)
- .addProguardConfiguration(
- ImmutableList.of(
- "-keep class "
- + CLASS_NAME
- + " { public static void main(java.lang.String[]); }"),
- Origin.unknown())
- .setDisableMinification(true)
- .addProguardConfiguration(
- ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
-
- if (runtimeKind == RuntimeKind.DEX) {
- AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
- builder
- .setMinApiLevel(minSdk.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
- .setOutput(outjar, OutputMode.DexIndexed);
- config = new DexDebugTestConfig(outjar);
- } else {
- assert (runtimeKind == RuntimeKind.CF);
- builder
- .setOutput(outjar, OutputMode.ClassFile)
- .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
- config = new CfDebugTestConfig(outjar);
- }
-
+ .addKeepMainRule(CLASS_NAME)
+ .noMinification()
+ .addKeepAttributeSourceFile()
+ .addKeepAttributeLineNumberTable()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> {
+ options.lineNumberOptimization = lineNumberOptimization;
+ options.testing.forceJumboStringProcessing = forceJumboStringProcessing;
+ // TODO(b/117848700): Can we make these tests neutral to inlining threshold?
+ // Also CF needs improvements here.
+ options.inliningInstructionLimit = parameters.isCfRuntime() ? 5 : 4;
+ })
+ .compile();
+ DebugTestConfig config = result.debugConfig();
if (writeProguardMap) {
- Path proguardMapPath = outdir.resolve("proguard.map");
- builder.setProguardMapOutputPath(proguardMapPath);
- config.setProguardMap(proguardMapPath, MISSING_FILE_IS_EMPTY_MAP);
+ config.setProguardMap(result.writeProguardMap(), MissingFileAction.MISSING_FILE_IS_EMPTY_MAP);
}
-
- ToolHelper.runR8(
- builder.build(), options -> {
- options.lineNumberOptimization = lineNumberOptimization;
- options.testing.forceJumboStringProcessing = forceJumboStringProcessing;
- // TODO(b/117848700): Can we make these tests neutral to inlining threshold?
- // Also CF needs improvements here.
- options.inliningInstructionLimit = runtimeKind == RuntimeKind.CF ? 5 : 4;
- });
-
return config;
}
- private boolean forceJumboStringProcessing;
- private RuntimeKind runtimeKind;
+ private final TestParameters parameters;
+ private final boolean forceJumboStringProcessing;
- @Parameters(name = "config: {0}")
- public static Collection<Config> data() {
- return Arrays.asList(Config.values());
+ @Parameters(name = "{0}, force-jumbo: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build(),
+ BooleanUtils.values());
}
- public DebugInfoWhenInliningTest(Config config) {
- this.forceJumboStringProcessing = config == Config.DEX_FORCE_JUMBO;
- this.runtimeKind = config == Config.CF ? RuntimeKind.CF : RuntimeKind.DEX;
+ public DebugInfoWhenInliningTest(TestParameters parameters, boolean forceJumboString) {
+ assumeTrue(!parameters.isCfRuntime() || !forceJumboString);
+ this.parameters = parameters;
+ this.forceJumboStringProcessing = forceJumboString;
+ }
+
+ private void assumeMappingIsNotToPCs() {
+ assumeTrue(
+ "Ignoring test when the line number table is removed.",
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport()));
}
@Test
public void testEachLineNotOptimized() throws Throwable {
+ assumeMappingIsNotToPCs();
// The reason why the not-optimized test contains half as many line numbers as the optimized
// one:
//
@@ -116,11 +86,12 @@
// (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
// are emitted duplicated in the dex code, the debugger stops only when there's a change.
int[] lineNumbers = {7, 32, 11, 7};
- testEachLine(makeConfig(LineNumberOptimization.OFF, false, runtimeKind), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.OFF, false), lineNumbers);
}
@Test
public void testEachLineNotOptimizedWithMap() throws Throwable {
+ assumeMappingIsNotToPCs();
// The reason why the not-optimized test contains half as many line numbers as the optimized
// one:
//
@@ -128,17 +99,19 @@
// (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
// are emitted duplicated in the dex code, the debugger stops only when there's a change.
int[] lineNumbers = {7, 32, 11, 7};
- testEachLine(makeConfig(LineNumberOptimization.OFF, true, runtimeKind), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.OFF, true), lineNumbers);
}
@Test
public void testEachLineOptimized() throws Throwable {
+ assumeMappingIsNotToPCs();
int[] lineNumbers = {1, 2, 3, 4, 5, 6, 7, 8};
- testEachLine(makeConfig(LineNumberOptimization.ON, false, runtimeKind), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.ON, false), lineNumbers);
}
@Test
public void testEachLineOptimizedWithMap() throws Throwable {
+ assumeMappingIsNotToPCs();
int[] lineNumbers = {7, 7, 32, 32, 11, 11, 7, 7};
List<List<SignatureAndLine>> inlineFramesList =
ImmutableList.of(
@@ -170,8 +143,7 @@
new SignatureAndLine("void Inlining3.differentFileMultilevelInliningLevel2()", 7),
new SignatureAndLine("void Inlining2.differentFileMultilevelInliningLevel1()", 36),
new SignatureAndLine("void main(java.lang.String[])", 26)));
- testEachLine(
- makeConfig(LineNumberOptimization.ON, true, runtimeKind), lineNumbers, inlineFramesList);
+ testEachLine(makeConfig(LineNumberOptimization.ON, true), lineNumbers, inlineFramesList);
}
private void testEachLine(DebugTestConfig config, int[] lineNumbers) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index c34c633..d92d4ec 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -652,10 +652,23 @@
String getObfuscatedMethodName(
String originalClassName, String originalMethodName, String methodSignature);
+
+ boolean canUsePcForMissingLineNumberTable();
}
private class IdentityTranslator implements Translator {
+ private final boolean usePcForMissingLineTable;
+
+ public IdentityTranslator(boolean usePcForMissingLineNumberTable) {
+ this.usePcForMissingLineTable = usePcForMissingLineNumberTable;
+ }
+
+ @Override
+ public boolean canUsePcForMissingLineNumberTable() {
+ return usePcForMissingLineTable;
+ }
+
@Override
public String getOriginalClassName(String obfuscatedClassName) {
return obfuscatedClassName;
@@ -709,7 +722,9 @@
private class ClassNameMapperTranslator extends IdentityTranslator {
private final ClassNameMapper classNameMapper;
- public ClassNameMapperTranslator(ClassNameMapper classNameMapper) {
+ public ClassNameMapperTranslator(
+ ClassNameMapper classNameMapper, boolean usePcForMissingLineTable) {
+ super(usePcForMissingLineTable);
this.classNameMapper = classNameMapper;
}
@@ -872,9 +887,11 @@
this.debuggeeClassName = debuggeeClassName;
this.commandsQueue = new ArrayDeque<>(commands);
if (classNameMapper == null) {
- this.translator = new IdentityTranslator();
+ this.translator = new IdentityTranslator(config.shouldUsePcForMissingLineNumberTable());
} else {
- this.translator = new ClassNameMapperTranslator(classNameMapper);
+ this.translator =
+ new ClassNameMapperTranslator(
+ classNameMapper, config.shouldUsePcForMissingLineNumberTable());
}
}
@@ -1161,6 +1178,9 @@
long startCodeIndex = reply.getNextValueAsLong();
long endCodeIndex = reply.getNextValueAsLong();
int lines = reply.getNextValueAsInt();
+ if (lines == 0 && translator.canUsePcForMissingLineNumberTable()) {
+ return (int) location.index;
+ }
int line = -1;
long previousLineCodeIndex = -1;
for (int i = 0; i < lines; ++i) {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index 1dc24d8..4ab22aa 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -25,6 +25,7 @@
private Path proguardMap = null;
private ClassNameMapper.MissingFileAction missingProguardMapAction;
+ private boolean usePcForMissingLineNumberTable = false;
/** The expected runtime kind for the debuggee. */
public abstract RuntimeKind getRuntimeKind();
@@ -37,6 +38,14 @@
return getRuntimeKind() == RuntimeKind.DEX;
}
+ public void allowUsingPcForMissingLineNumberTable() {
+ usePcForMissingLineNumberTable = true;
+ }
+
+ public boolean shouldUsePcForMissingLineNumberTable() {
+ return usePcForMissingLineNumberTable;
+ }
+
/** Classpath paths for the debuggee. */
public List<Path> getPaths() {
return paths;
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index 35f24c9..d307de3 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -4,18 +4,14 @@
package com.android.tools.r8.debug;
import static com.android.tools.r8.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_EMPTY_MAP;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -29,114 +25,96 @@
20, 7, 8, 28, 29, 9, 21, 12, 13, 22, 16, 17
};
private static final int[] OPTIMIZED_LINE_NUMBERS = {1, 1, 2, 1, 2, 1, 2, 3, 2, 3, 4, 3};
+
private static final String CLASS1 = "LineNumberOptimization1";
private static final String CLASS2 = "LineNumberOptimization2";
private static final String FILE1 = CLASS1 + ".java";
private static final String FILE2 = CLASS2 + ".java";
private static final String MAIN_SIGNATURE = "([Ljava/lang/String;)V";
- private RuntimeKind runtimeKind;
+ private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
- public static Collection<Object[]> setup() {
- return ImmutableList.of(
- new Object[] {"CF", RuntimeKind.CF}, new Object[] {"DEX", RuntimeKind.DEX});
+ public static TestParametersCollection setup() {
+ return TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build();
}
- public LineNumberOptimizationTest(String name, RuntimeKind runtimeKind) {
- this.runtimeKind = runtimeKind;
+ public LineNumberOptimizationTest(TestParameters parameters) {
+ this.parameters = parameters;
}
- private static DebugTestConfig makeConfig(
+ private DebugTestConfig makeConfig(
LineNumberOptimization lineNumberOptimization,
boolean writeProguardMap,
- boolean dontOptimizeByEnablingDebug,
- RuntimeKind runtimeKind)
+ boolean dontOptimizeByEnablingDebug)
throws Exception {
- Path outdir = temp.newFolder().toPath();
- Path outjar = outdir.resolve("r8_compiled.jar");
- R8Command.Builder builder =
- R8Command.builder()
+ R8TestCompileResult result =
+ testForR8(parameters.getBackend())
.addProgramFiles(DEBUGGEE_JAR)
+ .setMinApi(parameters.getApiLevel())
.setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .addProguardConfiguration(
- ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
+ .noTreeShaking()
+ .noMinification()
+ .addKeepAttributeSourceFile()
+ .addKeepAttributeLineNumberTable()
+ .addOptionsModification(
+ options -> {
+ if (!dontOptimizeByEnablingDebug) {
+ options.lineNumberOptimization = lineNumberOptimization;
+ }
+ options.enableInlining = false;
+ })
+ .compile();
- DebugTestConfig config = null;
-
- if (runtimeKind == RuntimeKind.CF) {
- builder
- .setOutput(outjar, OutputMode.ClassFile)
- .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
- config = new CfDebugTestConfig(outjar);
- } else {
- assert (runtimeKind == RuntimeKind.DEX);
- AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
- builder
- .setMinApiLevel(minSdk.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
- .setOutput(outjar, OutputMode.DexIndexed);
- config = new D8DebugTestConfig();
- }
-
- config.addPaths(outjar);
+ DebugTestConfig config = result.debugConfig();
if (writeProguardMap) {
- Path proguardMapPath = outdir.resolve("proguard.map");
- builder.setProguardMapOutputPath(proguardMapPath);
- config.setProguardMap(proguardMapPath, MISSING_FILE_IS_EMPTY_MAP);
+ config.setProguardMap(result.writeProguardMap(), MISSING_FILE_IS_EMPTY_MAP);
}
-
- ToolHelper.runR8(
- builder.build(),
- options -> {
- if (!dontOptimizeByEnablingDebug) {
- options.lineNumberOptimization = lineNumberOptimization;
- }
- options.enableInlining = false;
- });
-
return config;
}
+ private void assumeMappingIsNotToPCs() {
+ assumeTrue(
+ "Ignoring test when the line number table is removed.",
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport()));
+ }
+
@Test
public void testNotOptimized() throws Throwable {
- testRelease(
- makeConfig(LineNumberOptimization.OFF, false, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
+ assumeMappingIsNotToPCs();
+ testRelease(makeConfig(LineNumberOptimization.OFF, false, false), ORIGINAL_LINE_NUMBERS);
}
@Test
public void testNotOptimizedWithMap() throws Throwable {
- testRelease(
- makeConfig(LineNumberOptimization.OFF, true, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
+ assumeMappingIsNotToPCs();
+ testRelease(makeConfig(LineNumberOptimization.OFF, true, false), ORIGINAL_LINE_NUMBERS);
}
@Test
public void testNotOptimizedByEnablingDebug() throws Throwable {
- testDebug(
- makeConfig(LineNumberOptimization.OFF, false, true, runtimeKind),
- ORIGINAL_LINE_NUMBERS_DEBUG);
+ testDebug(makeConfig(LineNumberOptimization.OFF, false, true), ORIGINAL_LINE_NUMBERS_DEBUG);
}
@Test
public void testNotOptimizedByEnablingDebugWithMap() throws Throwable {
- testDebug(
- makeConfig(LineNumberOptimization.OFF, true, true, runtimeKind),
- ORIGINAL_LINE_NUMBERS_DEBUG);
+ testDebug(makeConfig(LineNumberOptimization.OFF, true, true), ORIGINAL_LINE_NUMBERS_DEBUG);
}
@Test
public void testOptimized() throws Throwable {
- testRelease(
- makeConfig(LineNumberOptimization.ON, false, false, runtimeKind), OPTIMIZED_LINE_NUMBERS);
+ assumeMappingIsNotToPCs();
+ DebugTestConfig config = makeConfig(LineNumberOptimization.ON, false, false);
+ config.allowUsingPcForMissingLineNumberTable();
+ testRelease(config, OPTIMIZED_LINE_NUMBERS);
}
@Test
public void testOptimizedWithMap() throws Throwable {
- testRelease(
- makeConfig(LineNumberOptimization.ON, true, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
+ assumeMappingIsNotToPCs();
+ testRelease(makeConfig(LineNumberOptimization.ON, true, false), ORIGINAL_LINE_NUMBERS);
}
private void testDebug(DebugTestConfig config, int[] lineNumbers) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/debug/R8DebugNonMinifiedProgramTestRunner.java b/src/test/java/com/android/tools/r8/debug/R8DebugNonMinifiedProgramTestRunner.java
index 3f61638..9771a28 100644
--- a/src/test/java/com/android/tools/r8/debug/R8DebugNonMinifiedProgramTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/R8DebugNonMinifiedProgramTestRunner.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestCompileResult;
@@ -69,6 +70,13 @@
});
}
+ private void assumeMappingIsNotToPCs() {
+ assumeTrue(
+ "Ignoring test when the line number table is removed.",
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport()));
+ }
+
@Test
public void testDebugMode() throws Throwable {
runTest(compiledDebug.apply(parameters.getBackend(), parameters.getApiLevel()));
@@ -76,6 +84,7 @@
@Test
public void testNoOptimizationAndNoMinification() throws Throwable {
+ assumeMappingIsNotToPCs();
runTest(compiledNoOptNoMinify.apply(parameters.getBackend(), parameters.getApiLevel()));
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
index 395f29f..73aff7f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
@@ -8,17 +8,35 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.DexParser;
import com.android.tools.r8.dex.DexSection;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
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 CanonicalizeWithInline extends TestBase {
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private final TestParameters parameters;
+
+ public CanonicalizeWithInline(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
private int getNumberOfDebugInfos(Path file) throws IOException {
DexSection[] dexSections = DexParser.parseMapFrom(file);
for (DexSection dexSection : dexSections) {
@@ -36,6 +54,7 @@
R8TestCompileResult result =
testForR8(Backend.DEX)
+ .setMinApi(AndroidApiLevel.B)
.addProgramClasses(clazzA, clazzB)
.addKeepRules(
"-keepattributes SourceFile,LineNumberTable",
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
index 18913cc..d6e30b4 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -70,11 +70,6 @@
.addKeepAttributeLineNumberTable()
.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(
- internalOptions -> {
- // TODO(b/37830524): Remove when activated.
- internalOptions.enablePcDebugInfoOutput = true;
- })
.run(parameters.getRuntime(), MAIN)
.assertFailureWithErrorThatMatches(containsString(EXPECTED))
.inspectOriginalStackTrace(
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
index 270e7cd..c2f1811 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.naming.retrace.StackTrace;
import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -53,11 +52,6 @@
return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
}
- // TODO(b/37830524): Remove when activated.
- private void enablePcDebugInfoOutput(InternalOptions options) {
- options.enablePcDebugInfoOutput = true;
- }
-
@Test
public void testD8Debug() throws Exception {
testForD8(parameters.getBackend())
@@ -65,7 +59,6 @@
.addProgramClasses(MAIN)
.setMinApi(parameters.getApiLevel())
.internalEnableMappingOutput()
- .addOptionsModification(this::enablePcDebugInfoOutput)
.run(parameters.getRuntime(), MAIN)
// For a debug build we always expect the output to have actual line information.
.inspectFailure(this::checkHasLineNumberInfo)
@@ -79,7 +72,6 @@
.addProgramClasses(MAIN)
.setMinApi(parameters.getApiLevel())
.internalEnableMappingOutput()
- .addOptionsModification(this::enablePcDebugInfoOutput)
.run(parameters.getRuntime(), MAIN)
.inspectFailure(
inspector -> {
@@ -98,7 +90,6 @@
.release()
.addProgramClasses(MAIN)
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(this::enablePcDebugInfoOutput)
.run(parameters.getRuntime(), MAIN)
// If compiling without a map output actual debug info should also be retained. Otherwise
// there would not be any way to obtain the actual lines.
@@ -114,7 +105,6 @@
.addKeepMainRule(MAIN)
.addKeepAttributeLineNumberTable()
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(this::enablePcDebugInfoOutput)
.run(parameters.getRuntime(), MAIN)
.inspectOriginalStackTrace(
(stackTrace, inspector) -> {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index 3e60ef1..9efecb4 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -6,25 +6,22 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.debuginfo.InliningWithoutPositionsTestSourceDump.Location;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
import com.android.tools.r8.naming.Range;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -39,12 +36,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class InliningWithoutPositionsTestRunner {
-
- enum Backend {
- CF,
- DEX
- }
+public class InliningWithoutPositionsTestRunner extends TestBase {
private static final String TEST_CLASS = "InliningWithoutPositionsTestSource";
private static final String TEST_PACKAGE = "com.android.tools.r8.debuginfo";
@@ -52,7 +44,7 @@
@ClassRule public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
- private final Backend backend;
+ private final TestParameters parameters;
private final boolean mainPos;
private final boolean foo1Pos;
private final boolean barPos;
@@ -62,13 +54,14 @@
@Parameters(name = "{0}: main/foo1/bar/foo2 positions: {1}/{2}/{3}/{4}, throwLocation: {5}")
public static Collection<Object[]> data() {
List<Object[]> testCases = new ArrayList<>();
- for (Backend backend : Backend.values()) {
+ for (TestParameters parameters :
+ TestParametersBuilder.builder().withAllRuntimes().withApiLevel(AndroidApiLevel.B).build()) {
for (int i = 0; i < 16; ++i) {
for (Location throwLocation : Location.values()) {
if (throwLocation != Location.MAIN) {
testCases.add(
new Object[] {
- backend, (i & 1) != 0, (i & 2) != 0, (i & 4) != 0, (i & 8) != 0, throwLocation
+ parameters, (i & 1) != 0, (i & 2) != 0, (i & 4) != 0, (i & 8) != 0, throwLocation
});
}
}
@@ -78,13 +71,13 @@
}
public InliningWithoutPositionsTestRunner(
- Backend backend,
+ TestParameters parameters,
boolean mainPos,
boolean foo1Pos,
boolean barPos,
boolean foo2Pos,
Location throwLocation) {
- this.backend = backend;
+ this.parameters = parameters;
this.mainPos = mainPos;
this.foo1Pos = foo1Pos;
this.barPos = barPos;
@@ -97,54 +90,23 @@
// See InliningWithoutPositionsTestSourceDump for the code compiled here.
Path testClassDir = temp.newFolder().toPath();
Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
- Path outputPath = temp.newFolder().toPath();
-
Files.write(
testClassPath,
InliningWithoutPositionsTestSourceDump.dump(
mainPos, foo1Pos, barPos, foo2Pos, throwLocation));
- Path proguardMapPath = testClassDir.resolve("proguard.map");
-
- R8Command.Builder builder =
- R8Command.builder()
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
.addProgramFiles(testClassPath)
.setMode(CompilationMode.RELEASE)
- .setProguardMapOutputPath(proguardMapPath);
- if (backend == Backend.DEX) {
- AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
- builder
- .setMinApiLevel(minSdk.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
- .setOutput(outputPath, OutputMode.DexIndexed);
- } else {
- assert (backend == Backend.CF);
- builder
- .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
- .setOutput(outputPath, OutputMode.ClassFile);
- }
- builder
- .addProguardConfiguration(
- ImmutableList.of(
- "-keep class " + MAIN_CLASS + " { public static void main(java.lang.String[]); }"),
- Origin.unknown())
- .setDisableMinification(true)
- .addProguardConfiguration(
- ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
-
- ToolHelper.runR8(builder.build(), options -> options.inliningInstructionLimit = 40);
-
- ProcessResult result;
- if (backend == Backend.DEX) {
- ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
- artCommandBuilder.appendClasspath(outputPath.resolve("classes.dex").toString());
- artCommandBuilder.setMainClass(MAIN_CLASS);
-
- result = ToolHelper.runArtRaw(artCommandBuilder);
- } else {
- result = ToolHelper.runJava(outputPath, MAIN_CLASS);
- }
- assertNotEquals(result.exitCode, 0);
+ .addKeepMainRule(MAIN_CLASS)
+ .noMinification()
+ .addKeepAttributeSourceFile()
+ .addKeepAttributeLineNumberTable()
+ .addOptionsModification(options -> options.inliningInstructionLimit = 40)
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailure();
// Verify stack trace.
// result.stderr looks like this:
@@ -152,7 +114,7 @@
// Exception in thread "main" java.lang.RuntimeException: <FOO1-exception>
// at
// com.android.tools.r8.debuginfo.InliningWithoutPositionsTestSource.main(InliningWithoutPositionsTestSource.java:1)
- String[] lines = result.stderr.split("\n");
+ String[] lines = result.getStdErr().split("\n");
// The line containing 'java.lang.RuntimeException' should contain the expected message, which
// is "LOCATIONCODE-exception>"
@@ -189,7 +151,7 @@
// 1:1:void bar():0:0 -> main
// 1:1:void foo(boolean):0 -> main
// 1:1:void main(java.lang.String[]):0 -> main
- ClassNameMapper mapper = ClassNameMapper.mapperFromFile(proguardMapPath);
+ ClassNameMapper mapper = ClassNameMapper.mapperFromString(result.proguardMap());
assertNotNull(mapper);
ClassNamingForNameMapper classNaming = mapper.getClassNaming(TEST_PACKAGE + "." + TEST_CLASS);
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
index 993b56f..2d396f7d 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
@@ -73,6 +73,7 @@
.setMinApi(parameters.getApiLevel())
.addKeepAllClassesRule()
.addKeepAttributeInnerClassesAndEnclosingMethod()
+ .noHorizontalClassMergingOfSynthetics()
.compile()
.run(parameters.getRuntime(), TestClass.class)
.applyIf(
@@ -125,7 +126,7 @@
static Callable<Class<?>> staticOuter() {
return new Callable<Class<?>>() {
@Override
- public Class<?> call() throws Exception {
+ public Class<?> call() {
return getClass().getEnclosingClass();
}
};
@@ -134,7 +135,7 @@
default Callable<Class<?>> defaultOuter() {
return new Callable<Class<?>>() {
@Override
- public Class<?> call() throws Exception {
+ public Class<?> call() {
return getClass().getEnclosingClass();
}
};
@@ -145,7 +146,7 @@
static Callable<Class<?>> staticOuter() {
class Local implements Callable<Class<?>> {
@Override
- public Class<?> call() throws Exception {
+ public Class<?> call() {
return getClass().getEnclosingClass();
}
}
@@ -155,7 +156,7 @@
default Callable<Class<?>> defaultOuter() {
class Local implements Callable<Class<?>> {
@Override
- public Class<?> call() throws Exception {
+ public Class<?> call() {
return getClass().getEnclosingClass();
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index acfc021..00d6d45 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -91,9 +91,6 @@
@Test
public void testD8Merging() throws Exception {
- assumeTrue(
- "b/147485959: Merging does not happen for CF due to lack of synthetic annotations",
- parameters.isDexRuntime());
boolean intermediate = true;
runD8Merging(intermediate);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
index 3a923e0..640796d 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
@@ -3,11 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.lambdas;
+import static org.junit.Assert.assertTrue;
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.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.StringUtils;
import java.util.ArrayList;
import java.util.List;
@@ -35,6 +37,17 @@
StringUtils.lines(
"getStacktraceWithFileNames(" + fileName + ")",
"lambda$main$0(" + fileName + ")",
+ "call(D8$$SyntheticClass)",
+ "main(" + fileName + ")",
+ "getStacktraceWithFileNames(" + fileName + ")",
+ "lambda$main$1(" + fileName + ")",
+ "call(D8$$SyntheticClass)",
+ "main(" + fileName + ")");
+
+ static final String EXPECTED_D8_ANDROID_O =
+ StringUtils.lines(
+ "getStacktraceWithFileNames(" + fileName + ")",
+ "lambda$main$0(" + fileName + ")",
"call(NULL)",
"main(" + fileName + ")",
"getStacktraceWithFileNames(" + fileName + ")",
@@ -43,6 +56,7 @@
"main(" + fileName + ")");
private final TestParameters parameters;
+ private final boolean isAndroidOOrLater;
private final boolean isDalvik;
@Parameterized.Parameters(name = "{0}")
@@ -53,6 +67,9 @@
public LambdaInStacktraceTest(TestParameters parameters) {
this.parameters = parameters;
isDalvik = parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik();
+ isAndroidOOrLater =
+ parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0);
}
@Test
@@ -71,7 +88,26 @@
.addInnerClasses(LambdaInStacktraceTest.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestRunner.class, Boolean.toString(isDalvik))
- .assertSuccessWithOutput(EXPECTED_D8);
+ .assertSuccessWithOutput(isAndroidOOrLater ? EXPECTED_D8_ANDROID_O : EXPECTED_D8);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.getRuntime().isDex());
+ String stdout =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LambdaInStacktraceTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestRunner.class)
+ .addKeepAttributeSourceFile()
+ .addKeepRules("-renamesourcefileattribute SourceFile")
+ .noTreeShaking()
+ .run(parameters.getRuntime(), TestRunner.class, Boolean.toString(isDalvik))
+ .assertSuccess()
+ .getStdOut();
+ assertTrue(
+ StringUtils.splitLines(stdout).stream()
+ .allMatch(s -> s.contains(isAndroidOOrLater ? "NULL" : "SourceFile")));
}
static class TestRunner {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
index 3d063c5..a4c0ca7 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
@@ -4,17 +4,32 @@
package com.android.tools.r8.desugar.nestaccesscontrol;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
@RunWith(Parameterized.class)
public class Java11D8CompilationTest extends TestBase {
@@ -41,4 +56,83 @@
.compile()
.inspect(Java11D8CompilationTest::assertNoNests);
}
+
+ @Test
+ public void testR8CompiledWithD8ToCf() throws Exception {
+ Path r8Desugared =
+ testForD8(Backend.CF)
+ .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .inspect(Java11D8CompilationTest::assertNoNests)
+ .writeToZip();
+
+ // Check that the desugared classes has the expected class file versions and that no nest
+ // related attributes remains.
+ ZipUtils.iter(
+ r8Desugared,
+ (entry, input) -> {
+ if (SyntheticItemsTestUtils.isExternalStaticInterfaceCall(
+ Reference.classFromBinaryName(
+ entry
+ .getName()
+ .substring(0, entry.getName().length() - CLASS_EXTENSION.length())))) {
+ assertEquals(CfVersion.V1_8, extractClassFileVersionAndAssertNoNestAttributes(input));
+ } else {
+ assertTrue(
+ extractClassFileVersionAndAssertNoNestAttributes(input)
+ .isLessThanOrEqualTo(CfVersion.V1_7));
+ }
+ });
+ }
+
+ protected static CfVersion extractClassFileVersionAndAssertNoNestAttributes(InputStream classFile)
+ throws IOException {
+ class ClassFileVersionExtractor extends ClassVisitor {
+ private int version;
+
+ private ClassFileVersionExtractor() {
+ super(ASM_VERSION);
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ this.version = version;
+ }
+
+ @Override
+ public void visitAttribute(Attribute attribute) {}
+
+ CfVersion getClassFileVersion() {
+ return CfVersion.fromRaw(version);
+ }
+
+ @Override
+ public void visitNestHost(String nestHost) {
+ // ASM will always report the NestHost attribute if present (independently of class
+ // file version).
+ fail();
+ }
+
+ @Override
+ public void visitNestMember(String nestMember) {
+ // ASM will always report the NestHost attribute if present (independently of class
+ // file version).
+ fail();
+ }
+ }
+
+ ClassReader reader = new ClassReader(classFile);
+ ClassFileVersionExtractor extractor = new ClassFileVersionExtractor();
+ reader.accept(
+ extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ return extractor.getClassFileVersion();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
index 8bcb3a6..b54a7d5 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
@@ -6,14 +6,26 @@
import static com.android.tools.r8.desugar.staticinterfacemethod.InvokeStaticDesugarTest.Library.foo;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import com.android.tools.r8.DesugarTestConfiguration;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -23,19 +35,26 @@
public class InvokeStaticDesugarTest extends TestBase {
private final TestParameters parameters;
+ private final boolean intermediate;
private final String EXPECTED = "Hello World!";
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ @Parameters(name = "{0}, intermediate in first step: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values());
}
- public InvokeStaticDesugarTest(TestParameters parameters) {
+ public InvokeStaticDesugarTest(TestParameters parameters, boolean intermediate) {
this.parameters = parameters;
+ this.intermediate = intermediate;
}
@Test
public void testDesugar() throws Exception {
+ // Intermediate not used in this test.
+ assumeFalse(intermediate);
+
final TestRunResult<?> runResult =
testForDesugaring(parameters)
.addLibraryClasses(Library.class)
@@ -50,6 +69,57 @@
}
}
+ @Test
+ public void testDoubleDesugar() throws Exception {
+ // Desugar using API level that cannot leave static interface invokes.
+ Path jar =
+ testForD8(Backend.CF)
+ .addLibraryClasses(Library.class)
+ .addProgramClasses(Main.class)
+ .setMinApi(AndroidApiLevel.B)
+ .setIntermediate(intermediate)
+ .compile()
+ .inspect(i -> assertEquals(1, getSyntheticMethods(i).size()))
+ .writeToZip();
+
+ testForDesugaring(parameters)
+ .addLibraryClasses(Library.class)
+ .addProgramFiles(jar)
+ .addRunClasspathFiles(compileRunClassPath())
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ // When double desugaring to API level below L two synthetics are seen.
+ c ->
+ DesugarTestConfiguration.isDesugared(c)
+ && (parameters.isCfRuntime()
+ || parameters
+ .getRuntime()
+ .asDex()
+ .getVm()
+ .isNewerThan(DexVm.ART_4_4_4_HOST))
+ && parameters.getApiLevel().isLessThan(AndroidApiLevel.L),
+ r -> {
+ assertEquals(intermediate ? 1 : 2, countSynthetics(r));
+ r.assertSuccessWithOutputLines(EXPECTED);
+ },
+ // Don't inspect failing code, as inspection is only supported when run succeeds,
+ // and testForDesugaring does not have separate compile where the code can be
+ // inspected before running.
+ c ->
+ parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .getVm()
+ .isOlderThanOrEqual(DexVm.ART_4_4_4_HOST),
+ r -> r.assertFailureWithErrorThatMatches(containsString("java.lang.VerifyError")),
+ // When double desugaring to API level L and above one synthetics seen.
+ r -> {
+ assertEquals(1, countSynthetics(r));
+ r.assertSuccessWithOutputLines(EXPECTED);
+ });
+ }
+
private Path compileRunClassPath() throws Exception {
if (parameters.isCfRuntime()) {
return compileToZip(parameters, ImmutableList.of(), Library.class);
@@ -68,6 +138,34 @@
}
}
+ private int countSynthetics(TestRunResult<?> r) {
+ IntBox box = new IntBox();
+ try {
+ r.inspect(inspector -> box.set(getSyntheticMethods(inspector).size()));
+ } catch (Exception e) {
+ box.set(-1);
+ fail();
+ }
+ return box.get();
+ }
+
+ private Set<MethodReference> getSyntheticMethods(CodeInspector inspector) {
+ Set<MethodReference> methods = new HashSet<>();
+ assert inspector.allClasses().stream()
+ .allMatch(
+ c ->
+ !SyntheticItemsTestUtils.isExternalSynthetic(c.getFinalReference())
+ || SyntheticItemsTestUtils.isExternalStaticInterfaceCall(
+ c.getFinalReference()));
+ inspector.allClasses().stream()
+ .filter(c -> SyntheticItemsTestUtils.isExternalStaticInterfaceCall(c.getFinalReference()))
+ .forEach(
+ c ->
+ c.allMethods(m -> !m.isInstanceInitializer())
+ .forEach(m -> methods.add(m.asMethodReference())));
+ return methods;
+ }
+
public interface Library {
static void foo() {
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
index aac4a9e..1f15b61 100644
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -102,9 +103,8 @@
// R8 will optimize the generated methods for the two cases below where the thrown
// exception is known or not, thus the synthetic methods will be 2.
int expectedSynthetics =
- parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
- ? 2
- : 0;
+ BooleanUtils.intValue(
+ parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()));
List<FoundClassSubject> foundClassSubjects = inspector.allClasses();
assertEquals(INPUT_CLASSES + expectedSynthetics, foundClassSubjects.size());
});
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
index 12a819d..75306f7 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GenericSignature;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GenericSignaturePrinter;
import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
import com.android.tools.r8.naming.NamingLens;
@@ -162,30 +163,29 @@
public void testPruningInterfaceBound() {
DexItemFactory factory = new DexItemFactory();
DexType context = factory.createType("Lj$/util/stream/Node$OfPrimitive;");
- String className = "j$.util.stream.Node$OfPrimitive";
+ String methodName = "j$.util.stream.Node$OfPrimitive.foo()";
TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
- ClassSignature parsedClassSignature =
- GenericSignature.parseClassSignature(
- className,
- "<T_SPLITR::Lj$/util/Spliterator$OfPrimitive;T_NODE:Ljava/lang/Object;>"
- + "Ljava/lang/Object;",
+ MethodTypeSignature parsedMethodSignature =
+ GenericSignature.parseMethodSignature(
+ methodName,
+ "<T_SPLITR::Lj$/util/Spliterator$OfPrimitive;T_NODE:Ljava/lang/Object;>()V",
Origin.unknown(),
factory,
testDiagnosticMessages);
testDiagnosticMessages.assertNoMessages();
- assertTrue(parsedClassSignature.hasSignature());
+ assertTrue(parsedMethodSignature.hasSignature());
GenericSignatureTypeRewriter rewriter =
new GenericSignatureTypeRewriter(
factory,
dexType -> dexType.toDescriptorString().equals("Lj$/util/Spliterator$OfPrimitive;"),
Function.identity(),
- context);
- ClassSignature rewritten = rewriter.rewrite(parsedClassSignature);
+ null);
+ MethodTypeSignature rewritten = rewriter.rewrite(parsedMethodSignature);
assertNotNull(rewritten);
assertTrue(rewritten.hasSignature());
- ClassSignature reparsed =
- GenericSignature.parseClassSignature(
- className, rewritten.toString(), Origin.unknown(), factory, testDiagnosticMessages);
+ MethodTypeSignature reparsed =
+ GenericSignature.parseMethodSignature(
+ methodName, rewritten.toString(), Origin.unknown(), factory, testDiagnosticMessages);
assertTrue(reparsed.hasSignature());
testDiagnosticMessages.assertNoMessages();
assertEquals(rewritten.toString(), reparsed.toString());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureEnclosingTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureEnclosingTest.java
new file mode 100644
index 0000000..5eccad7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureEnclosingTest.java
@@ -0,0 +1,136 @@
+// 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.graph.genericsignature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.genericsignature.GenericSignatureEnclosingTest.Bar.Inner;
+import com.android.tools.r8.graph.genericsignature.GenericSignatureEnclosingTest.Bar.SubInner;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+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 GenericSignatureEnclosingTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean isCompat;
+ private final String objectDescriptor = "Ljava/lang/Object;";
+
+ @Parameters(name = "{0}, isCompat: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public GenericSignatureEnclosingTest(TestParameters parameters, boolean isCompat) {
+ this.parameters = parameters;
+ this.isCompat = isCompat;
+ }
+
+ @Test()
+ public void testR8() throws Exception {
+ (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+ .addInnerClasses(getClass())
+ .addKeepClassAndMembersRules(Foo.class, Bar.class, Bar.Inner.class, Bar.SubInner.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(Foo.class)
+ .addKeepAttributeSignature()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> {
+ options.horizontalClassMergerOptions().disable();
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "Bar::enclosingMethod", "Hello World", "Bar::enclosingMethod2", "Hello World")
+ .inspect(
+ inspector -> {
+ inspectEnclosingClass(inspector, "$1", descriptor(Main.class));
+ inspectEnclosingClass(inspector, "$2", objectDescriptor);
+
+ ClassSubject inner = inspector.clazz(Inner.class);
+ assertThat(inner, isPresent());
+ assertTrue(inner.getDexProgramClass().getInnerClasses().isEmpty());
+ assertEquals(
+ "<T:" + objectDescriptor + ">" + objectDescriptor,
+ inner.getFinalSignatureAttribute());
+
+ ClassSubject subInner = inspector.clazz(SubInner.class);
+ assertThat(subInner, isPresent());
+ assertTrue(subInner.getDexProgramClass().getInnerClasses().isEmpty());
+ assertEquals(
+ "L" + binaryName(Bar.Inner.class) + "<Ljava/lang/String;>;",
+ subInner.getFinalSignatureAttribute());
+ });
+ }
+
+ private void inspectEnclosingClass(CodeInspector inspector, String suffix, String secondArg) {
+ ClassSubject enclosing = inspector.clazz(Bar.class.getTypeName() + suffix);
+ assertThat(enclosing, isPresent());
+ assertNull(enclosing.getDexProgramClass().getEnclosingMethodAttribute());
+ assertEquals(
+ isCompat ? "L" + binaryName(Foo.class) + "<Ljava/lang/Object;" + secondArg + ">;" : null,
+ enclosing.getFinalSignatureAttribute());
+ }
+
+ public abstract static class Foo<T, R> {
+
+ R foo(T r) {
+ System.out.println("Hello World");
+ return null;
+ }
+ }
+
+ public static class Bar<S> {
+
+ public class Inner<T> {}
+
+ public class SubInner extends Bar<Integer>.Inner<String> {}
+
+ public static <T, R extends Main> Foo<T, R> enclosingMethod() {
+ return new Foo<T, R>() {
+ @Override
+ R foo(T r) {
+ System.out.println("Bar::enclosingMethod");
+ return super.foo(r);
+ }
+ };
+ }
+
+ public static <T, R> Foo<T, R> enclosingMethod2() {
+ return new Foo<T, R>() {
+ @Override
+ R foo(T r) {
+ System.out.println("Bar::enclosingMethod2");
+ return super.foo(r);
+ }
+ };
+ }
+
+ public static void run() {
+ enclosingMethod().foo(null);
+ enclosingMethod2().foo(null);
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Bar.run();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesKeepTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesKeepTest.java
new file mode 100644
index 0000000..0e09d6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesKeepTest.java
@@ -0,0 +1,73 @@
+// 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.graph.genericsignature;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.reflect.Type;
+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 GenericSignaturePrunedInterfacesKeepTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED =
+ new String[] {
+ "interface com.android.tools.r8.graph.genericsignature"
+ + ".GenericSignaturePrunedInterfacesKeepTest$J",
+ "com.android.tools.r8.graph.genericsignature"
+ + ".GenericSignaturePrunedInterfacesKeepTest$J<java.lang.Object>"
+ };
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public GenericSignaturePrunedInterfacesKeepTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepAttributeSignature()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public interface I {}
+
+ public interface J<T> {}
+
+ public static class A implements I {}
+
+ public static class B extends A implements I, J<Object> {
+
+ public static void foo() {
+ for (Type genericInterface : B.class.getInterfaces()) {
+ System.out.println(genericInterface);
+ }
+ for (Type genericInterface : B.class.getGenericInterfaces()) {
+ System.out.println(genericInterface);
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesObfuscationTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesObfuscationTest.java
new file mode 100644
index 0000000..7cf462c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesObfuscationTest.java
@@ -0,0 +1,72 @@
+// 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.graph.genericsignature;
+
+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 java.lang.reflect.Type;
+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 GenericSignaturePrunedInterfacesObfuscationTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"interface a.b", "a.b<java.lang.Object>"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public GenericSignaturePrunedInterfacesObfuscationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8Compat(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(I.class, A.class)
+ .addKeepClassRulesWithAllowObfuscation(J.class)
+ .addKeepAttributeSignature()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .enableInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public interface I {}
+
+ public interface J<T> {}
+
+ public static class A implements I {}
+
+ public static class B extends A implements I, J<Object> {
+
+ @NeverInline
+ public static void foo() {
+ for (Type genericInterface : B.class.getInterfaces()) {
+ System.out.println(genericInterface);
+ }
+ for (Type genericInterface : B.class.getGenericInterfaces()) {
+ System.out.println(genericInterface);
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesTest.java
new file mode 100644
index 0000000..85cee97
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedInterfacesTest.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.graph.genericsignature;
+
+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 java.lang.reflect.Type;
+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 GenericSignaturePrunedInterfacesTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED =
+ new String[] {
+ "interface com.android.tools.r8.graph.genericsignature"
+ + ".GenericSignaturePrunedInterfacesTest$J",
+ "com.android.tools.r8.graph.genericsignature"
+ + ".GenericSignaturePrunedInterfacesTest$J<java.lang.Object>"
+ };
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public GenericSignaturePrunedInterfacesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8Compat(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(I.class, J.class, A.class)
+ .addKeepAttributeSignature()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .enableInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public interface I {}
+
+ public interface J<T> {}
+
+ public static class A implements I {}
+
+ public static class B extends A implements I, J<Object> {
+
+ @NeverInline
+ public static void foo() {
+ for (Type genericInterface : B.class.getInterfaces()) {
+ System.out.println(genericInterface);
+ }
+ for (Type genericInterface : B.class.getGenericInterfaces()) {
+ System.out.println(genericInterface);
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureStaticMethodTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureStaticMethodTest.java
new file mode 100644
index 0000000..05c8aa5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureStaticMethodTest.java
@@ -0,0 +1,88 @@
+// 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.graph.genericsignature;
+
+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.graph.AccessFlags;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+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 GenericSignatureStaticMethodTest extends TestBase {
+
+ private final String[] EXPECTED =
+ new String[] {
+ "true",
+ "public class com.android.tools.r8.graph.genericsignature"
+ + ".GenericSignatureStaticMethodTest$Main<T>"
+ };
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public GenericSignatureStaticMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(
+ transformer(Main.class)
+ .removeInnerClasses()
+ .setAccessFlags(Main.class.getDeclaredMethod("test"), AccessFlags::setStatic)
+ .transform())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(Main.class)
+ .setAccessFlags(Main.class.getDeclaredMethod("test"), AccessFlags::setStatic)
+ .transform())
+ .addKeepAllClassesRule()
+ .addKeepAttributeSignature()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Main<T> {
+
+ public static void main(String[] args) throws Exception {
+ Method test = Main.class.getDeclaredMethod("test");
+ System.out.println(Modifier.isStatic(test.getModifiers()));
+ Type genericReturnType = test.getGenericReturnType();
+ if (genericReturnType instanceof TypeVariable) {
+ Class<?> genericDeclaration =
+ (Class<?>) ((TypeVariable<?>) genericReturnType).getGenericDeclaration();
+ System.out.println(genericDeclaration.toGenericString());
+ }
+ }
+
+ public /* static after rewriting */ T test() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreCompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/GMSCoreCompilationTestBase.java
index 4ed740b..93656d1 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreCompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreCompilationTestBase.java
@@ -6,5 +6,7 @@
public abstract class GMSCoreCompilationTestBase extends CompilationTestBase {
// Files pertaining to the full GMSCore build.
static final String PG_CONF = "GmsCore_prod_alldpi_release_all_locales_proguard.config";
+ static final String PG_MAP = "GmsCore_prod_alldpi_release_all_locales_proguard.map";
static final String DEPLOY_JAR = "GmsCore_prod_alldpi_release_all_locales_deploy.jar";
+ static final String RELEASE_APK_X86 = "x86_GmsCore_prod_alldpi_release.apk";
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10DisassemblerTest.java
similarity index 67%
rename from src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
rename to src/test/java/com/android/tools/r8/internal/GMSCoreV10DisassemblerTest.java
index 192b554..014f5d3 100644
--- a/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10DisassemblerTest.java
@@ -3,16 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
-import static com.android.tools.r8.ToolHelper.DEFAULT_PROGUARD_MAP_FILE;
-
import com.android.tools.r8.Disassemble;
-import com.android.tools.r8.utils.FileUtils;
import java.io.OutputStream;
import java.io.PrintStream;
-import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
-import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -21,14 +16,14 @@
// Invoke R8 on the dex files extracted from GMSCore.apk to disassemble the dex code.
@RunWith(Parameterized.class)
-public class R8DisassemblerTest {
+public class GMSCoreV10DisassemblerTest extends GMSCoreCompilationTestBase {
- static final String APP_DIR = "third_party/gmscore/v5/";
+ private static final String APP_DIR = "third_party/gmscore/gmscore_v10/";
@Parameters(name = "deobfuscate: {0} smali: {1}")
public static Iterable<Object[]> data() {
- return Arrays
- .asList(new Object[][]{{false, false}, {false, true}, {true, false}, {true, true}});
+ return Arrays.asList(
+ new Object[][] {{false, false}, {false, true}, {true, false}, {true, true}});
}
@Parameter(0)
@@ -42,20 +37,22 @@
// This test only ensures that we do not break disassembling of dex code. It does not
// check the generated code. To make it fast, we get rid of the output.
PrintStream originalOut = System.out;
- System.setOut(new PrintStream(new OutputStream() {
- public void write(int b) { /* ignore*/ }
- }));
+ System.setOut(
+ new PrintStream(
+ new OutputStream() {
+ @Override
+ public void write(int b) {
+ // Intentionally empty.
+ }
+ }));
try {
Disassemble.DisassembleCommand.Builder builder = Disassemble.DisassembleCommand.builder();
builder.setUseSmali(smali);
if (deobfuscate) {
- builder.setProguardMapFile(Paths.get(APP_DIR, DEFAULT_PROGUARD_MAP_FILE));
+ builder.setProguardMapFile(Paths.get(APP_DIR, PG_MAP));
}
- builder.addProgramFiles(
- Files.list(Paths.get(APP_DIR))
- .filter(FileUtils::isDexFile)
- .collect(Collectors.toList()));
+ builder.addProgramFiles(Paths.get(APP_DIR).resolve(RELEASE_APK_X86));
Disassemble.DisassembleCommand command = builder.build();
Disassemble.disassemble(command);
} finally {
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10ProguardMapReaderTest.java
new file mode 100644
index 0000000..2c2742a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10ProguardMapReaderTest.java
@@ -0,0 +1,24 @@
+// 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.internal;
+
+import com.android.tools.r8.naming.ClassNameMapper;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GMSCoreV10ProguardMapReaderTest extends GMSCoreCompilationTestBase {
+
+ private static final String APP_DIR = "third_party/gmscore/gmscore_v10/";
+
+ @Test
+ public void roundTripTestGmsCoreV10() throws IOException {
+ Path map = Paths.get(APP_DIR).resolve(PG_MAP);
+ ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(map);
+ ClassNameMapper secondMapper = ClassNameMapper.mapperFromString(firstMapper.toString());
+ Assert.assertEquals(firstMapper, secondMapper);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/ProguardMapReaderGMSCoreTest.java b/src/test/java/com/android/tools/r8/internal/ProguardMapReaderGMSCoreTest.java
deleted file mode 100644
index f3fdca4..0000000
--- a/src/test/java/com/android/tools/r8/internal/ProguardMapReaderGMSCoreTest.java
+++ /dev/null
@@ -1,65 +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.internal;
-
-import com.android.tools.r8.naming.ClassNameMapper;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class ProguardMapReaderGMSCoreTest {
-
- public static final String GMSCORE_V4_MAP = "third_party/gmscore/v4/proguard.map";
- public static final String GMSCORE_V5_MAP = "third_party/gmscore/v5/proguard.map";
- public static final String GMSCORE_V6_MAP = "third_party/gmscore/v6/proguard.map";
- public static final String GMSCORE_V7_MAP = "third_party/gmscore/v7/proguard.map";
- public static final String GMSCORE_V8_MAP = "third_party/gmscore/v8/proguard.map";
- public static final String GMSCORE_V9_MAP =
- "third_party/gmscore/gmscore_v9/GmsCore_prod_alldpi_release_all_locales_proguard.map";
- public static final String GMSCORE_V10_MAP =
- "third_party/gmscore/gmscore_v10/GmsCore_prod_alldpi_release_all_locales_proguard.map";
-
- public void roundTripTest(Path path) throws IOException {
- ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(path);
- ClassNameMapper secondMapper = ClassNameMapper.mapperFromString(firstMapper.toString());
- Assert.assertEquals(firstMapper, secondMapper);
- }
-
- @Test
- public void roundTripTestGmsCoreV4() throws IOException {
- roundTripTest(Paths.get(GMSCORE_V4_MAP));
- }
-
- @Test
- public void roundTripTestGmsCoreV5() throws IOException {
- roundTripTest(Paths.get(GMSCORE_V5_MAP));
- }
-
- @Test
- public void roundTripTestGmsCoreV6() throws IOException {
- roundTripTest(Paths.get(GMSCORE_V6_MAP));
- }
-
- @Test
- public void roundTripTestGmsCoreV7() throws IOException {
- roundTripTest(Paths.get(GMSCORE_V7_MAP));
- }
-
- @Test
- public void roundTripTestGmsCoreV8() throws IOException {
- roundTripTest(Paths.get(GMSCORE_V8_MAP));
- }
-
- @Test
- public void roundTripTestGmsCoreV9() throws IOException {
- roundTripTest(Paths.get(GMSCORE_V9_MAP));
- }
-
- @Test
- public void roundTripTestGmsCoreV10() throws IOException {
- roundTripTest(Paths.get(GMSCORE_V10_MAP));
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SyntheticInitClassPositionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SyntheticInitClassPositionTest.java
index 1dc11c0..fe6ef20 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SyntheticInitClassPositionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SyntheticInitClassPositionTest.java
@@ -52,7 +52,6 @@
.addKeepAttributeLineNumberTable()
.addKeepAttributeSourceFile()
.setMinApi(parameters.getApiLevel())
- .compile()
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatThrows(ExceptionInInitializerError.class)
.inspectStackTrace(stackTrace -> assertThat(stackTrace, isSame(expectedStackTrace)));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
index a86ee63..0531857 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
@@ -49,8 +50,11 @@
options.outline.threshold = 2;
options.outline.minSize = 2;
})
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .noHorizontalClassMergingOfSynthetics()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
index b4dd6b7..1e5ef3f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
@@ -62,7 +62,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
@@ -80,23 +80,26 @@
.assertSuccessWithOutput(EXPECTED);
}
- CodeInspector inspector = testForR8(parameters.getBackend())
- .noMinification()
- .setMinApi(parameters.getRuntime())
- .addProgramClassesAndInnerClasses(TestClass.class)
- .addKeepMainRule(TestClass.class)
- .addOptionsModification(options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
- // To trigger outliner, set # of expected outline candidate as threshold.
- options.outline.threshold = 2;
- options.enableInlining = false;
- })
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspector();
+ CodeInspector inspector =
+ testForR8(parameters.getBackend())
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClassesAndInnerClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> {
+ if (parameters.isCfRuntime()) {
+ assert !options.outline.enabled;
+ options.outline.enabled = true;
+ }
+ // To trigger outliner, set # of expected outline candidate as threshold.
+ options.outline.threshold = 2;
+ options.enableInlining = false;
+ })
+ .noHorizontalClassMergingOfSynthetics()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspector();
for (FoundClassSubject clazz : inspector.allClasses()) {
if (!SyntheticItemsTestUtils.isExternalOutlineClass(clazz.getFinalReference())) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
index dd6dace..0d1f637 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
@@ -6,6 +6,7 @@
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.notIf;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -25,7 +26,6 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
-import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,18 +66,23 @@
return false;
}
- private void checkOutlineFromFeature(CodeInspector inspector) {
- // There are two expected outlines, each is currently in its own class.
- List<FoundMethodSubject> allMethods = new ArrayList<>();
- for (int i = 0; i < 2; i++) {
- ClassSubject clazz =
- inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, i));
- assertThat(clazz, isPresent());
- assertEquals(1, clazz.allMethods().size());
- allMethods.addAll(clazz.allMethods());
- }
+ private ClassSubject checkOutlineFromFeature(CodeInspector inspector) {
+ // There are two expected outlines, in a single single class after horizontal class merging.
+ ClassSubject classSubject0 =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 0));
+ ClassSubject classSubject1 =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 1));
+ assertThat(classSubject1, notIf(isPresent(), classSubject0.isPresent()));
+
+ ClassSubject classSubject = classSubject0.isPresent() ? classSubject0 : classSubject1;
+
+ List<FoundMethodSubject> allMethods = classSubject.allMethods();
+ assertEquals(2, allMethods.size());
+
// One of the methods is StringBuilder the other references the feature.
assertTrue(allMethods.stream().anyMatch(this::referenceFeatureClass));
+
+ return classSubject;
}
@Test
@@ -89,33 +94,24 @@
.addKeepClassAndMembersRules(FeatureClass.class)
.setMinApi(parameters.getApiLevel())
.addOptionsModification(options -> options.outline.threshold = 2)
- .compile()
- .inspect(this::checkOutlineFromFeature);
+ .compile();
+
+ CodeInspector inspector = compileResult.inspector();
+ ClassSubject outlineClass = checkOutlineFromFeature(inspector);
// Check that parts of method1, ..., method4 in FeatureClass was outlined.
- ClassSubject featureClass = compileResult.inspector().clazz(FeatureClass.class);
+ ClassSubject featureClass = inspector.clazz(FeatureClass.class);
assertThat(featureClass, isPresent());
// Find the final names of the two outline classes.
- String outlineClassName0 =
- ClassNameMapper.mapperFromString(compileResult.getProguardMap())
- .getObfuscatedToOriginalMapping()
- .inverse
- .get(
- SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 0).getTypeName());
- String outlineClassName1 =
- ClassNameMapper.mapperFromString(compileResult.getProguardMap())
- .getObfuscatedToOriginalMapping()
- .inverse
- .get(
- SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 1).getTypeName());
+ String outlineClassName = outlineClass.getFinalName();
// Verify they are called from the feature methods.
// Note: should the choice of synthetic grouping change these expectations will too.
- assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName0));
- assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName0));
- assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName1));
- assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName1));
+ assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
+ assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
+ assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName));
+ assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName));
compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("123456");
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index d268888..5a60806 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -117,6 +116,7 @@
.addKeepAttributes("InnerClasses", "EnclosingMethod")
.addOptionsModification(this::configure)
.enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), main)
.assertSuccessWithOutput(EXPECTED);
@@ -124,10 +124,11 @@
@Test
public void testTrivial() throws Exception {
- SingleTestRunResult result =
+ R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.addKeepMainRule(main)
.noMinification()
.addKeepAttributes("InnerClasses", "EnclosingMethod")
@@ -241,7 +242,7 @@
HostOkFieldOnly.class,
CandidateOkFieldOnly.class
};
- SingleTestRunResult result =
+ R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
@@ -283,6 +284,7 @@
.addProgramClasses(classes)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableMemberValuePropagationAnnotations()
.addKeepMainRule(main)
.allowAccessModification()
@@ -405,7 +407,7 @@
Candidate.class
};
String javaOutput = runOnJava(main);
- SingleTestRunResult result =
+ R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
index 4e3f691..fe09d69 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -45,6 +46,7 @@
.addKeepMainRule(MAIN)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("Companion#boo", "Companion#foo")
@@ -79,6 +81,7 @@
static class Host {
private static final Companion companion = new Companion();
+ @NoHorizontalClassMerging
static class Companion {
@NeverInline
private static Object boo() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
index 295657a..8c0b01f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
@@ -6,7 +6,9 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoHorizontalClassMerging;
+@NoHorizontalClassMerging
public class CandidateConflictField {
@NeverPropagateValue
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
index 6bc91b4..a743f11 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -5,7 +5,9 @@
package com.android.tools.r8.ir.optimize.staticizer.trivial;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+@NoHorizontalClassMerging
public class SimpleWithGetter {
private static SimpleWithGetter INSTANCE = new SimpleWithGetter();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
index 24edea4..b02dc73 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
@@ -5,7 +5,9 @@
package com.android.tools.r8.ir.optimize.staticizer.trivial;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+@NoHorizontalClassMerging
public class SimpleWithParams {
static SimpleWithParams INSTANCE = new SimpleWithParams(123);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java
index f3c76cb..24786f4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.ir.optimize.staticizer.trivial;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+@NoHorizontalClassMerging
public class SimpleWithPhi {
public static class Companion {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
index 1f82c8b..0a80034 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -21,6 +21,7 @@
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.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -66,7 +67,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> {
HorizontalClassMergerOptions defaultHorizontalClassMergerOptions =
- new HorizontalClassMergerOptions();
+ new InternalOptions().horizontalClassMergerOptions();
assertEquals(4833, inspector.getSources().size());
assertEquals(167, inspector.getTargets().size());
assertTrue(
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 3b0b052..8dde075 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -23,6 +23,7 @@
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
@@ -284,11 +285,13 @@
return new StackTrace(stackTraceLines, stderr);
}
+ private static List<StackTraceLine> internalConvert(Stream<String> lines) {
+ return lines.map(StackTraceLine::parse).collect(Collectors.toList());
+ }
+
private static List<StackTraceLine> internalExtractFromJvm(String stderr) {
- return StringUtils.splitLines(stderr).stream()
- .filter(s -> s.startsWith(TAB_AT_PREFIX))
- .map(StackTraceLine::parse)
- .collect(Collectors.toList());
+ return internalConvert(
+ StringUtils.splitLines(stderr).stream().filter(s -> s.startsWith(TAB_AT_PREFIX)));
}
public static StackTrace extractFromJvm(String stderr) {
@@ -324,7 +327,7 @@
.build(),
allowExperimentalMapping);
// Keep the original stderr in the retraced stacktrace.
- return new StackTrace(internalExtractFromJvm(StringUtils.lines(box.result)), originalStderr);
+ return new StackTrace(internalConvert(box.result.stream()), originalStderr);
}
public StackTrace filter(Predicate<StackTraceLine> filter) {
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
index 47240a5..04f722d 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileName;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
@@ -51,6 +52,9 @@
@Test
public void testSourceFileAndLineNumberTable() throws Exception {
+ // TODO(b/186015503): This test fails when mapping via PCs.
+ // also the test should be updated to use TestParameters and api levels.
+ assumeTrue("b/186015503", !backend.isDex() || mode != CompilationMode.RELEASE);
runTest(
ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
// For the desugaring to companion classes the retrace stacktrace is still the same
diff --git a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
index 68c1d36..90617f7 100644
--- a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.regress.b150400371;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -44,8 +45,12 @@
(inspector) -> {
MethodSubject main =
inspector.method(InlineInto.class.getDeclaredMethod("main", String[].class));
- IntSet lines = new IntArraySet(main.getLineNumberTable().getLines());
- assertEquals(2, lines.size());
+ if (parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport())) {
+ IntSet lines = new IntArraySet(main.getLineNumberTable().getLines());
+ assertEquals(2, lines.size());
+ } else {
+ assertNull(main.getLineNumberTable());
+ }
});
}
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index dfe1002..24fad30 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -47,7 +47,11 @@
public static List<Object[]> data() {
// TODO(b/141817471): Extend with compilation modes.
return buildParameters(
- getTestParameters().withAllRuntimesAndApiLevels().build(),
+ getTestParameters()
+ .withAllRuntimes()
+ // TODO(b/186018416): Update to support tests retracing with PC mappings.
+ .withApiLevelsEndingAtExcluding(apiLevelWithPcAsLineNumberSupport())
+ .build(),
getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
}
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 232c1f5..ed842eb 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -47,7 +47,11 @@
public static List<Object[]> data() {
// TODO(b/141817471): Extend with compilation modes.
return buildParameters(
- getTestParameters().withAllRuntimesAndApiLevels().build(),
+ getTestParameters()
+ .withAllRuntimes()
+ // TODO(b/186018416): Update to support tests retracing with PC mappings.
+ .withApiLevelsEndingAtExcluding(apiLevelWithPcAsLineNumberSupport())
+ .build(),
getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index cd839f1..c1a704e 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -54,7 +54,7 @@
private final boolean testExternal;
- @Parameters(name = "{0}")
+ @Parameters(name = "external: {0}")
public static Boolean[] data() {
return BooleanUtils.values();
}
@@ -96,6 +96,13 @@
}
@Test
+ public void testMissingStackTraceFile() throws IOException {
+ Path mappingFile = folder.newFile("mapping.txt").toPath();
+ Files.write(mappingFile, "foo.bar.baz -> foo:".getBytes());
+ runAbortTest(containsString("NoSuchFileException"), mappingFile.toString(), "stacktrace.txt");
+ }
+
+ @Test
public void testVerbose() throws IOException {
FoundMethodVerboseStackTrace stackTrace = new FoundMethodVerboseStackTrace();
runTest(
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java
index f7e50d9..abb4c9f 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java
@@ -9,6 +9,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestShrinkerBuilder;
@@ -61,9 +62,12 @@
@Test
public void test() throws Exception {
if (shrinker.isR8()) {
- run(testForR8(parameters.getBackend()));
+ run(testForR8(parameters.getBackend()).enableNoHorizontalClassMergingAnnotations());
} else {
- run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+ run(
+ testForProguard(shrinker.getProguardVersion())
+ .addDontWarn(getClass())
+ .addNoHorizontalClassMergingAnnotations());
}
}
@@ -113,6 +117,7 @@
}
}
+ @NoHorizontalClassMerging
static class B {
public String foo() {
return "B::foo";
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
index c9b3bc7..f198845 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
@@ -9,6 +9,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestShrinkerBuilder;
@@ -64,9 +65,13 @@
run(
testForR8(parameters.getBackend())
// Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
- .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation));
+ .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)
+ .enableNoHorizontalClassMergingAnnotations());
} else {
- run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+ run(
+ testForProguard(shrinker.getProguardVersion())
+ .addDontWarn(getClass())
+ .addNoHorizontalClassMergingAnnotations());
}
}
@@ -107,6 +112,7 @@
}
}
+ @NoHorizontalClassMerging
static class B {
public String foo() {
return "B::foo";
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
index e7d5191..5a1d0f9 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
@@ -9,6 +9,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestShrinkerBuilder;
@@ -64,9 +65,13 @@
run(
testForR8(parameters.getBackend())
// Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
- .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation));
+ .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)
+ .enableNoHorizontalClassMergingAnnotations());
} else {
- run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+ run(
+ testForProguard(shrinker.getProguardVersion())
+ .addDontWarn(getClass())
+ .addNoHorizontalClassMergingAnnotations());
}
}
@@ -116,6 +121,7 @@
}
}
+ @NoHorizontalClassMerging
static class B {
public String foo() {
return "B::foo";
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
index a314e96..82bcb21 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.attributes;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -62,6 +63,11 @@
assertFalse(mainMethod.hasLocalVariableTable());
}
+ private boolean doesNotHavePcSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport());
+ }
+
@Test
public void keepLineNumberTable()
throws CompilationFailedException, IOException, ExecutionException {
@@ -69,7 +75,7 @@
"-keepattributes " + ProguardKeepAttributes.LINE_NUMBER_TABLE
);
MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
- assertTrue(mainMethod.hasLineNumberTable());
+ assertEquals(doesNotHavePcSupport(), mainMethod.hasLineNumberTable());
assertFalse(mainMethod.hasLocalVariableTable());
}
@@ -83,7 +89,7 @@
+ ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
);
MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
- assertTrue(mainMethod.hasLineNumberTable());
+ assertEquals(doesNotHavePcSupport(), mainMethod.hasLineNumberTable());
// Locals are never included in release builds.
assertFalse(mainMethod.hasLocalVariableTable());
}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index fe06c06..9e49bc9 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -65,6 +65,24 @@
originalMethod.getMethodDescriptor());
}
+ public static boolean isExternalSynthetic(ClassReference reference) {
+ for (SyntheticKind kind : SyntheticKind.values()) {
+ if (kind == SyntheticKind.RECORD_TAG) {
+ continue;
+ }
+ if (kind.isFixedSuffixSynthetic) {
+ if (SyntheticNaming.isSynthetic(reference, null, kind)) {
+ return true;
+ }
+ } else {
+ if (SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, kind)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
public static boolean isInternalLambda(ClassReference reference) {
return SyntheticNaming.isSynthetic(reference, Phase.INTERNAL, SyntheticKind.LAMBDA);
}
diff --git a/src/test/java/com/android/tools/r8/utils/ApkUtils.java b/src/test/java/com/android/tools/r8/utils/ApkUtils.java
new file mode 100644
index 0000000..0fae220
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ApkUtils.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.utils;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class ApkUtils {
+
+ public static ProcessResult apkMasseur(Path apk, Path dexSources, Path out) throws IOException {
+ ImmutableList.Builder<String> command =
+ new ImmutableList.Builder<String>()
+ .add("tools/apk_masseur.py")
+ .add("--dex")
+ .add(dexSources.toString())
+ .add("--out")
+ .add(out.toString())
+ .add("--install")
+ .add(apk.toString());
+ ProcessBuilder builder = new ProcessBuilder(command.build());
+ builder.directory(Paths.get(ToolHelper.THIRD_PARTY_DIR).toAbsolutePath().getParent().toFile());
+ return ToolHelper.runProcess(builder);
+ }
+}
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 e6fa548..0410bc0 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
@@ -106,7 +106,14 @@
}
public HorizontallyMergedClassesInspector assertNoClassesMerged() {
- assertTrue(horizontallyMergedClasses.getSources().isEmpty());
+ if (!horizontallyMergedClasses.getSources().isEmpty()) {
+ DexType source = horizontallyMergedClasses.getSources().iterator().next();
+ fail(
+ "Expected no classes to be merged, got: "
+ + source.getTypeName()
+ + " -> "
+ + getTarget(source).getTypeName());
+ }
return this;
}
@@ -146,6 +153,11 @@
Stream.of(classes).map(Reference::classFromClass).collect(Collectors.toList()));
}
+ public HorizontallyMergedClassesInspector assertIsCompleteMergeGroup(
+ ClassReference... classReferences) {
+ return assertIsCompleteMergeGroup(Arrays.asList(classReferences));
+ }
+
public HorizontallyMergedClassesInspector assertIsCompleteMergeGroup(String... typeNames) {
return assertIsCompleteMergeGroup(
Stream.of(typeNames).map(Reference::classFromTypeName).collect(Collectors.toList()));
diff --git a/third_party/api-outlining/simple-app-dump.tar.gz.sha1 b/third_party/api-outlining/simple-app-dump.tar.gz.sha1
new file mode 100644
index 0000000..cf9cc96
--- /dev/null
+++ b/third_party/api-outlining/simple-app-dump.tar.gz.sha1
@@ -0,0 +1 @@
+9913f083a29519e3fdc626a769ad33c7ba2498c1
\ No newline at end of file