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