Merge commit 'd9bd076076490ac1b89acf2385d94d15dc6e1b68' into dev-release
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg index 577a0d0..49e39c7 100644 --- a/infra/config/global/generated/cr-buildbucket.cfg +++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -24,7 +24,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -53,7 +53,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -82,7 +82,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -118,7 +118,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -156,7 +156,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -190,7 +190,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -222,7 +222,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -255,7 +255,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -292,7 +292,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -329,7 +329,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -366,7 +366,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -403,7 +403,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -440,7 +440,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -477,7 +477,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -514,7 +514,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -551,7 +551,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -588,7 +588,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -625,7 +625,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -662,7 +662,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -699,7 +699,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -736,7 +736,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -773,7 +773,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -810,7 +810,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -847,7 +847,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -884,7 +884,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -921,7 +921,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -958,7 +958,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -995,7 +995,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1032,7 +1032,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1069,7 +1069,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1105,7 +1105,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1141,7 +1141,7 @@ dimensions: "cores:2" dimensions: "cpu:x86-64" dimensions: "internal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1174,7 +1174,7 @@ dimensions: "cores:2" dimensions: "cpu:x86-64" dimensions: "internal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1207,7 +1207,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1243,7 +1243,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1279,7 +1279,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1315,7 +1315,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1351,7 +1351,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1387,7 +1387,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1423,7 +1423,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1459,7 +1459,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1494,7 +1494,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1531,7 +1531,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1569,7 +1569,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1605,7 +1605,7 @@ dimensions: "cores:8" dimensions: "cpu:x86-64" dimensions: "normal:true" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1640,7 +1640,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave" @@ -1672,7 +1672,7 @@ swarming_tags: "vpython:native-python-wrapper" dimensions: "cores:8" dimensions: "cpu:x86-64" - dimensions: "os:Ubuntu-18.04" + dimensions: "os:Ubuntu-22.04" dimensions: "pool:luci.r8.ci" exe { cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg index 90b913a..11b6c85 100644 --- a/infra/config/global/generated/project.cfg +++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@ name: "r8" access: "group:all" lucicfg { - version: "1.38.1" + version: "1.38.2" package_dir: ".." config_dir: "generated" entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star index bbbf235..9fb3733 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star
@@ -178,7 +178,7 @@ if windows: dimensions["os"] = "Windows-10" else: - dimensions["os"] = "Ubuntu-18.04" + dimensions["os"] = "Ubuntu-22.04" if internal: dimensions["internal"] = "true" if normal:
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java index f251ecb..285ef1b 100644 --- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java +++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -65,6 +65,7 @@ private final List<ArtProfileForRewriting> artProfilesForRewriting; private final List<StartupProfileProvider> startupProfileProviders; private final ClassConflictResolver classConflictResolver; + private final CancelCompilationChecker cancelCompilationChecker; BaseCompilerCommand(boolean printHelp, boolean printVersion) { super(printHelp, printVersion); @@ -87,6 +88,7 @@ artProfilesForRewriting = null; startupProfileProviders = null; classConflictResolver = null; + cancelCompilationChecker = null; } BaseCompilerCommand( @@ -109,7 +111,8 @@ boolean isAndroidPlatformBuild, List<ArtProfileForRewriting> artProfilesForRewriting, List<StartupProfileProvider> startupProfileProviders, - ClassConflictResolver classConflictResolver) { + ClassConflictResolver classConflictResolver, + CancelCompilationChecker cancelCompilationChecker) { super(app); assert minApiLevel > 0; assert mode != null; @@ -132,6 +135,7 @@ this.artProfilesForRewriting = artProfilesForRewriting; this.startupProfileProviders = startupProfileProviders; this.classConflictResolver = classConflictResolver; + this.cancelCompilationChecker = cancelCompilationChecker; } /** @@ -244,6 +248,10 @@ return classConflictResolver; } + public CancelCompilationChecker getCancelCompilationChecker() { + return cancelCompilationChecker; + } + DumpInputFlags getDumpInputFlags() { return dumpInputFlags; } @@ -287,6 +295,7 @@ private List<ArtProfileForRewriting> artProfilesForRewriting = new ArrayList<>(); private List<StartupProfileProvider> startupProfileProviders = new ArrayList<>(); private ClassConflictResolver classConflictResolver = null; + private CancelCompilationChecker cancelCompilationChecker = null; abstract CompilationMode defaultCompilationMode(); @@ -733,6 +742,21 @@ } /** + * Set a cancellation checker. + * + * <p>The cancellation checker will be periodically called to check if the compilation should be + * cancelled before completion. + */ + public B setCancelCompilationChecker(CancelCompilationChecker checker) { + this.cancelCompilationChecker = checker; + return self(); + } + + public CancelCompilationChecker getCancelCompilationChecker() { + return cancelCompilationChecker; + } + + /** * Allow to skip to dump into file and dump into directory instruction, this is primarily used * for chained compilation in L8 so there are no duplicated dumps. */
diff --git a/src/main/java/com/android/tools/r8/CancelCompilationChecker.java b/src/main/java/com/android/tools/r8/CancelCompilationChecker.java new file mode 100644 index 0000000..f08cde1 --- /dev/null +++ b/src/main/java/com/android/tools/r8/CancelCompilationChecker.java
@@ -0,0 +1,26 @@ +// Copyright (c) 2023, 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; + +/** Client supplied checker to allow cancelling a compilation before completion. */ +@Keep +public interface CancelCompilationChecker { + + /** + * Callback that is called periodically by the compiler to check if the current compilation should + * be cancelled before completion. The method should return true or false. The behavior of + * throwing an exception from this method will also abort the compilation but the compiler will + * treat such cases as an unexpected runtime failure and not as an expected client cancellation. + * + * <p>If this method returns true at any point before compilation has successfully completed, then + * the compiler will exit with a {@link CompilationFailedException}. It is *not* possible to + * cancel the cancellation. + * + * <p>This method may be called from multiple compiler threads. It is up to the client to ensure + * thread safety in determining if compilation should be cancelled. + * + * @return True if the compilation should be cancelled, otherwise false. + */ + boolean cancel(); +}
diff --git a/src/main/java/com/android/tools/r8/CompilationFailedException.java b/src/main/java/com/android/tools/r8/CompilationFailedException.java index 0078c06..94c7fde 100644 --- a/src/main/java/com/android/tools/r8/CompilationFailedException.java +++ b/src/main/java/com/android/tools/r8/CompilationFailedException.java
@@ -10,19 +10,15 @@ @Keep public class CompilationFailedException extends Exception { - public CompilationFailedException() { - super("Compilation failed to complete"); - } + private final boolean cancelled; - public CompilationFailedException(Throwable cause) { - this("Compilation failed to complete", cause); - } - - public CompilationFailedException(String message, Throwable cause) { + CompilationFailedException(String message, Throwable cause, boolean cancelled) { super(message, cause); + this.cancelled = cancelled; } - public CompilationFailedException(String message) { - super(message); + /** True if the compilation was cancelled by {@link CancelCompilationChecker} otherwise false. */ + public boolean wasCancelled() { + return cancelled; } }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index d1495c2..49f8155 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.inspector.Inspector; import com.android.tools.r8.inspector.internal.InspectorImpl; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification; +import com.android.tools.r8.naming.ProguardMapStringConsumer; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.profile.art.ArtProfileForRewriting; import com.android.tools.r8.shaking.ProguardConfigurationParser; @@ -490,6 +491,7 @@ getArtProfilesForRewriting(), getStartupProfileProviders(), getClassConflictResolver(), + getCancelCompilationChecker(), factory); } } @@ -585,6 +587,7 @@ List<ArtProfileForRewriting> artProfilesForRewriting, List<StartupProfileProvider> startupProfileProviders, ClassConflictResolver classConflictResolver, + CancelCompilationChecker cancelCompilationChecker, DexItemFactory factory) { super( inputApp, @@ -606,7 +609,8 @@ isAndroidPlatformBuild, artProfilesForRewriting, startupProfileProviders, - classConflictResolver); + classConflictResolver, + cancelCompilationChecker); this.intermediate = intermediate; this.globalSyntheticsConsumer = globalSyntheticsConsumer; this.syntheticInfoConsumer = syntheticInfoConsumer; @@ -665,7 +669,13 @@ internal.setSyntheticInfoConsumer(syntheticInfoConsumer); internal.desugarGraphConsumer = desugarGraphConsumer; internal.mainDexKeepRules = mainDexKeepRules; - internal.proguardMapConsumer = proguardMapConsumer; + internal.proguardMapConsumer = + proguardMapConsumer == null + ? null + : ProguardMapStringConsumer.builder() + .setStringConsumer(proguardMapConsumer) + .setDiagnosticsHandler(getReporter()) + .build(); internal.lineNumberOptimization = !internal.debug && proguardMapConsumer != null ? LineNumberOptimization.ON @@ -742,6 +752,8 @@ ProgramClassCollection.wrappedConflictResolver( getClassConflictResolver(), internal.reporter); + internal.cancelCompilationChecker = getCancelCompilationChecker(); + internal.tool = Tool.D8; internal.setDumpInputFlags(getDumpInputFlags()); internal.dumpOptions = dumpOptions();
diff --git a/src/main/java/com/android/tools/r8/Finishable.java b/src/main/java/com/android/tools/r8/Finishable.java new file mode 100644 index 0000000..2e114ef --- /dev/null +++ b/src/main/java/com/android/tools/r8/Finishable.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2023, 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; + +public interface Finishable { + + /** + * Callback when no further content will be provided for the resource. + * + * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics + * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown, + * then the compiler guaranties to exit with an error. + * + * @param handler Diagnostics handler for reporting. + */ + default void finished(DiagnosticsHandler handler) {} +}
diff --git a/src/main/java/com/android/tools/r8/InternalCompilationFailedExceptionUtils.java b/src/main/java/com/android/tools/r8/InternalCompilationFailedExceptionUtils.java new file mode 100644 index 0000000..18e96ff --- /dev/null +++ b/src/main/java/com/android/tools/r8/InternalCompilationFailedExceptionUtils.java
@@ -0,0 +1,31 @@ +// Copyright (c) 2023, 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; + +/** Non-API class to allocate compilation failed exceptions without exposing the constructor. */ +public final class InternalCompilationFailedExceptionUtils { + + private InternalCompilationFailedExceptionUtils() {} + + public static CompilationFailedException createForTesting() { + return createForTesting("Compilation failed to complete", null); + } + + public static CompilationFailedException createForTesting(String message) { + return createForTesting(message, null); + } + + public static CompilationFailedException createForTesting(Throwable cause) { + return createForTesting("Compilation failed to complete", cause); + } + + public static CompilationFailedException createForTesting(String message, Throwable cause) { + return create(message, cause, false); + } + + public static CompilationFailedException create( + String message, Throwable cause, boolean cancelled) { + return new CompilationFailedException(message, cause, cancelled); + } +}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java index 6bac5df..7a28ef3 100644 --- a/src/main/java/com/android/tools/r8/L8Command.java +++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -102,6 +102,7 @@ DumpInputFlags dumpInputFlags, MapIdProvider mapIdProvider, ClassConflictResolver classConflictResolver, + CancelCompilationChecker cancelCompilationChecker, DexItemFactory factory) { super( inputApp, @@ -123,7 +124,8 @@ false, null, null, - classConflictResolver); + classConflictResolver, + cancelCompilationChecker); this.d8Command = d8Command; this.r8Command = r8Command; this.desugaredLibrarySpecification = desugaredLibrarySpecification; @@ -228,6 +230,8 @@ ProgramClassCollection.wrappedConflictResolver( getClassConflictResolver(), internal.reporter); + internal.cancelCompilationChecker = getCancelCompilationChecker(); + if (!DETERMINISTIC_DEBUGGING) { assert internal.threadCount == ThreadUtils.NOT_SPECIFIED; internal.threadCount = getThreadCount(); @@ -456,6 +460,7 @@ getDumpInputFlags(), getMapIdProvider(), getClassConflictResolver(), + getCancelCompilationChecker(), factory); } }
diff --git a/src/main/java/com/android/tools/r8/ProguardMapConsumer.java b/src/main/java/com/android/tools/r8/ProguardMapConsumer.java new file mode 100644 index 0000000..f00b428 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ProguardMapConsumer.java
@@ -0,0 +1,13 @@ +// Copyright (c) 2023, 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.naming.ClassNameMapper; +import com.android.tools.r8.naming.ProguardMapMarkerInfo; + +public abstract class ProguardMapConsumer implements Finishable { + + public abstract void accept(ProguardMapMarkerInfo makerInfo, ClassNameMapper classNameMapper); +}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index d96d546..2ff0966 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -67,10 +67,10 @@ import com.android.tools.r8.optimize.MemberRebindingAnalysis; import com.android.tools.r8.optimize.MemberRebindingIdentityLens; import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory; -import com.android.tools.r8.optimize.RedundantBridgeRemover; import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting; import com.android.tools.r8.optimize.interfaces.analysis.CfOpenClosedInterfacesAnalysis; import com.android.tools.r8.optimize.proto.ProtoNormalizer; +import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemover; import com.android.tools.r8.origin.CommandLineOrigin; import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker; import com.android.tools.r8.profile.art.rewriting.ProfileCollectionAdditions;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index ee35e7f..6003038 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.keepanno.asm.KeepEdgeReader; import com.android.tools.r8.keepanno.ast.KeepEdge; import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor; +import com.android.tools.r8.naming.ProguardMapStringConsumer; import com.android.tools.r8.naming.SourceFileRewriter; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.origin.PathOrigin; @@ -659,7 +660,7 @@ getArtProfilesForRewriting(), getStartupProfileProviders(), getClassConflictResolver(), - fakeCompilerVersion); + getCancelCompilationChecker()); if (inputDependencyGraphConsumer != null) { inputDependencyGraphConsumer.finished(); @@ -846,7 +847,6 @@ private final FeatureSplitConfiguration featureSplitConfiguration; private final String synthesizedClassPrefix; private final boolean enableMissingLibraryApiModeling; - private final SemanticVersion fakeCompilerVersion; /** Get a new {@link R8Command.Builder}. */ public static Builder builder() { @@ -940,7 +940,7 @@ List<ArtProfileForRewriting> artProfilesForRewriting, List<StartupProfileProvider> startupProfileProviders, ClassConflictResolver classConflictResolver, - SemanticVersion fakeCompilerVersion) { + CancelCompilationChecker cancelCompilationChecker) { super( inputApp, mode, @@ -961,7 +961,8 @@ isAndroidPlatformBuild, artProfilesForRewriting, startupProfileProviders, - classConflictResolver); + classConflictResolver, + cancelCompilationChecker); assert proguardConfiguration != null; assert mainDexKeepRules != null; this.mainDexKeepRules = mainDexKeepRules; @@ -983,7 +984,6 @@ this.featureSplitConfiguration = featureSplitConfiguration; this.synthesizedClassPrefix = synthesizedClassPrefix; this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling; - this.fakeCompilerVersion = fakeCompilerVersion; } private R8Command(boolean printHelp, boolean printVersion) { @@ -1007,7 +1007,6 @@ featureSplitConfiguration = null; synthesizedClassPrefix = null; enableMissingLibraryApiModeling = false; - fakeCompilerVersion = null; } public DexItemFactory getDexItemFactory() { @@ -1070,11 +1069,18 @@ } // Amend the proguard-map consumer with options from the proguard configuration. - internal.proguardMapConsumer = + StringConsumer stringConsumer = wrapStringConsumer( proguardMapConsumer, proguardConfiguration.isPrintMapping(), proguardConfiguration.getPrintMappingFile()); + internal.proguardMapConsumer = + stringConsumer == null + ? null + : ProguardMapStringConsumer.builder() + .setStringConsumer(stringConsumer) + .setDiagnosticsHandler(getReporter()) + .build(); // Amend the usage information consumer with options from the proguard configuration. internal.usageInformationConsumer = @@ -1173,6 +1179,8 @@ ProgramClassCollection.wrappedConflictResolver( getClassConflictResolver(), internal.reporter); + internal.cancelCompilationChecker = getCancelCompilationChecker(); + if (!DETERMINISTIC_DEBUGGING) { assert internal.threadCount == ThreadUtils.NOT_SPECIFIED; internal.threadCount = getThreadCount();
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java index d0e05cc..2583572 100644 --- a/src/main/java/com/android/tools/r8/StringConsumer.java +++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -15,7 +15,7 @@ /** Interface for receiving String resource. */ @KeepForSubclassing -public interface StringConsumer { +public interface StringConsumer extends Finishable { /** * Callback to receive part of a string resource. @@ -32,17 +32,6 @@ */ void accept(String string, DiagnosticsHandler handler); - /** - * Callback when no further content will be provided for the string resource. - * - * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics - * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown, - * then the compiler guaranties to exit with an error. - * - * @param handler Diagnostics handler for reporting. - */ - default void finished(DiagnosticsHandler handler) {} - static EmptyConsumer emptyConsumer() { return EmptyConsumer.EMPTY_CONSUMER; }
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java index fb97359..1b732da 100644 --- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java +++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
@@ -47,6 +47,9 @@ return computeApiLevelForLibraryReference(reference, ComputedApiLevel.unknown()); } + public abstract ComputedApiLevel computeApiLevelForLibraryReferenceIgnoringDesugaredLibrary( + DexReference reference, ComputedApiLevel unknownValue); + public ComputedApiLevel computeApiLevelForDefinition(Iterable<DexType> types) { return computeApiLevelForDefinition(types, ComputedApiLevel.unknown()); } @@ -102,6 +105,12 @@ } @Override + public ComputedApiLevel computeApiLevelForLibraryReferenceIgnoringDesugaredLibrary( + DexReference reference, ComputedApiLevel unknownValue) { + return ComputedApiLevel.notSet(); + } + + @Override public ComputedApiLevel computeInitialMinApiLevel(InternalOptions options) { return ComputedApiLevel.notSet(); } @@ -141,6 +150,12 @@ } @Override + public ComputedApiLevel computeApiLevelForLibraryReferenceIgnoringDesugaredLibrary( + DexReference reference, ComputedApiLevel unknownValue) { + return cache.lookupIgnoringDesugaredLibrary(reference, unknownValue); + } + + @Override public void reportUnknownApiReferences() { cache .getUnknownReferencesToReport()
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java index 0e267e5..627063c 100644 --- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java +++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -67,12 +67,22 @@ } public ComputedApiLevel lookup(DexReference reference, ComputedApiLevel unknownValue) { + return lookup(reference, unknownValue, false); + } + + public ComputedApiLevel lookupIgnoringDesugaredLibrary( + DexReference reference, ComputedApiLevel unknownValue) { + return lookup(reference, unknownValue, true); + } + + private ComputedApiLevel lookup( + DexReference reference, ComputedApiLevel unknownValue, boolean ignoringDesugaredLibrary) { DexType contextType = reference.getContextType(); if (contextType.isArrayType()) { if (reference.isDexMethod() && reference.asDexMethod().match(factory.objectMembers.clone)) { return appView.computedMinApiLevel(); } - return lookup(contextType.toBaseType(factory), unknownValue); + return lookup(contextType.toBaseType(factory), unknownValue, ignoringDesugaredLibrary); } if (contextType.isPrimitiveType() || contextType.isVoidType()) { return appView.computedMinApiLevel(); @@ -87,10 +97,11 @@ if (reference.getContextType() == factory.objectType) { return appView.computedMinApiLevel(); } - if (appView - .options() - .machineDesugaredLibrarySpecification - .isContextTypeMaintainedOrRewritten(reference)) { + if (!ignoringDesugaredLibrary + && appView + .options() + .machineDesugaredLibrarySpecification + .isContextTypeMaintainedOrRewritten(reference)) { // If we end up desugaring the reference, the library classes is bridged by j$ which is part // of the program. return appView.computedMinApiLevel();
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java index 8a196d9..caa1c27 100644 --- a/src/main/java/com/android/tools/r8/bisect/Bisect.java +++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -5,7 +5,7 @@ import com.android.tools.r8.OutputMode; import com.android.tools.r8.ProgramConsumer; -import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.ProguardMapConsumer; import com.android.tools.r8.bisect.BisectOptions.Result; import com.android.tools.r8.dex.ApplicationReader; import com.android.tools.r8.dex.ApplicationWriter; @@ -182,9 +182,9 @@ private static void writeApp(DexApplication app, Path output, ExecutorService executor) throws IOException, ExecutionException { InternalOptions options = app.options; - // Save the original consumers so they can be unwrapped after write. + // Save the original consumers, so they can be unwrapped after write. ProgramConsumer programConsumer = options.programConsumer; - StringConsumer proguardMapConsumer = options.proguardMapConsumer; + ProguardMapConsumer proguardMapConsumer = options.proguardMapConsumer; AndroidAppConsumers compatSink = new AndroidAppConsumers(options); ApplicationWriter writer = ApplicationWriter.create(
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 95cdc02..398da41 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
@@ -120,23 +120,28 @@ } public int getAsmOpcode() { + return getAsmOpcode(opcode, type); + } + + public static int getAsmOpcode(Opcode opcode, NumericType type) { + int typeOffset = getAsmOpcodeTypeOffset(type); switch (opcode) { case Add: - return Opcodes.IADD + getAsmOpcodeTypeOffset(); + return Opcodes.IADD + typeOffset; case Sub: - return Opcodes.ISUB + getAsmOpcodeTypeOffset(); + return Opcodes.ISUB + typeOffset; case Mul: - return Opcodes.IMUL + getAsmOpcodeTypeOffset(); + return Opcodes.IMUL + typeOffset; case Div: - return Opcodes.IDIV + getAsmOpcodeTypeOffset(); + return Opcodes.IDIV + typeOffset; case Rem: - return Opcodes.IREM + getAsmOpcodeTypeOffset(); + return Opcodes.IREM + typeOffset; default: throw new Unreachable("CfArithmeticBinop has unknown opcode " + opcode); } } - private int getAsmOpcodeTypeOffset() { + private static int getAsmOpcodeTypeOffset(NumericType type) { switch (type) { case LONG: return Opcodes.LADD - Opcodes.IADD;
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java index c741ee9..eda00f9 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -49,6 +49,8 @@ import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; @@ -337,9 +339,17 @@ for (ProgramResource input : dexSources) { DexReader dexReader = new DexReader(input); if (options.passthroughDexCode) { - computedMinApiLevel = validateOrComputeMinApiLevel(computedMinApiLevel, dexReader); + if (!options.testing.dexContainerExperiment) { + computedMinApiLevel = validateOrComputeMinApiLevel(computedMinApiLevel, dexReader); + } else { + assert dexReader.getDexVersion() == DexVersion.V41; + } } - dexParsers.add(new DexParser<>(dexReader, PROGRAM, options)); + if (!options.testing.dexContainerExperiment) { + dexParsers.add(new DexParser<>(dexReader, PROGRAM, options)); + } else { + addDexParsersForContainer(dexParsers, dexReader); + } } options.setMinApiLevel(computedMinApiLevel); @@ -349,17 +359,56 @@ // Read the DexCode items and DexProgramClass items in parallel. if (!options.skipReadingDexCode) { ApplicationReaderMap applicationReaderMap = ApplicationReaderMap.getInstance(options); - for (DexParser<DexProgramClass> dexParser : dexParsers) { - futures.add( - executorService.submit( - () -> { - dexParser.addClassDefsTo( - classes::add, applicationReaderMap); // Depends on Methods, Code items etc. - })); + if (!options.testing.dexContainerExperiment) { + for (DexParser<DexProgramClass> dexParser : dexParsers) { + futures.add( + executorService.submit( + () -> { + dexParser.addClassDefsTo( + classes::add, + applicationReaderMap); // Depends on Methods, Code items etc. + })); + } + } else { + // All Dex parsers use the same DEX reader, so don't process in parallel. + for (int i = 0; i < dexParsers.size(); i++) { + dexParsers.get(i).addClassDefsTo(classes::add, applicationReaderMap); + } } } } + private void addDexParsersForContainer( + List<DexParser<DexProgramClass>> dexParsers, DexReader dexReader) { + // Find the start offsets of each dex section. + IntList offsets = new IntArrayList(); + dexReader.setByteOrder(); + int offset = 0; + while (offset < dexReader.end()) { + offsets.add(offset); + DexReader tmp = new DexReader(Origin.unknown(), dexReader.buffer.array(), offset); + assert tmp.getDexVersion() == DexVersion.V41; + assert dexReader.getUint(offset + Constants.HEADER_SIZE_OFFSET) + == Constants.TYPE_HEADER_ITEM_SIZE_V41; + assert dexReader.getUint(offset + Constants.HEADER_OFF_OFFSET) == offset; + int dataSize = dexReader.getUint(offset + Constants.DATA_SIZE_OFFSET); + int dataOffset = dexReader.getUint(offset + Constants.DATA_OFF_OFFSET); + int file_size = dexReader.getUint(offset + Constants.FILE_SIZE_OFFSET); + assert dataOffset == 0; + assert dataSize == 0; + offset += file_size; + } + assert offset == dexReader.end(); + // Create a parser for the last section with string data. + DexParser<DexProgramClass> last = + new DexParser<>(dexReader, PROGRAM, options, offsets.getInt(offsets.size() - 1), null); + // Create a parsers for the remaining sections with reference to the string data. + for (int i = 0; i < offsets.size() - 1; i++) { + dexParsers.add(new DexParser<>(dexReader, PROGRAM, options, offsets.getInt(i), last)); + } + dexParsers.add(last); + } + private boolean includeAnnotationClass(DexProgramClass clazz) { if (!options.pruneNonVissibleAnnotationClasses) { return true;
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 0d9fac5..263acfc 100644 --- a/src/main/java/com/android/tools/r8/dex/DexParser.java +++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -95,21 +95,23 @@ private final int NO_INDEX = -1; private final Origin origin; private DexReader dexReader; - private final DexSection[] dexSections; + private final List<DexSection> dexSections; + private final int offset; private int[] stringIDs; private final ClassKind<T> classKind; private final InternalOptions options; private Object2LongMap<String> checksums; - public static DexSection[] parseMapFrom(Path file) throws IOException { + public static List<DexSection> parseMapFrom(Path file) throws IOException { return parseMapFrom(Files.newInputStream(file), new PathOrigin(file)); } - public static DexSection[] parseMapFrom(InputStream stream, Origin origin) throws IOException { + public static List<DexSection> parseMapFrom(InputStream stream, Origin origin) + throws IOException { return parseMapFrom(new DexReader(origin, ByteStreams.toByteArray(stream))); } - private static DexSection[] parseMapFrom(DexReader dexReader) { + private static List<DexSection> parseMapFrom(DexReader dexReader) { DexParser<DexProgramClass> dexParser = new DexParser<>(dexReader, ClassKind.PROGRAM, new InternalOptions()); return dexParser.dexSections; @@ -144,13 +146,27 @@ private final DexItemFactory dexItemFactory; public DexParser(DexReader dexReader, ClassKind<T> classKind, InternalOptions options) { + this(dexReader, classKind, options, 0, null); + } + + public DexParser( + DexReader dexReader, + ClassKind<T> classKind, + InternalOptions options, + int offset, + DexParser<T> parserWithStringIDs) { assert dexReader.getOrigin() != null; this.origin = dexReader.getOrigin(); this.dexReader = dexReader; + this.offset = offset; this.dexItemFactory = options.itemFactory; dexReader.setByteOrder(); dexSections = parseMap(); - parseStringIDs(); + if (parserWithStringIDs == null) { + parseStringIDs(); + } else { + stringIDs = parserWithStringIDs.stringIDs; + } this.classKind = classKind; this.options = options; } @@ -919,12 +935,12 @@ return new DexSection(type, 0, 0, 0); } - private DexSection[] parseMap() { + private List<DexSection> parseMap() { // Read the dexSections information from the MAP. - int mapOffset = dexReader.getUint(Constants.MAP_OFF_OFFSET); + int mapOffset = dexReader.getUint(offset + Constants.MAP_OFF_OFFSET); dexReader.position(mapOffset); int mapSize = dexReader.getUint(); - final DexSection[] result = new DexSection[mapSize]; + final List<DexSection> result = new ArrayList<>(mapSize); for (int i = 0; i < mapSize; i++) { int type = dexReader.getUshort(); int unused = dexReader.getUshort(); @@ -943,12 +959,12 @@ + dexReader.end(), origin); } - result[i] = new DexSection(type, unused, size, offset); + result.add(new DexSection(type, unused, size, offset)); } for (int i = 0; i < mapSize - 1; i++) { - result[i].setEnd(result[i + 1].offset); + result.get(i).setEnd(result.get(i + 1).offset); } - result[mapSize - 1].setEnd(dexReader.end()); + result.get(mapSize - 1).setEnd(dexReader.end()); return result; }
diff --git a/src/main/java/com/android/tools/r8/dex/DexReader.java b/src/main/java/com/android/tools/r8/dex/DexReader.java index 18c0beb..87829fe 100644 --- a/src/main/java/com/android/tools/r8/dex/DexReader.java +++ b/src/main/java/com/android/tools/r8/dex/DexReader.java
@@ -25,7 +25,7 @@ public DexReader(ProgramResource resource) throws ResourceException, IOException { super(resource); - version = parseMagic(buffer); + version = parseMagic(buffer, 0); } /** @@ -35,18 +35,23 @@ */ DexReader(Origin origin, byte[] bytes) { super(origin, bytes); - version = parseMagic(buffer); + version = parseMagic(buffer, 0); + } + + DexReader(Origin origin, byte[] bytes, int offset) { + super(origin, bytes); + version = parseMagic(buffer, offset); } // Parse the magic header and determine the dex file version. - private DexVersion parseMagic(CompatByteBuffer buffer) { + private DexVersion parseMagic(CompatByteBuffer buffer, int offset) { try { buffer.get(); buffer.rewind(); } catch (BufferUnderflowException e) { throw new CompilationError("Dex file is empty", origin); } - int index = 0; + int index = offset; for (byte prefixByte : DEX_FILE_MAGIC_PREFIX) { byte actualByte = buffer.get(index++); if (actualByte != prefixByte) {
diff --git a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java index e848e3b..c2ae2a1 100644 --- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java +++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -8,7 +8,6 @@ import com.android.tools.r8.experimental.startup.StartupProfile; import com.android.tools.r8.experimental.startup.profile.StartupProfileClassRule; import com.android.tools.r8.experimental.startup.profile.StartupProfileMethodRule; -import com.android.tools.r8.experimental.startup.profile.StartupProfileRule; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexAnnotationDirectory; @@ -82,14 +81,12 @@ virtualFile.classes().size()); LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView, true); StartupIndexedItemCollection indexedItemCollection = new StartupIndexedItemCollection(); - for (StartupProfileRule startupItem : startupProfileForWriting.getRules()) { - startupItem.accept( - startupClass -> - collectStartupItems(startupClass, indexedItemCollection, virtualFileDefinitions), - startupMethod -> - collectStartupItems( - startupMethod, indexedItemCollection, virtualFileDefinitions, rewriter)); - } + startupProfileForWriting.forEachRule( + startupClass -> + collectStartupItems(startupClass, indexedItemCollection, virtualFileDefinitions), + startupMethod -> + collectStartupItems( + startupMethod, indexedItemCollection, virtualFileDefinitions, rewriter)); } private void collectStartupItems(
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupProfile.java index a91c273..f629224 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupProfile.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupProfile.java
@@ -14,8 +14,6 @@ import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.ThrowingConsumer; -import java.util.Collection; -import java.util.Collections; public class EmptyStartupProfile extends StartupProfile { @@ -32,9 +30,15 @@ } @Override + public <E extends Exception> void forEachRule( + ThrowingConsumer<? super StartupProfileRule, E> consumer) { + // Intentionally empty. + } + + @Override public <E1 extends Exception, E2 extends Exception> void forEachRule( - ThrowingConsumer<StartupProfileClassRule, E1> classRuleConsumer, - ThrowingConsumer<StartupProfileMethodRule, E2> methodRuleConsumer) { + ThrowingConsumer<? super StartupProfileClassRule, E1> classRuleConsumer, + ThrowingConsumer<? super StartupProfileMethodRule, E2> methodRuleConsumer) { // Intentionally empty. } @@ -49,11 +53,6 @@ } @Override - public Collection<StartupProfileRule> getRules() { - return Collections.emptyList(); - } - - @Override public boolean isEmpty() { return true; }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java index e3699f7..f45e4c0 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.experimental.startup; -import com.android.tools.r8.experimental.startup.profile.StartupProfileRule; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexProgramClass; @@ -74,11 +73,7 @@ private Set<DexReference> computeStartupItems() { Set<DexReference> startupItems = Sets.newIdentityHashSet(); - for (StartupProfileRule startupItem : startupProfile.getRules()) { - startupItem.accept( - startupClass -> startupItems.add(startupClass.getReference()), - startupMethod -> startupItems.add(startupMethod.getReference())); - } + startupProfile.forEachRule(rule -> startupItems.add(rule.getReference())); return startupItems; } }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java index 0689d7c..747097f 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
@@ -30,6 +30,7 @@ import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.ThrowingConsumer; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -87,7 +88,7 @@ public static StartupProfile merge(Collection<StartupProfile> startupProfiles) { Builder builder = builder(); for (StartupProfile startupProfile : startupProfiles) { - startupProfile.getRules().forEach(builder::addStartupItem); + startupProfile.forEachRule(builder::addStartupItem); } return builder.build(); } @@ -131,9 +132,10 @@ return StartupProfile.merge(startupProfiles); } - public abstract boolean isStartupClass(DexType type); + public abstract <E extends Exception> void forEachRule( + ThrowingConsumer<? super StartupProfileRule, E> consumer) throws E; - public abstract Collection<StartupProfileRule> getRules(); + public abstract boolean isStartupClass(DexType type); public abstract boolean isEmpty();
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java index e5b6b58..ff5b0e9 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
@@ -61,10 +61,13 @@ startupProfileProvider.getStartupProfile(startupProfileBuilder); // Serialize the startup items. StringBuilder resultBuilder = new StringBuilder(); - for (StartupProfileRule startupItem : startupProfileBuilder.build().getRules()) { - startupItem.write(resultBuilder); - resultBuilder.append('\n'); - } + StartupProfile startupProfile = startupProfileBuilder.build(); + startupProfile.forEachRule(rule -> writeRule(rule, resultBuilder)); return resultBuilder.toString(); } + + private static void writeRule(StartupProfileRule rule, Appendable appendable) throws IOException { + rule.write(appendable); + appendable.append('\n'); + } }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/NonEmptyStartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/NonEmptyStartupProfile.java index 4b35a4b..4cec4e9 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/profile/NonEmptyStartupProfile.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/NonEmptyStartupProfile.java
@@ -17,7 +17,6 @@ import com.android.tools.r8.synthesis.SyntheticItems; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.ThrowingConsumer; -import java.util.Collection; import java.util.LinkedHashMap; import java.util.Set; import java.util.function.BiConsumer; @@ -45,11 +44,17 @@ } @Override + public <E extends Exception> void forEachRule( + ThrowingConsumer<? super StartupProfileRule, E> consumer) throws E { + forEachRule(consumer, consumer); + } + + @Override public <E1 extends Exception, E2 extends Exception> void forEachRule( - ThrowingConsumer<StartupProfileClassRule, E1> classRuleConsumer, - ThrowingConsumer<StartupProfileMethodRule, E2> methodRuleConsumer) + ThrowingConsumer<? super StartupProfileClassRule, E1> classRuleConsumer, + ThrowingConsumer<? super StartupProfileMethodRule, E2> methodRuleConsumer) throws E1, E2 { - for (StartupProfileRule rule : getRules()) { + for (StartupProfileRule rule : startupRules.values()) { rule.accept(classRuleConsumer, methodRuleConsumer); } } @@ -65,11 +70,6 @@ } @Override - public Collection<StartupProfileRule> getRules() { - return startupRules.values(); - } - - @Override public boolean isEmpty() { return false; }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileClassRule.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileClassRule.java index c474ff2..8da68cf 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileClassRule.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileClassRule.java
@@ -33,8 +33,8 @@ @Override public <E1 extends Exception, E2 extends Exception> void accept( - ThrowingConsumer<StartupProfileClassRule, E1> classConsumer, - ThrowingConsumer<StartupProfileMethodRule, E2> methodConsumer) + ThrowingConsumer<? super StartupProfileClassRule, E1> classConsumer, + ThrowingConsumer<? super StartupProfileMethodRule, E2> methodConsumer) throws E1 { classConsumer.accept(this); }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileMethodRule.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileMethodRule.java index 0a3a36a..54a7b17 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileMethodRule.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileMethodRule.java
@@ -33,8 +33,8 @@ @Override public <E1 extends Exception, E2 extends Exception> void accept( - ThrowingConsumer<StartupProfileClassRule, E1> classConsumer, - ThrowingConsumer<StartupProfileMethodRule, E2> methodConsumer) + ThrowingConsumer<? super StartupProfileClassRule, E1> classConsumer, + ThrowingConsumer<? super StartupProfileMethodRule, E2> methodConsumer) throws E2 { methodConsumer.accept(this); }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileRule.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileRule.java index 200d364..64dbb2c 100644 --- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileRule.java +++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileRule.java
@@ -14,8 +14,8 @@ implements AbstractProfileRule, Comparable<StartupProfileRule> { public abstract <E1 extends Exception, E2 extends Exception> void accept( - ThrowingConsumer<StartupProfileClassRule, E1> classConsumer, - ThrowingConsumer<StartupProfileMethodRule, E2> methodConsumer) + ThrowingConsumer<? super StartupProfileClassRule, E1> classConsumer, + ThrowingConsumer<? super StartupProfileMethodRule, E2> methodConsumer) throws E1, E2; public abstract <T> T apply(
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index d7a47a1..f9a5279 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -475,9 +475,9 @@ return isInstanceInitializer() || willBeInlinedIntoInstanceInitializer(dexItemFactory); } - public boolean isDefaultInitializer() { + public boolean isDefaultInstanceInitializer() { checkIfObsolete(); - return isInstanceInitializer() && getReference().proto.parameters.isEmpty(); + return isInstanceInitializer() && getParameters().isEmpty(); } public boolean isClassInitializer() { @@ -580,10 +580,10 @@ return false; } else if (accessFlags.isPrivate()) { return getHolderType() == other.getHolderType(); - } else if (accessFlags.isPublic() || accessFlags.isProtected()) { + } else if (accessFlags.isPublic()) { return true; } else { - assert accessFlags.isPackagePrivate(); + assert accessFlags.isPackagePrivate() || accessFlags.isProtected(); return getHolderType().getPackageName().equals(other.getHolderType().getPackageName()); } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 9cdb7f2..5c14069 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -592,6 +592,9 @@ public final DexType javaUtilSetType = createStaticallyKnownType("Ljava/util/Set;"); public final DexType androidAppActivity = createStaticallyKnownType("Landroid/app/Activity;"); + public final DexType androidAppFragment = createStaticallyKnownType("Landroid/app/Fragment;"); + public final DexType androidAppZygotePreload = + createStaticallyKnownType("Landroid/app/ZygotePreload;"); public final DexType androidOsBuildType = createStaticallyKnownType("Landroid/os/Build;"); public final DexType androidOsBuildVersionType = createStaticallyKnownType("Landroid/os/Build$VERSION;");
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java index cf6039c..f2aa7cf 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java +++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.lightir.LirBuilder; import java.util.function.Function; public abstract class ArithmeticBinop extends Binop { @@ -167,4 +168,9 @@ public void buildCf(CfBuilder builder) { builder.add(new CfArithmeticBinop(getCfOpcode(), type), this); } + + @Override + public void buildLir(LirBuilder<Value, ?> builder) { + builder.addArithmeticBinop(getCfOpcode(), type, leftValue(), rightValue()); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java index acc4428..ad8a742 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -17,8 +17,8 @@ import com.android.tools.r8.dex.code.DexIputWide; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndField; import com.android.tools.r8.graph.DexClassAndMethod; -import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.FieldResolutionResult; @@ -142,15 +142,21 @@ return true; } - DexEncodedField encodedField = resolutionResult.getResolvedField(); - assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught."; + DexClassAndField field = resolutionResult.getResolutionPair(); + assert field != null : "NoSuchFieldError (resolution failure) should be caught."; - if (encodedField.type().isAlwaysNull(appViewWithLiveness)) { + if (field.getType().isAlwaysNull(appViewWithLiveness)) { return false; } - return appInfoWithLiveness.isFieldRead(encodedField) - || isStoringObjectWithFinalizer(appViewWithLiveness, encodedField); + if (appView + .getAssumeInfoCollection() + .isMaterializableInAllContexts(appViewWithLiveness, field)) { + return false; + } + + return appInfoWithLiveness.isFieldRead(field.getDefinition()) + || isStoringObjectWithFinalizer(appViewWithLiveness, field.getDefinition()); } // In D8, we always have to assume that the field can be read, and thus have side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java index 7e18e04..98c55cc 100644 --- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -16,8 +16,8 @@ import com.android.tools.r8.dex.code.DexSputWide; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClassAndField; import com.android.tools.r8.graph.DexClassAndMethod; -import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.FieldResolutionResult; @@ -119,22 +119,28 @@ return true; } - DexEncodedField encodedField = resolutionResult.getResolvedField(); - assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught."; + DexClassAndField field = resolutionResult.getResolutionPair(); + assert field != null : "NoSuchFieldError (resolution failure) should be caught."; boolean isDeadProtoExtensionField = appView.withGeneratedExtensionRegistryShrinker( - shrinker -> shrinker.isDeadProtoExtensionField(encodedField.getReference()), false); + shrinker -> shrinker.isDeadProtoExtensionField(field.getReference()), false); if (isDeadProtoExtensionField) { return false; } - if (encodedField.type().isAlwaysNull(appViewWithLiveness)) { + if (field.getType().isAlwaysNull(appViewWithLiveness)) { return false; } - return appInfoWithLiveness.isFieldRead(encodedField) - || isStoringObjectWithFinalizer(appViewWithLiveness, encodedField); + if (appView + .getAssumeInfoCollection() + .isMaterializableInAllContexts(appViewWithLiveness, field)) { + return false; + } + + return appInfoWithLiveness.isFieldRead(field.getDefinition()) + || isStoringObjectWithFinalizer(appViewWithLiveness, field.getDefinition()); } // In D8, we always have to assume that the field can be read, and thus have side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java index 125b53c..c2e13af 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
@@ -86,7 +86,7 @@ continue; } - if (method.getDefinition().isDefaultInitializer() + if (method.getDefinition().isDefaultInstanceInitializer() && appView.hasProguardCompatibilityActions() && appView.getProguardCompatibilityActions().isCompatInstantiated(method.getHolder())) { continue;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java index 4a43131..58f8ac7 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
@@ -28,8 +28,6 @@ public abstract class AbstractGenerateFiles { - private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar"; - // If we increment this api level, we need to verify everything works correctly. static final AndroidApiLevel MAX_TESTED_ANDROID_API_LEVEL = AndroidApiLevel.U; @@ -41,26 +39,33 @@ final Path desugaredLibrarySpecificationPath; final Collection<Path> desugaredLibraryImplementation; final Path outputDirectory; + final Path androidJar; - public AbstractGenerateFiles( - String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory) + AbstractGenerateFiles( + String desugarConfigurationPath, + String desugarImplementationPath, + String outputDirectory, + String androidJarPath) throws Exception { this( Paths.get(desugarConfigurationPath), ImmutableList.of(Paths.get(desugarImplementationPath)), - Paths.get(outputDirectory)); + Paths.get(outputDirectory), + Paths.get(androidJarPath)); } AbstractGenerateFiles( Path desugarConfigurationPath, Collection<Path> desugarImplementationPath, - Path outputDirectory) + Path outputDirectory, + Path androidJar) throws Exception { + assert androidJar != null; this.desugaredLibrarySpecificationPath = desugarConfigurationPath; DesugaredLibrarySpecification specification = readDesugaredLibraryConfiguration(desugarConfigurationPath); - Path androidJarPath = getAndroidJarPath(specification.getRequiredCompilationApiLevel()); - DexApplication app = createApp(androidJarPath, options); + this.androidJar = androidJar; + DexApplication app = createApp(androidJar, options); this.desugaredLibrarySpecification = specification.toMachineSpecification(app, Timing.empty()); this.desugaredLibraryImplementation = desugarImplementationPath; this.outputDirectory = outputDirectory; @@ -69,15 +74,6 @@ } } - static Path getAndroidJarPath(AndroidApiLevel apiLevel) { - String jar = - apiLevel == AndroidApiLevel.MASTER - ? "third_party/android_jar/lib-master/android.jar" - : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel()); - return Paths.get(jar); - } - - private DesugaredLibrarySpecification readDesugaredLibraryConfiguration( Path desugarConfigurationPath) { return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification( @@ -88,10 +84,10 @@ AndroidApiLevel.B.getLevel()); } - private static DexApplication createApp(Path androidLib, InternalOptions options) + private static DexApplication createApp(Path androidJar, InternalOptions options) throws IOException { AndroidApp.Builder builder = AndroidApp.builder(); - AndroidApp inputApp = builder.addLibraryFiles(androidLib).build(); + AndroidApp inputApp = builder.addLibraryFiles(androidJar).build(); ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty()); ExecutorService executorService = ThreadUtils.getExecutorService(options); assert !options.ignoreJavaLibraryOverride; @@ -103,19 +99,43 @@ abstract AndroidApiLevel run() throws Exception; - public static void main(String[] args) throws Exception { - if (args.length == 3) { - new GenerateLintFiles(args[0], args[1], args[2]).run(); - return; + private static String getFallBackAndroidJarPath(AndroidApiLevel apiLevel) { + String jar = + apiLevel == AndroidApiLevel.MASTER + ? "third_party/android_jar/lib-master/android.jar" + : String.format("third_party/android_jar/lib-v%d/android.jar", apiLevel.getLevel()); + Path jarPath = Paths.get(jar); + if (!Files.exists(jarPath)) { + throw new RuntimeException( + "Generate files tools should pass a valid recent android.jar as parameter if used outside" + + " of the r8 repository. Missing file: " + + jarPath); } - if (args.length == 4 && args[0].equals("--generate-api-docs")) { - new GenerateHtmlDoc(args[1], args[2], args[3]).run(); + return jar; + } + + private static String getAndroidJarPath(String[] args, int fullLength) { + return args.length == fullLength + ? args[fullLength - 1] + : getFallBackAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL); + } + + public static void main(String[] args) throws Exception { + if (args[0].equals("--generate-api-docs")) { + if (args.length == 4 || args.length == 5) { + new GenerateHtmlDoc(args[1], args[2], args[3], getAndroidJarPath(args, 5)).run(); + return; + } + } else if (args.length == 3 || args.length == 4) { + new GenerateLintFiles(args[0], args[1], args[2], getAndroidJarPath(args, 4)).run(); return; } throw new RuntimeException( StringUtils.joinLines( "Invalid invocation.", - "Usage: GenerateLineFiles [--generate-api-docs] " - + "<desugar configuration> <desugar implementation> <output directory>")); + "Usage: AbstractGenerateFiles [--generate-api-docs] <desugar configuration> <desugar" + + " implementation> <output directory> [<android jar path for Android " + + MAX_TESTED_ANDROID_API_LEVEL + + " or higher>]")); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java index c62d6b1..fab7228 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -37,9 +37,12 @@ private static final String SUP_4 = "<sup>4</sup>"; public GenerateHtmlDoc( - String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory) + String desugarConfigurationPath, + String desugarImplementationPath, + String outputDirectory, + String androidJarPath) throws Exception { - super(desugarConfigurationPath, desugarImplementationPath, outputDirectory); + super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJarPath); } private static class StringBuilderWithIndent { @@ -554,7 +557,7 @@ new PrintStream(Files.newOutputStream(outputDirectory.resolve(outputFileName))); SupportedClasses supportedClasses = - new SupportedClassesGenerator(options) + new SupportedClassesGenerator(options, androidJar) .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath); // Full classes added.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java index 5bfbe46..e1e72d8 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -55,22 +55,27 @@ private static final boolean FORMAT_WITH_FIELD = true; public static GenerateLintFiles createForTesting( - Path specification, Set<Path> implementation, Path outputDirectory) throws Exception { - return new GenerateLintFiles(specification, implementation, outputDirectory); - } - - public GenerateLintFiles( - String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory) + Path specification, Set<Path> implementation, Path outputDirectory, Path androidJar) throws Exception { - super(desugarConfigurationPath, desugarImplementationPath, outputDirectory); + return new GenerateLintFiles(specification, implementation, outputDirectory, androidJar); } public GenerateLintFiles( + String desugarConfigurationPath, + String desugarImplementationPath, + String outputDirectory, + String androidJarPath) + throws Exception { + super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJarPath); + } + + private GenerateLintFiles( Path desugarConfigurationPath, Collection<Path> desugarImplementationPath, - Path outputDirectory) + Path outputDirectory, + Path androidJar) throws Exception { - super(desugarConfigurationPath, desugarImplementationPath, outputDirectory); + super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJar); } private CfCode buildEmptyThrowingCfCode(DexMethod method) { @@ -256,11 +261,17 @@ AndroidApiLevel compilationLevel = desugaredLibrarySpecification.getRequiredCompilationApiLevel(); SupportedClasses supportedMethods = - new SupportedClassesGenerator(options) + new SupportedClassesGenerator(options, androidJar) .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath); - System.out.println("Generating lint files for compile API " + compilationLevel); + System.out.println( + "Generating lint files for " + + desugaredLibrarySpecification.getIdentifier() + + " (compile API " + + compilationLevel + + ")"); generateLintFiles(compilationLevel, AndroidApiLevel.B, supportedMethods); generateLintFiles(compilationLevel, AndroidApiLevel.L, supportedMethods); + System.out.println(); return compilationLevel; }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java index cb24e59..f384fd7 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
@@ -10,8 +10,9 @@ import com.android.tools.r8.androidapi.ComputedApiLevel; import com.android.tools.r8.dex.ApplicationReader; import com.android.tools.r8.dex.Constants; -import com.android.tools.r8.features.ClassToFeatureSplitMap; +import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; @@ -28,14 +29,12 @@ import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.MethodResolutionResult; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter; -import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification; import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser; import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.ClassAnnotation; import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.FieldAnnotation; import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation; import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification; -import com.android.tools.r8.shaking.MainDexInfo; import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; @@ -46,7 +45,6 @@ import com.google.common.collect.Sets; import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -55,35 +53,28 @@ public class SupportedClassesGenerator { - private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar"; - private final InternalOptions options; + private final DirectMappedDexApplication appForMax; + private final SupportedClasses.Builder builder = SupportedClasses.builder(); - public SupportedClassesGenerator(InternalOptions options) { + public SupportedClassesGenerator(InternalOptions options, Path androidJar) throws IOException { this.options = options; + this.appForMax = createAppForMax(androidJar); } public SupportedClasses run(Collection<Path> desugaredLibraryImplementation, Path specification) throws IOException { - SupportedClasses.Builder builder = SupportedClasses.builder(); // First analyze everything which is supported when desugaring for api 1. - collectSupportedMembersInB(desugaredLibraryImplementation, specification, builder); + collectSupportedMembersInB(desugaredLibraryImplementation, specification); // Second annotate all apis which are partially and/or fully supported. - AndroidApp library = - AndroidApp.builder() - .addProgramFiles(getAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL)) - .build(); - DirectMappedDexApplication appForMax = - new ApplicationReader(library, options, Timing.empty()).read().toDirect(); - annotateMethodsNotOnLatestAndroidJar(appForMax, builder); - annotateParallelMethods(builder); - annotatePartialDesugaringMembers(builder, specification); - annotateClasses(builder, appForMax); + annotateMethodsNotOnLatestAndroidJar(); + annotateParallelMethods(); + annotatePartialDesugaringMembers(specification); + annotateClasses(); return builder.build(); } - private void annotateClasses( - SupportedClasses.Builder builder, DirectMappedDexApplication appForMax) { + private void annotateClasses() { builder.forEachClassFieldsAndMethods( (clazz, fields, methods) -> { ClassAnnotation classAnnotation = builder.getClassAnnotation(clazz.type); @@ -123,34 +114,27 @@ return fullySupported; } - private void annotatePartialDesugaringMembers( - SupportedClasses.Builder builder, Path specification) throws IOException { - for (int api = AndroidApiLevel.K.getLevel(); + private void annotatePartialDesugaringMembers(Path specification) throws IOException { + // The first difference should be at 18 so we're safe starting at J and not B. + for (int api = AndroidApiLevel.J.getLevel(); api <= MAX_TESTED_ANDROID_API_LEVEL.getLevel(); api++) { - if (api == 20) { - // Missing android.jar. - continue; - } AndroidApiLevel androidApiLevel = AndroidApiLevel.getAndroidApiLevel(api); MachineDesugaredLibrarySpecification machineSpecification = getMachineSpecification(androidApiLevel, specification); options.setMinApiLevel(androidApiLevel); options.resetDesugaredLibrarySpecificationForTesting(); options.setDesugaredLibrarySpecification(machineSpecification); - AndroidApp library = - AndroidApp.builder().addProgramFiles(getAndroidJarPath(androidApiLevel)).build(); - DirectMappedDexApplication dexApplication = - new ApplicationReader(library, options, Timing.empty()).read().toDirect(); - AppInfoWithClassHierarchy appInfo = - AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy( - dexApplication, - ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(), - MainDexInfo.none(), - GlobalSyntheticsStrategy.forNonSynthesizing()); + AppInfo initialAppInfo = + AppInfo.createInitialAppInfo(appForMax, GlobalSyntheticsStrategy.forNonSynthesizing()); + AppView<?> appView = + AppView.createForD8(initialAppInfo, options.getTypeRewriter(), Timing.empty()); + AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring(); + + // This should depend only on machine specification and min api. List<DexMethod> backports = - BackportedMethodRewriter.generateListOfBackportedMethods(dexApplication, options); + BackportedMethodRewriter.generateListOfBackportedMethods(appForMax, options); int finalApi = api; builder.forEachClassAndMethod( @@ -170,13 +154,31 @@ // interface is supported. return; } + // This is not supported through desugared library. We look-up to see if it is + // supported at the given min-api level by the library. MethodResolutionResult methodResolutionResult = appInfo.resolveMethod( dexMethod, appInfo .contextIndependentDefinitionFor(dexMethod.getHolderType()) .isInterface()); - if (methodResolutionResult.isFailedResolution()) { + if (methodResolutionResult.isSingleResolution()) { + ComputedApiLevel computedApiLevel = + appView + .apiLevelCompute() + .computeApiLevelForLibraryReferenceIgnoringDesugaredLibrary( + methodResolutionResult.getResolvedMethod().getReference(), + ComputedApiLevel.unknown()); + if (!computedApiLevel.isKnownApiLevel()) { + throw new RuntimeException( + "API database does not recognize the method " + + encodedMethod.getReference().toSourceString()); + } + if (finalApi < computedApiLevel.asKnownApiLevel().getApiLevel().getLevel()) { + builder.annotateMethod(dexMethod, MethodAnnotation.createMissingInMinApi(finalApi)); + } + } else { + assert methodResolutionResult.isFailedResolution(); builder.annotateMethod(dexMethod, MethodAnnotation.createMissingInMinApi(finalApi)); } }); @@ -192,7 +194,24 @@ } FieldResolutionResult fieldResolutionResult = appInfo.resolveField(encodedField.getReference()); - if (fieldResolutionResult.isFailedResolution()) { + if (fieldResolutionResult.isSingleFieldResolutionResult()) { + DexEncodedField resolvedField = fieldResolutionResult.getResolvedField(); + ComputedApiLevel computedApiLevel = + appView + .apiLevelCompute() + .computeApiLevelForLibraryReferenceIgnoringDesugaredLibrary( + resolvedField.getReference(), ComputedApiLevel.unknown()); + if (!computedApiLevel.isKnownApiLevel()) { + throw new RuntimeException( + "API database does not recognize the field " + + encodedField.getReference().toSourceString()); + } + if (finalApi < computedApiLevel.asKnownApiLevel().getApiLevel().getLevel()) { + builder.annotateField( + encodedField.getReference(), FieldAnnotation.createMissingInMinApi(finalApi)); + } + } else { + assert fieldResolutionResult.isFailedResolution(); builder.annotateField( encodedField.getReference(), FieldAnnotation.createMissingInMinApi(finalApi)); } @@ -200,14 +219,13 @@ } } - private void annotateParallelMethods(SupportedClasses.Builder builder) { + private void annotateParallelMethods() { for (DexMethod parallelMethod : getParallelMethods()) { builder.annotateMethodIfPresent(parallelMethod, MethodAnnotation.getParallelStreamMethod()); } } - private void annotateMethodsNotOnLatestAndroidJar( - DirectMappedDexApplication appForMax, SupportedClasses.Builder builder) { + private void annotateMethodsNotOnLatestAndroidJar() { builder.forEachClassAndMethod( (clazz, method) -> { DexClass dexClass = appForMax.definitionFor(clazz.type); @@ -219,19 +237,8 @@ }); } - static Path getAndroidJarPath(AndroidApiLevel apiLevel) { - String jar = - apiLevel == AndroidApiLevel.MASTER - ? "third_party/android_jar/lib-master/android.jar" - : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel()); - return Paths.get(jar); - } - private void collectSupportedMembersInB( - Collection<Path> desugaredLibraryImplementation, - Path specification, - SupportedClasses.Builder builder) - throws IOException { + Collection<Path> desugaredLibraryImplementation, Path specification) throws IOException { MachineDesugaredLibrarySpecification machineSpecification = getMachineSpecification(AndroidApiLevel.B, specification); @@ -245,22 +252,8 @@ DirectMappedDexApplication implementationApplication = new ApplicationReader(implementation, options, Timing.empty()).read().toDirect(); - AndroidApp library = - AndroidApp.builder() - .addLibraryFiles(getAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL)) - .build(); - DirectMappedDexApplication amendedAppForMax = - new ApplicationReader(library, options, Timing.empty()).read().toDirect(); - List<DexMethod> backports = - BackportedMethodRewriter.generateListOfBackportedMethods(amendedAppForMax, options); - - DesugaredLibraryAmender.run( - machineSpecification.getAmendLibraryMethods(), - machineSpecification.getAmendLibraryFields(), - amendedAppForMax, - options.reporter, - ComputedApiLevel.unknown()); + BackportedMethodRewriter.generateListOfBackportedMethods(appForMax, options); for (DexProgramClass clazz : implementationApplication.classes()) { // All emulated interfaces static and default methods are supported. @@ -285,20 +278,20 @@ } builder.addSupportedMethod(clazz, method); } - addBackports(clazz, backports, builder, amendedAppForMax); + addBackports(clazz, backports); builder.annotateClass(clazz.type, ClassAnnotation.getAdditionnalMembersOnClass()); } else { // All methods in maintained or rewritten classes are supported. if ((clazz.accessFlags.isPublic() || clazz.accessFlags.isProtected()) && machineSpecification.isContextTypeMaintainedOrRewritten(clazz.type) - && amendedAppForMax.definitionFor(clazz.type) != null) { + && appForMax.definitionFor(clazz.type) != null) { for (DexEncodedMethod method : clazz.methods()) { if (!method.isPublic() && !method.isProtectedMethod()) { continue; } builder.addSupportedMethod(clazz, method); } - addBackports(clazz, backports, builder, amendedAppForMax); + addBackports(clazz, backports); for (DexEncodedField field : clazz.fields()) { if (!field.isPublic() && !field.isProtected()) { continue; @@ -321,7 +314,7 @@ return; } } - dexClass = amendedAppForMax.definitionFor(method.getHolderType()); + dexClass = appForMax.definitionFor(method.getHolderType()); DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method); assert dexEncodedMethod != null; builder.addSupportedMethod(dexClass, dexEncodedMethod); @@ -342,7 +335,7 @@ return; } } - dexClass = amendedAppForMax.definitionFor(field.getHolderType()); + dexClass = appForMax.definitionFor(field.getHolderType()); DexEncodedField dexEncodedField = dexClass.lookupField(field); assert dexEncodedField != null; builder.addSupportedField(dexClass, dexEncodedField); @@ -350,14 +343,10 @@ }); } - private void addBackports( - DexProgramClass clazz, - List<DexMethod> backports, - SupportedClasses.Builder builder, - DirectMappedDexApplication amendedAppForMax) { + private void addBackports(DexProgramClass clazz, List<DexMethod> backports) { for (DexMethod backport : backports) { if (clazz.type == backport.getHolderType()) { - DexClass maxClass = amendedAppForMax.definitionFor(clazz.type); + DexClass maxClass = appForMax.definitionFor(clazz.type); DexEncodedMethod dexEncodedMethod = maxClass.lookupMethod(backport); // Some backports are not in amendedAppForMax, such as Stream#ofNullable and recent ones // introduced in U. @@ -402,22 +391,19 @@ options.reporter, false, api.getLevel()); - Path androidJarPath = getAndroidJarPath(librarySpecification.getRequiredCompilationApiLevel()); - DexApplication app = createLoadingApp(androidJarPath, options); - return librarySpecification.toMachineSpecification(app, Timing.empty()); + return librarySpecification.toMachineSpecification(appForMax, Timing.empty()); } - private DexApplication createLoadingApp(Path androidLib, InternalOptions options) - throws IOException { + private DirectMappedDexApplication createAppForMax(Path androidJar) throws IOException { AndroidApp.Builder builder = AndroidApp.builder(); - AndroidApp inputApp = builder.addLibraryFiles(androidLib).build(); + AndroidApp inputApp = builder.addLibraryFiles(androidJar).build(); ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty()); ExecutorService executorService = ThreadUtils.getExecutorService(options); assert !options.ignoreJavaLibraryOverride; options.ignoreJavaLibraryOverride = true; - DexApplication loadingApp = applicationReader.read(executorService); + DexApplication appForMax = applicationReader.read(executorService); options.ignoreJavaLibraryOverride = false; - return loadingApp; + return appForMax.toDirect(); } private Set<DexMethod> getParallelMethods() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java index 587a78c..1810750 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
@@ -342,19 +342,23 @@ private void replaceInstancePutByNullCheckIfNeverRead( IRCode code, InstructionListIterator iterator, InstancePut current) { - DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField(); - if (field == null || field.isStatic()) { + DexClassAndField field = appView.appInfo().resolveField(current.getField()).getResolutionPair(); + if (field == null || field.getAccessFlags().isStatic()) { return; } // If the field is read, we can't remove the instance-put unless the value of the field is known // to be null (in which case the instance-put is a no-op because it assigns the field the same // value as its default value). - if (!field.type().isAlwaysNull(appView) && appView.appInfo().isFieldRead(field)) { + if (field.getType().isAlwaysNull(appView) + || !appView.appInfo().isFieldRead(field.getDefinition())) { + iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context()); return; } - iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context()); + if (appView.getAssumeInfoCollection().isMaterializableInAllContexts(appView, field)) { + iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context()); + } } @Override @@ -364,19 +368,24 @@ private void replaceStaticPutByInitClassIfNeverRead( IRCode code, InstructionListIterator iterator, StaticPut current) { - DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField(); - if (field == null || !field.isStatic()) { + DexClassAndField field = appView.appInfo().resolveField(current.getField()).getResolutionPair(); + if (field == null || !field.getAccessFlags().isStatic()) { return; } // If the field is read, we can't remove the static-put unless the value of the field is known // to be null (in which case the static-put is a no-op because it assigns the field the same // value as its default value). - if (!field.type().isAlwaysNull(appView) && appView.appInfo().isFieldRead(field)) { + if (field.getType().isAlwaysNull(appView) + || !appView.appInfo().isFieldRead(field.getDefinition())) { + iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible( + appView, code, field.getHolderType()); return; } - iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible( - appView, code, field.getHolderType()); + if (appView.getAssumeInfoCollection().isMaterializableInAllContexts(appView, field)) { + iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible( + appView, code, field.getHolderType()); + } } }
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java index 5d34483..dc174ff 100644 --- a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java +++ b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
@@ -17,6 +17,10 @@ return value & 0xFF; } + private static int truncateToU1(long value) { + return (int) value & 0xFF; + } + public static int ensureU1(int value) { assert isU1(value); return truncateToU1(value); @@ -47,6 +51,35 @@ return value; } + public static int longEncodingSize(long value) { + return 8; + } + + public static void writeEncodedLong(long value, ByteWriter writer) { + assert 8 == longEncodingSize(value); + writer.put(truncateToU1(value >> 56)); + writer.put(truncateToU1(value >> 48)); + writer.put(truncateToU1(value >> 40)); + writer.put(truncateToU1(value >> 32)); + writer.put(truncateToU1(value >> 24)); + writer.put(truncateToU1(value >> 16)); + writer.put(truncateToU1(value >> 8)); + writer.put(truncateToU1(value)); + } + + public static long readEncodedLong(ByteIterator iterator) { + assert 8 == longEncodingSize(0); + long value = ((long) truncateToU1(iterator.nextByte())) << 56; + value |= ((long) truncateToU1(iterator.nextByte())) << 48; + value |= ((long) truncateToU1(iterator.nextByte())) << 40; + value |= ((long) truncateToU1(iterator.nextByte())) << 32; + value |= ((long) truncateToU1(iterator.nextByte())) << 24; + value |= ((long) truncateToU1(iterator.nextByte())) << 16; + value |= ((long) truncateToU1(iterator.nextByte())) << 8; + value |= truncateToU1(iterator.nextByte()); + return value; + } + public static boolean isU2(int value) { return (value >= 0) && (value <= 0xFFFF); }
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java index ebaa39c..78e84db 100644 --- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java +++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeElement; +import com.android.tools.r8.ir.code.Add; import com.android.tools.r8.ir.code.Argument; import com.android.tools.r8.ir.code.ArrayLength; import com.android.tools.r8.ir.code.BasicBlock; @@ -36,14 +37,17 @@ import com.android.tools.r8.ir.code.InvokeSuper; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.MoveException; +import com.android.tools.r8.ir.code.Mul; import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.SyntheticPosition; +import com.android.tools.r8.ir.code.Rem; import com.android.tools.r8.ir.code.Return; import com.android.tools.r8.ir.code.StaticGet; +import com.android.tools.r8.ir.code.Sub; import com.android.tools.r8.ir.code.Throw; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; @@ -334,10 +338,33 @@ } @Override - public void onDivInt(EV leftValueIndex, EV rightValueIndex) { + public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) { Value dest = getOutValueForNextInstruction(TypeElement.getInt()); - addInstruction( - new Div(NumericType.INT, dest, getValue(leftValueIndex), getValue(rightValueIndex))); + addInstruction(new Add(type, dest, getValue(leftValueIndex), getValue(rightValueIndex))); + } + + @Override + public void onSub(NumericType type, EV leftValueIndex, EV rightValueIndex) { + Value dest = getOutValueForNextInstruction(TypeElement.getInt()); + addInstruction(new Sub(type, dest, getValue(leftValueIndex), getValue(rightValueIndex))); + } + + @Override + public void onMul(NumericType type, EV leftValueIndex, EV rightValueIndex) { + Value dest = getOutValueForNextInstruction(TypeElement.getInt()); + addInstruction(new Mul(type, dest, getValue(leftValueIndex), getValue(rightValueIndex))); + } + + @Override + public void onDiv(NumericType type, EV leftValueIndex, EV rightValueIndex) { + Value dest = getOutValueForNextInstruction(TypeElement.getInt()); + addInstruction(new Div(type, dest, getValue(leftValueIndex), getValue(rightValueIndex))); + } + + @Override + public void onRem(NumericType type, EV leftValueIndex, EV rightValueIndex) { + Value dest = getOutValueForNextInstruction(TypeElement.getInt()); + addInstruction(new Rem(type, dest, getValue(leftValueIndex), getValue(rightValueIndex))); } @Override
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java index 37806dd..024d6d7 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java +++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.lightir; +import com.android.tools.r8.cf.code.CfArithmeticBinop; +import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode; import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.DebugLocalInfo; @@ -42,6 +44,11 @@ /** Builder for constructing LIR code from IR. */ public class LirBuilder<V, EV> { + private static final int FLOAT_0 = Float.floatToRawIntBits(0); + private static final int FLOAT_1 = Float.floatToRawIntBits(1); + private static final int FLOAT_2 = Float.floatToRawIntBits(2); + private static final long DOUBLE_0 = Double.doubleToRawLongBits(0); + private static final long DOUBLE_1 = Double.doubleToRawLongBits(1); private final DexItemFactory factory; private final ByteArrayWriter byteWriter = new ByteArrayWriter(); @@ -254,6 +261,48 @@ return this; } + public LirBuilder<V, EV> addConstFloat(int value) { + if (value == FLOAT_0) { + return addNoOperandInstruction(LirOpcodes.FCONST_0); + } + if (value == FLOAT_1) { + return addNoOperandInstruction(LirOpcodes.FCONST_1); + } + if (value == FLOAT_2) { + return addNoOperandInstruction(LirOpcodes.FCONST_2); + } + advanceInstructionState(); + writer.writeInstruction(LirOpcodes.FCONST, ByteUtils.intEncodingSize(value)); + ByteUtils.writeEncodedInt(value, writer::writeOperand); + return this; + } + + public LirBuilder<V, EV> addConstLong(long value) { + if (value == 0) { + return addNoOperandInstruction(LirOpcodes.LCONST_0); + } + if (value == 1) { + return addNoOperandInstruction(LirOpcodes.LCONST_1); + } + advanceInstructionState(); + writer.writeInstruction(LirOpcodes.LCONST, ByteUtils.longEncodingSize(value)); + ByteUtils.writeEncodedLong(value, writer::writeOperand); + return this; + } + + public LirBuilder<V, EV> addConstDouble(long value) { + if (value == DOUBLE_0) { + return addNoOperandInstruction(LirOpcodes.DCONST_0); + } + if (value == DOUBLE_1) { + return addNoOperandInstruction(LirOpcodes.DCONST_1); + } + advanceInstructionState(); + writer.writeInstruction(LirOpcodes.DCONST, ByteUtils.longEncodingSize(value)); + ByteUtils.writeEncodedLong(value, writer::writeOperand); + return this; + } + public LirBuilder<V, EV> addConstNumber(ValueType type, long value) { switch (type) { case OBJECT: @@ -261,8 +310,11 @@ case INT: return addConstInt((int) value); case FLOAT: + return addConstFloat((int) value); case LONG: + return addConstLong(value); case DOUBLE: + return addConstDouble(value); default: throw new Unimplemented(); } @@ -495,4 +547,13 @@ public LirBuilder<V, EV> addCmp(NumericType type, Bias bias, V leftValue, V rightValue) { return addTwoValueInstruction(getCmpOpcode(type, bias), leftValue, rightValue); } + + public LirBuilder<V, EV> addArithmeticBinop( + Opcode binop, NumericType type, V leftValue, V rightValue) { + // The LIR and CF opcodes are the same values, check that the two endpoints match. + assert LirOpcodes.IADD == CfArithmeticBinop.getAsmOpcode(Opcode.Add, NumericType.INT); + assert LirOpcodes.DREM == CfArithmeticBinop.getAsmOpcode(Opcode.Rem, NumericType.DOUBLE); + int opcode = CfArithmeticBinop.getAsmOpcode(binop, type); + return addTwoValueInstruction(opcode, leftValue, rightValue); + } }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirInstructionView.java b/src/main/java/com/android/tools/r8/lightir/LirInstructionView.java index 3eef40d..546da90 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirInstructionView.java +++ b/src/main/java/com/android/tools/r8/lightir/LirInstructionView.java
@@ -30,6 +30,9 @@ /** Get the next operand as an encoded integer */ int getNextIntegerOperand(); + /** Get the next operand as an encoded long */ + long getNextLongOperand(); + /** Get the next operand as a constant-pool index. */ int getNextConstantOperand();
diff --git a/src/main/java/com/android/tools/r8/lightir/LirIterator.java b/src/main/java/com/android/tools/r8/lightir/LirIterator.java index dd29c93..84eaee6 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirIterator.java +++ b/src/main/java/com/android/tools/r8/lightir/LirIterator.java
@@ -88,6 +88,12 @@ } @Override + public long getNextLongOperand() { + assert hasMoreOperands(); + return u8(); + } + + @Override public int getNextConstantOperand() { return getNextIntegerOperand(); } @@ -116,4 +122,9 @@ currentByteIndex += 4; return ByteUtils.readEncodedInt(iterator); } + + private long u8() { + currentByteIndex += 8; + return ByteUtils.readEncodedLong(iterator); + } }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java index 96cb7ee..925e8af 100644 --- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java +++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -62,10 +62,82 @@ onConstNumber(NumericType.INT, value); } + public void onConstFloat(int value) { + onConstNumber(NumericType.FLOAT, value); + } + + public void onConstLong(long value) { + onConstNumber(NumericType.LONG, value); + } + + public void onConstDouble(long value) { + onConstNumber(NumericType.DOUBLE, value); + } + public void onConstString(DexString string) { onInstruction(); } + public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) { + onInstruction(); + } + + public void onAddInt(EV leftValueIndex, EV rightValueIndex) { + onAdd(NumericType.INT, leftValueIndex, rightValueIndex); + } + + public void onAddLong(EV leftValueIndex, EV rightValueIndex) { + onAdd(NumericType.LONG, leftValueIndex, rightValueIndex); + } + + public void onAddFloat(EV leftValueIndex, EV rightValueIndex) { + onAdd(NumericType.FLOAT, leftValueIndex, rightValueIndex); + } + + public void onAddDouble(EV leftValueIndex, EV rightValueIndex) { + onAdd(NumericType.DOUBLE, leftValueIndex, rightValueIndex); + } + + public void onSub(NumericType type, EV leftValueIndex, EV rightValueIndex) { + onInstruction(); + } + + public void onSubInt(EV leftValueIndex, EV rightValueIndex) { + onSub(NumericType.INT, leftValueIndex, rightValueIndex); + } + + public void onSubLong(EV leftValueIndex, EV rightValueIndex) { + onSub(NumericType.LONG, leftValueIndex, rightValueIndex); + } + + public void onSubFloat(EV leftValueIndex, EV rightValueIndex) { + onSub(NumericType.FLOAT, leftValueIndex, rightValueIndex); + } + + public void onSubDouble(EV leftValueIndex, EV rightValueIndex) { + onSub(NumericType.DOUBLE, leftValueIndex, rightValueIndex); + } + + public void onMul(NumericType type, EV leftValueIndex, EV rightValueIndex) { + onInstruction(); + } + + public void onMulInt(EV leftValueIndex, EV rightValueIndex) { + onMul(NumericType.INT, leftValueIndex, rightValueIndex); + } + + public void onMulLong(EV leftValueIndex, EV rightValueIndex) { + onMul(NumericType.LONG, leftValueIndex, rightValueIndex); + } + + public void onMulFloat(EV leftValueIndex, EV rightValueIndex) { + onMul(NumericType.FLOAT, leftValueIndex, rightValueIndex); + } + + public void onMulDouble(EV leftValueIndex, EV rightValueIndex) { + onMul(NumericType.DOUBLE, leftValueIndex, rightValueIndex); + } + public void onDiv(NumericType type, EV leftValueIndex, EV rightValueIndex) { onInstruction(); } @@ -74,6 +146,38 @@ onDiv(NumericType.INT, leftValueIndex, rightValueIndex); } + public void onDivLong(EV leftValueIndex, EV rightValueIndex) { + onDiv(NumericType.LONG, leftValueIndex, rightValueIndex); + } + + public void onDivFloat(EV leftValueIndex, EV rightValueIndex) { + onDiv(NumericType.FLOAT, leftValueIndex, rightValueIndex); + } + + public void onDivDouble(EV leftValueIndex, EV rightValueIndex) { + onDiv(NumericType.DOUBLE, leftValueIndex, rightValueIndex); + } + + public void onRem(NumericType type, EV leftValueIndex, EV rightValueIndex) { + onInstruction(); + } + + public void onRemInt(EV leftValueIndex, EV rightValueIndex) { + onRem(NumericType.INT, leftValueIndex, rightValueIndex); + } + + public void onRemLong(EV leftValueIndex, EV rightValueIndex) { + onRem(NumericType.LONG, leftValueIndex, rightValueIndex); + } + + public void onRemFloat(EV leftValueIndex, EV rightValueIndex) { + onRem(NumericType.FLOAT, leftValueIndex, rightValueIndex); + } + + public void onRemDouble(EV leftValueIndex, EV rightValueIndex) { + onRem(NumericType.DOUBLE, leftValueIndex, rightValueIndex); + } + public void onIf(IfType ifKind, int blockIndex, EV valueIndex) { onInstruction(); } @@ -197,6 +301,136 @@ onConstInt(value); return; } + case LirOpcodes.FCONST_0: + case LirOpcodes.FCONST_1: + case LirOpcodes.FCONST_2: + { + float value = opcode - LirOpcodes.FCONST_0; + onConstFloat(Float.floatToRawIntBits(value)); + return; + } + case LirOpcodes.FCONST: + { + int value = view.getNextIntegerOperand(); + onConstFloat(value); + return; + } + case LirOpcodes.LCONST_0: + { + onConstLong(0); + return; + } + case LirOpcodes.LCONST_1: + { + onConstLong(1); + return; + } + case LirOpcodes.LCONST: + { + long value = view.getNextLongOperand(); + onConstLong(value); + return; + } + case LirOpcodes.DCONST_0: + { + onConstDouble(Double.doubleToRawLongBits(0)); + return; + } + case LirOpcodes.DCONST_1: + { + onConstDouble(Double.doubleToRawLongBits(1)); + return; + } + case LirOpcodes.DCONST: + { + long value = view.getNextLongOperand(); + onConstDouble(value); + return; + } + case LirOpcodes.IADD: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onAddInt(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.LADD: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onAddLong(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.FADD: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onAddFloat(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.DADD: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onAddDouble(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.ISUB: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onSubInt(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.LSUB: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onSubLong(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.FSUB: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onSubFloat(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.DSUB: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onSubDouble(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.IMUL: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onMulInt(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.LMUL: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onMulLong(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.FMUL: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onMulFloat(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.DMUL: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onMulDouble(leftValueIndex, rightValueIndex); + return; + } case LirOpcodes.IDIV: { EV leftValueIndex = getNextValueOperand(view); @@ -204,6 +438,55 @@ onDivInt(leftValueIndex, rightValueIndex); return; } + case LirOpcodes.LDIV: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onDivLong(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.FDIV: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onDivFloat(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.DDIV: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onDivDouble(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.IREM: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onRemInt(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.LREM: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onRemLong(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.FREM: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onRemFloat(leftValueIndex, rightValueIndex); + return; + } + case LirOpcodes.DREM: + { + EV leftValueIndex = getNextValueOperand(view); + EV rightValueIndex = getNextValueOperand(view); + onRemDouble(leftValueIndex, rightValueIndex); + return; + } case LirOpcodes.IFNE: { int blockIndex = view.getNextBlockOperand();
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java index 5899d87..ed80ba9 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
@@ -45,6 +45,12 @@ <T extends Throwable> void forAllFieldNaming( ThrowingConsumer<MemberNaming, T> consumer) throws T; + <T extends Throwable> void forAllFieldNamingSorted(ThrowingConsumer<MemberNaming, T> consumer) + throws T; + <T extends Throwable> void forAllMethodNaming( ThrowingConsumer<MemberNaming, T> consumer) throws T; + + <T extends Throwable> void forAllMethodNamingSorted(ThrowingConsumer<MemberNaming, T> consumer) + throws T; }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java index afc7132..a63f263 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind; import com.android.tools.r8.naming.mappinginformation.MappingInformation; import com.android.tools.r8.position.Position; +import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.ThrowingConsumer; import com.google.common.base.Objects; @@ -165,6 +166,14 @@ } @Override + public <T extends Throwable> void forAllFieldNamingSorted( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : CollectionUtils.sort(fieldMembers.values())) { + consumer.accept(naming); + } + } + + @Override public <T extends Throwable> void forAllMethodNaming( ThrowingConsumer<MemberNaming, T> consumer) throws T { for (MemberNaming naming : methodMembers.values()) { @@ -173,6 +182,14 @@ } @Override + public <T extends Throwable> void forAllMethodNamingSorted( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : CollectionUtils.sort(methodMembers.values())) { + consumer.accept(naming); + } + } + + @Override public MemberNaming lookup(Signature renamedSignature) { // As the key is inverted, this looks a lot like // {@link ClassNamingForNameMapper#lookupByOriginalSignature}.
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java index 2a1408a..5a71db9 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation; import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation; import com.android.tools.r8.utils.ChainableStringConsumer; +import com.android.tools.r8.utils.CollectionUtils; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.ThrowingConsumer; import com.google.common.collect.ImmutableList; @@ -424,6 +425,14 @@ } } + @Override + public <T extends Throwable> void forAllFieldNamingSorted( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : CollectionUtils.sort(fieldMembers.values())) { + consumer.accept(naming); + } + } + public Collection<MemberNaming> allFieldNamings() { return fieldMembers.values(); } @@ -460,6 +469,14 @@ } } + @Override + public <T extends Throwable> void forAllMethodNamingSorted( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : CollectionUtils.sort(methodMembers.values())) { + consumer.accept(naming); + } + } + public Collection<MemberNaming> allMethodNamings() { return methodMembers.values(); } @@ -471,7 +488,7 @@ additionalMappingInfo.forEach(info -> consumer.accept("# " + info.serialize()).accept("\n")); // Print field member namings. - forAllFieldNaming(m -> consumer.accept(" ").accept(m.toString()).accept("\n")); + forAllFieldNamingSorted(m -> consumer.accept(" ").accept(m.toString()).accept("\n")); // Sort MappedRanges by sequence number to restore construction order (original Proguard-map // input).
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java index a26d531..d1bfb78 100644 --- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java +++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; @@ -42,7 +43,7 @@ * * <p>This includes the signature and the original name. */ -public class MemberNaming implements MappingWithResidualInfo { +public class MemberNaming implements MappingWithResidualInfo, Comparable<MemberNaming> { private static final List<ReferentialMappingInformation> EMPTY_MAPPING_INFORMATION = Collections.emptyList(); @@ -172,6 +173,15 @@ return additionalMappingInformation; } + @Override + public int compareTo(MemberNaming other) { + return Comparator.comparing(MemberNaming::isFieldNaming) + .thenComparing(MemberNaming::getRenamedName) + .thenComparing(MemberNaming::getOriginalName) + .thenComparing(memberNaming -> getOriginalSignature().toString()) + .compare(this, other); + } + public abstract static class Signature { public final String name;
diff --git a/src/main/java/com/android/tools/r8/naming/MultiProguardMapConsumer.java b/src/main/java/com/android/tools/r8/naming/MultiProguardMapConsumer.java new file mode 100644 index 0000000..5901fd3 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/MultiProguardMapConsumer.java
@@ -0,0 +1,47 @@ +// Copyright (c) 2023, 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.naming; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.ProguardMapConsumer; +import java.util.ArrayList; +import java.util.List; + +public class MultiProguardMapConsumer extends ProguardMapConsumer { + + private final List<ProguardMapConsumer> proguardMapConsumers; + + public MultiProguardMapConsumer(List<ProguardMapConsumer> proguardMapConsumers) { + this.proguardMapConsumers = proguardMapConsumers; + } + + @Override + public void finished(DiagnosticsHandler handler) { + proguardMapConsumers.forEach(consumer -> consumer.finished(handler)); + } + + @Override + public void accept(ProguardMapMarkerInfo markerInfo, ClassNameMapper classNameMapper) { + proguardMapConsumers.forEach(consumer -> consumer.accept(markerInfo, classNameMapper)); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final List<ProguardMapConsumer> proguardMapConsumers = new ArrayList<>(); + + public Builder addProguardMapConsumer(ProguardMapConsumer consumer) { + proguardMapConsumers.add(consumer); + return this; + } + + public MultiProguardMapConsumer build() { + return new MultiProguardMapConsumer(proguardMapConsumers); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapChecker.java b/src/main/java/com/android/tools/r8/naming/ProguardMapChecker.java new file mode 100644 index 0000000..be80509 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapChecker.java
@@ -0,0 +1,129 @@ +// Copyright (c) 2023, 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.naming; + +import static com.android.tools.r8.naming.ProguardMapMarkerInfo.MARKER_KEY_PG_MAP_HASH; +import static com.android.tools.r8.naming.ProguardMapMarkerInfo.SHA_256_KEY; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.utils.InternalOptions; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class ProguardMapChecker implements StringConsumer { + + private final StringConsumer inner; + private final StringBuilder contents = new StringBuilder(); + + ProguardMapChecker(StringConsumer inner) { + if (!InternalOptions.assertionsEnabled()) { + // Make sure we never get here without assertions enabled. + throw new Unreachable(); + } + this.inner = inner; + } + + @Override + public void accept(String string, DiagnosticsHandler handler) { + inner.accept(string, handler); + contents.append(string); + } + + @Override + public void finished(DiagnosticsHandler handler) { + inner.finished(handler); + String stringContent = contents.toString(); + assert validateProguardMapParses(stringContent); + assert validateProguardMapHash(stringContent).isOk(); + } + + private static boolean validateProguardMapParses(String content) { + try { + ClassNameMapper.mapperFromString(content); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + return true; + } + + public static class VerifyMappingFileHashResult { + + private final boolean error; + private final String message; + + public static VerifyMappingFileHashResult createOk() { + return new VerifyMappingFileHashResult(false, null); + } + + public static VerifyMappingFileHashResult createInfo(String message) { + return new VerifyMappingFileHashResult(false, message); + } + + public static VerifyMappingFileHashResult createError(String message) { + return new VerifyMappingFileHashResult(true, message); + } + + private VerifyMappingFileHashResult(boolean error, String message) { + this.error = error; + this.message = message; + } + + public boolean isOk() { + return !error && message == null; + } + + public boolean isError() { + return error; + } + + public String getMessage() { + assert message != null; + return message; + } + } + + public static VerifyMappingFileHashResult validateProguardMapHash(String content) { + int lineEnd = -1; + while (true) { + int lineStart = lineEnd + 1; + lineEnd = content.indexOf('\n', lineStart); + if (lineEnd < 0) { + return VerifyMappingFileHashResult.createInfo("Failure to find map hash"); + } + String line = content.substring(lineStart, lineEnd).trim(); + if (line.isEmpty()) { + // Ignore empty lines in the header. + continue; + } + if (line.charAt(0) != '#') { + // At the first non-empty non-comment line we assume that the file has no hash marker. + return VerifyMappingFileHashResult.createInfo("Failure to find map hash in header"); + } + String headerLine = line.substring(1).trim(); + if (headerLine.startsWith(MARKER_KEY_PG_MAP_HASH)) { + int shaIndex = headerLine.indexOf(SHA_256_KEY + " ", MARKER_KEY_PG_MAP_HASH.length()); + if (shaIndex < 0) { + return VerifyMappingFileHashResult.createError( + "Unknown map hash function: '" + headerLine + "'"); + } + String headerHash = headerLine.substring(shaIndex + SHA_256_KEY.length()).trim(); + // We are on the hash line. Everything past this line is the hashed content. + Hasher hasher = Hashing.sha256().newHasher(); + String hashedContent = content.substring(lineEnd + 1); + hasher.putString(hashedContent, StandardCharsets.UTF_8); + String computedHash = hasher.hash().toString(); + return headerHash.equals(computedHash) + ? VerifyMappingFileHashResult.createOk() + : VerifyMappingFileHashResult.createError( + "Mismatching map hash: '" + headerHash + "' != '" + computedHash + "'"); + } + } + } +}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMarkerInfo.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMarkerInfo.java new file mode 100644 index 0000000..8394ecf --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMarkerInfo.java
@@ -0,0 +1,112 @@ +// Copyright (c) 2023, 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.naming; + +import com.android.tools.r8.Version; +import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.VersionProperties; +import java.util.ArrayList; +import java.util.List; + +public class ProguardMapMarkerInfo { + + private static final String MARKER_KEY_COMPILER = "compiler"; + private static final String MARKER_KEY_COMPILER_VERSION = "compiler_version"; + private static final String MARKER_KEY_COMPILER_HASH = "compiler_hash"; + private static final String MARKER_KEY_MIN_API = "min_api"; + private static final String MARKER_KEY_PG_MAP_ID = "pg_map_id"; + public static final String MARKER_KEY_PG_MAP_HASH = "pg_map_hash"; + public static final String SHA_256_KEY = "SHA-256"; + + private final String compilerName; + private final boolean isGeneratingDex; + private final AndroidApiLevel apiLevel; + private final MapVersion mapVersion; + private final ProguardMapId proguardMapId; + + private ProguardMapMarkerInfo( + String compilerName, + boolean isGeneratingDex, + AndroidApiLevel apiLevel, + MapVersion mapVersion, + ProguardMapId proguardMapId) { + this.compilerName = compilerName; + this.isGeneratingDex = isGeneratingDex; + this.apiLevel = apiLevel; + this.mapVersion = mapVersion; + this.proguardMapId = proguardMapId; + } + + public List<String> toPreamble() { + List<String> preamble = new ArrayList<>(); + preamble.add("# " + MARKER_KEY_COMPILER + ": " + compilerName); + preamble.add("# " + MARKER_KEY_COMPILER_VERSION + ": " + Version.LABEL); + if (isGeneratingDex) { + preamble.add("# " + MARKER_KEY_MIN_API + ": " + apiLevel.getLevel()); + } + if (Version.isDevelopmentVersion()) { + preamble.add("# " + MARKER_KEY_COMPILER_HASH + ": " + VersionProperties.INSTANCE.getSha()); + } + // Turn off linting of the mapping file in some build systems. + preamble.add("# common_typos_disable"); + // Emit the R8 specific map-file version. + if (mapVersion.isGreaterThan(MapVersion.MAP_VERSION_NONE)) { + preamble.add("# " + mapVersion.toMapVersionMappingInformation().serialize()); + } + preamble.add("# " + MARKER_KEY_PG_MAP_ID + ": " + proguardMapId.getId()); + preamble.add( + "# " + MARKER_KEY_PG_MAP_HASH + ": " + SHA_256_KEY + " " + proguardMapId.getHash()); + return preamble; + } + + public String serializeToString() { + return StringUtils.unixLines(toPreamble()); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String compilerName; + private boolean isGeneratingDex; + private AndroidApiLevel apiLevel; + private MapVersion mapVersion; + private ProguardMapId proguardMapId; + + public Builder setCompilerName(String compilerName) { + this.compilerName = compilerName; + return this; + } + + public Builder setApiLevel(AndroidApiLevel apiLevel) { + this.apiLevel = apiLevel; + return this; + } + + public Builder setGeneratingDex(boolean generatingDex) { + isGeneratingDex = generatingDex; + return this; + } + + public Builder setMapVersion(MapVersion mapVersion) { + this.mapVersion = mapVersion; + return this; + } + + public Builder setProguardMapId(ProguardMapId proguardMapId) { + this.proguardMapId = proguardMapId; + return this; + } + + public ProguardMapMarkerInfo build() { + return new ProguardMapMarkerInfo( + compilerName, isGeneratingDex, apiLevel, mapVersion, proguardMapId); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapPartitionConsumer.java b/src/main/java/com/android/tools/r8/naming/ProguardMapPartitionConsumer.java new file mode 100644 index 0000000..451fd6c --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapPartitionConsumer.java
@@ -0,0 +1,96 @@ +// Copyright (c) 2023, 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.naming; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.ProguardMapConsumer; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.retrace.MappingPartition; +import com.android.tools.r8.retrace.MappingPartitionMetadata; +import com.android.tools.r8.retrace.ProguardMapPartitioner; +import com.android.tools.r8.retrace.internal.ProguardMapProducerInternal; +import java.io.IOException; +import java.util.function.Consumer; + +public class ProguardMapPartitionConsumer extends ProguardMapConsumer { + + private final Consumer<MappingPartition> mappingPartitionConsumer; + private final Consumer<MappingPartitionMetadata> metadataConsumer; + private final Runnable finishedConsumer; + private final DiagnosticsHandler diagnosticsHandler; + + private ProguardMapPartitionConsumer( + Consumer<MappingPartition> mappingPartitionConsumer, + Consumer<MappingPartitionMetadata> metadataConsumer, + Runnable finishedConsumer, + DiagnosticsHandler diagnosticsHandler) { + this.mappingPartitionConsumer = mappingPartitionConsumer; + this.metadataConsumer = metadataConsumer; + this.finishedConsumer = finishedConsumer; + this.diagnosticsHandler = diagnosticsHandler; + } + + @Override + public void accept(ProguardMapMarkerInfo makerInfo, ClassNameMapper classNameMapper) { + try { + // TODO(b/274735214): Ensure we get markerInfo consumed as well. + MappingPartitionMetadata run = + ProguardMapPartitioner.builder(diagnosticsHandler) + .setProguardMapProducer(new ProguardMapProducerInternal(classNameMapper)) + .setPartitionConsumer(mappingPartitionConsumer) + // Setting these do not actually do anything currently since there is no parsing. + .setAllowEmptyMappedRanges(false) + .setAllowExperimentalMapping(false) + .build() + .run(); + metadataConsumer.accept(run); + } catch (IOException exception) { + throw new Unreachable("IOExceptions should only occur when parsing"); + } + } + + @Override + public void finished(DiagnosticsHandler handler) { + finishedConsumer.run(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Consumer<MappingPartition> mappingPartitionConsumer; + private Consumer<MappingPartitionMetadata> metadataConsumer; + private Runnable finishedConsumer; + private DiagnosticsHandler diagnosticsHandler; + + public Builder setMappingPartitionConsumer( + Consumer<MappingPartition> mappingPartitionConsumer) { + this.mappingPartitionConsumer = mappingPartitionConsumer; + return this; + } + + public Builder setMetadataConsumer(Consumer<MappingPartitionMetadata> metadataConsumer) { + this.metadataConsumer = metadataConsumer; + return this; + } + + public Builder setFinishedConsumer(Runnable finishedConsumer) { + this.finishedConsumer = finishedConsumer; + return this; + } + + public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) { + this.diagnosticsHandler = diagnosticsHandler; + return this; + } + + public ProguardMapPartitionConsumer build() { + return new ProguardMapPartitionConsumer( + mappingPartitionConsumer, metadataConsumer, finishedConsumer, diagnosticsHandler); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java b/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java new file mode 100644 index 0000000..95bbd38 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java
@@ -0,0 +1,74 @@ +// Copyright (c) 2023, 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.naming; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.ProguardMapConsumer; +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.utils.ChainableStringConsumer; + +/*** + * Default implementation of a ProguardMapConsumer that wraps around a string consumer for streamed + * string output. + */ +public class ProguardMapStringConsumer extends ProguardMapConsumer + implements ChainableStringConsumer { + + private final StringConsumer stringConsumer; + private final DiagnosticsHandler diagnosticsHandler; + + private ProguardMapStringConsumer( + StringConsumer stringConsumer, DiagnosticsHandler diagnosticsHandler) { + assert stringConsumer != null; + assert diagnosticsHandler != null; + this.stringConsumer = stringConsumer; + this.diagnosticsHandler = diagnosticsHandler; + } + + @Override + public void accept(ProguardMapMarkerInfo markerInfo, ClassNameMapper classNameMapper) { + accept(markerInfo.serializeToString()); + classNameMapper.write(this); + } + + @Override + public ChainableStringConsumer accept(String string) { + stringConsumer.accept(string, diagnosticsHandler); + return this; + } + + public StringConsumer getStringConsumer() { + return stringConsumer; + } + + @Override + public void finished(DiagnosticsHandler handler) { + stringConsumer.finished(handler); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private StringConsumer stringConsumer; + private DiagnosticsHandler diagnosticsHandler; + + public Builder setStringConsumer(StringConsumer stringConsumer) { + this.stringConsumer = stringConsumer; + return this; + } + + public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) { + this.diagnosticsHandler = diagnosticsHandler; + return this; + } + + public ProguardMapStringConsumer build() { + return new ProguardMapStringConsumer(stringConsumer, diagnosticsHandler); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java index a7f62dd..c3fc071 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -3,33 +3,20 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming; -import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.MapIdEnvironment; import com.android.tools.r8.MapIdProvider; -import com.android.tools.r8.StringConsumer; -import com.android.tools.r8.Version; +import com.android.tools.r8.ProguardMapConsumer; import com.android.tools.r8.dex.Marker.Tool; -import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.utils.ChainableStringConsumer; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Reporter; -import com.android.tools.r8.utils.VersionProperties; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; -import java.io.IOException; import java.nio.charset.StandardCharsets; public class ProguardMapSupplier { - public static final String MARKER_KEY_COMPILER = "compiler"; - public static final String MARKER_KEY_COMPILER_VERSION = "compiler_version"; - public static final String MARKER_KEY_COMPILER_HASH = "compiler_hash"; - public static final String MARKER_KEY_MIN_API = "min_api"; - public static final String MARKER_KEY_PG_MAP_ID = "pg_map_id"; - public static final String MARKER_KEY_PG_MAP_HASH = "pg_map_hash"; - public static final String SHA_256_KEY = "SHA-256"; - public static int PG_MAP_ID_LENGTH = 7; // Hash of the Proguard map (excluding the header up to and including the hash marker). @@ -56,8 +43,8 @@ } private final ClassNameMapper classNameMapper; - private final StringConsumer consumer; private final InternalOptions options; + private final ProguardMapConsumer consumer; private final Reporter reporter; private final Tool compiler; @@ -79,11 +66,18 @@ } public ProguardMapId writeProguardMap() { - ProguardMapId id = computeProguardMapId(); - writeMarker(id); - writeBody(); - ExceptionUtils.withFinishedResourceHandler(reporter, consumer); - return id; + ProguardMapId proguardMapId = computeProguardMapId(); + consumer.accept( + ProguardMapMarkerInfo.builder() + .setCompilerName(compiler.name()) + .setProguardMapId(proguardMapId) + .setGeneratingDex(options.isGeneratingDex()) + .setApiLevel(options.getMinApiLevel()) + .setMapVersion(options.getMapFileVersion()) + .build(), + classNameMapper); + ExceptionUtils.withConsumeResourceHandler(reporter, this.consumer::finished); + return proguardMapId; } private ProguardMapId computeProguardMapId() { @@ -92,52 +86,6 @@ return builder.build(options.mapIdProvider); } - private void writeBody() { - classNameMapper.write(new ProguardMapWriter()); - } - - private void writeMarker(ProguardMapId id) { - StringBuilder builder = new StringBuilder(); - builder.append( - "# " - + MARKER_KEY_COMPILER - + ": " - + compiler.name() - + "\n" - + "# " - + MARKER_KEY_COMPILER_VERSION - + ": " - + Version.LABEL - + "\n"); - if (options.isGeneratingDex()) { - builder.append("# " + MARKER_KEY_MIN_API + ": " + options.getMinApiLevel().getLevel() + "\n"); - } - if (Version.isDevelopmentVersion()) { - builder.append( - "# " + MARKER_KEY_COMPILER_HASH + ": " + VersionProperties.INSTANCE.getSha() + "\n"); - } - // Turn off linting of the mapping file in some build systems. - builder.append("# common_typos_disable" + "\n"); - // Emit the R8 specific map-file version. - MapVersion mapVersion = options.getMapFileVersion(); - if (mapVersion.isGreaterThan(MapVersion.MAP_VERSION_NONE)) { - builder - .append("# ") - .append(mapVersion.toMapVersionMappingInformation().serialize()) - .append("\n"); - } - builder.append("# " + MARKER_KEY_PG_MAP_ID + ": " + id.getId() + "\n"); - // Place the map hash as the last header item. Everything past this line is the hashed content. - builder - .append("# ") - .append(MARKER_KEY_PG_MAP_HASH) - .append(": ") - .append(SHA_256_KEY) - .append(" ") - .append(id.getHash()) - .append("\n"); - consumer.accept(builder.toString(), reporter); - } static class ProguardMapIdBuilder implements ChainableStringConsumer { @@ -171,123 +119,4 @@ } } - class ProguardMapWriter implements ChainableStringConsumer { - - @Override - public ProguardMapWriter accept(String string) { - consumer.accept(string, reporter); - return this; - } - } - - public static class ProguardMapChecker implements StringConsumer { - - private final StringConsumer inner; - private final StringBuilder contents = new StringBuilder(); - - ProguardMapChecker(StringConsumer inner) { - if (!InternalOptions.assertionsEnabled()) { - // Make sure we never get here without assertions enabled. - throw new Unreachable(); - } - this.inner = inner; - } - - @Override - public void accept(String string, DiagnosticsHandler handler) { - inner.accept(string, handler); - contents.append(string); - } - - @Override - public void finished(DiagnosticsHandler handler) { - inner.finished(handler); - String stringContent = contents.toString(); - assert validateProguardMapParses(stringContent); - assert validateProguardMapHash(stringContent).isOk(); - } - - private static boolean validateProguardMapParses(String content) { - try { - ClassNameMapper.mapperFromString(content); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - return true; - } - - public static class VerifyMappingFileHashResult { - private final boolean error; - private final String message; - - public static VerifyMappingFileHashResult createOk() { - return new VerifyMappingFileHashResult(false, null); - } - - public static VerifyMappingFileHashResult createInfo(String message) { - return new VerifyMappingFileHashResult(false, message); - } - - public static VerifyMappingFileHashResult createError(String message) { - return new VerifyMappingFileHashResult(true, message); - } - - private VerifyMappingFileHashResult(boolean error, String message) { - this.error = error; - this.message = message; - } - - public boolean isOk() { - return !error && message == null; - } - - public boolean isError() { - return error; - } - - public String getMessage() { - assert message != null; - return message; - } - } - - public static VerifyMappingFileHashResult validateProguardMapHash(String content) { - int lineEnd = -1; - while (true) { - int lineStart = lineEnd + 1; - lineEnd = content.indexOf('\n', lineStart); - if (lineEnd < 0) { - return VerifyMappingFileHashResult.createInfo("Failure to find map hash"); - } - String line = content.substring(lineStart, lineEnd).trim(); - if (line.isEmpty()) { - // Ignore empty lines in the header. - continue; - } - if (line.charAt(0) != '#') { - // At the first non-empty non-comment line we assume that the file has no hash marker. - return VerifyMappingFileHashResult.createInfo("Failure to find map hash in header"); - } - String headerLine = line.substring(1).trim(); - if (headerLine.startsWith(MARKER_KEY_PG_MAP_HASH)) { - int shaIndex = headerLine.indexOf(SHA_256_KEY + " ", MARKER_KEY_PG_MAP_HASH.length()); - if (shaIndex < 0) { - return VerifyMappingFileHashResult.createError( - "Unknown map hash function: '" + headerLine + "'"); - } - String headerHash = headerLine.substring(shaIndex + SHA_256_KEY.length()).trim(); - // We are on the hash line. Everything past this line is the hashed content. - Hasher hasher = Hashing.sha256().newHasher(); - String hashedContent = content.substring(lineEnd + 1); - hasher.putString(hashedContent, StandardCharsets.UTF_8); - String computedHash = hasher.hash().toString(); - return headerHash.equals(computedHash) - ? VerifyMappingFileHashResult.createOk() - : VerifyMappingFileHashResult.createError( - "Mismatching map hash: '" + headerHash + "' != '" + computedHash + "'"); - } - } - } - } }
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalOptions.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalOptions.java new file mode 100644 index 0000000..a902764 --- /dev/null +++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalOptions.java
@@ -0,0 +1,42 @@ +// Copyright (c) 2023, 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.optimize.redundantbridgeremoval; + +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexLibraryClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.SetUtils; +import java.util.Collections; +import java.util.Set; + +public class RedundantBridgeRemovalOptions { + + private final InternalOptions options; + + private Set<DexType> noConstructorShrinkingHierarchies; + + public RedundantBridgeRemovalOptions(InternalOptions options) { + this.options = options; + } + + public void clearNoConstructorShrinkingHierarchiesForTesting() { + noConstructorShrinkingHierarchies = Collections.emptySet(); + } + + public RedundantBridgeRemovalOptions ensureInitialized() { + if (noConstructorShrinkingHierarchies == null) { + DexItemFactory dexItemFactory = options.dexItemFactory(); + noConstructorShrinkingHierarchies = + SetUtils.newIdentityHashSet( + dexItemFactory.androidAppFragment, dexItemFactory.androidAppZygotePreload); + } + return this; + } + + public boolean isPlatformReflectingOnDefaultConstructorInSubclasses(DexLibraryClass clazz) { + return noConstructorShrinkingHierarchies.contains(clazz.getType()); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java similarity index 72% rename from src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java rename to src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java index 763c40f..c050859 100644 --- a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java +++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
@@ -1,9 +1,10 @@ // Copyright (c) 2017, 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.optimize; +package com.android.tools.r8.optimize.redundantbridgeremoval; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; @@ -15,23 +16,35 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; +import com.android.tools.r8.optimize.InvokeSingleTargetExtractor; import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind; -import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalLens; +import com.android.tools.r8.optimize.MemberRebindingIdentityLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.KeepMethodInfo; +import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.WorkList; import com.android.tools.r8.utils.collections.ProgramMethodSet; +import com.google.common.collect.Iterables; +import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; public class RedundantBridgeRemover { private final AppView<AppInfoWithLiveness> appView; + private final RedundantBridgeRemovalOptions redundantBridgeRemovalOptions; + + private final InvokedReflectivelyFromPlatformAnalysis invokedReflectivelyFromPlatformAnalysis = + new InvokedReflectivelyFromPlatformAnalysis(); public RedundantBridgeRemover(AppView<AppInfoWithLiveness> appView) { this.appView = appView; + this.redundantBridgeRemovalOptions = + appView.options().getRedundantBridgeRemovalOptions().ensureInitialized(); } private DexClassAndMethod getTargetForRedundantBridge(ProgramMethod method) { @@ -52,6 +65,9 @@ if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) { return null; } + if (invokedReflectivelyFromPlatformAnalysis.isMaybeInvokedReflectivelyFromPlatform(method)) { + return null; + } // This is a visibility forward, so check for the direct target. DexClassAndMethod targetMethod = appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(target).getResolutionPair(); @@ -236,4 +252,88 @@ }); appView.pruneItems(prunedItemsBuilder.build(), executorService); } + + class InvokedReflectivelyFromPlatformAnalysis { + + // Maps each class to a boolean indicating if the class inherits from android.app.Fragment or + // android.app.ZygotePreload. + private final Map<DexClass, Boolean> cache = new ConcurrentHashMap<>(); + + boolean isMaybeInvokedReflectivelyFromPlatform(ProgramMethod method) { + return method.getDefinition().isDefaultInstanceInitializer() + && !method.getHolder().isAbstract() + && computeIsPlatformReflectingOnDefaultConstructor(method.getHolder()); + } + + private boolean computeIsPlatformReflectingOnDefaultConstructor(DexProgramClass clazz) { + Boolean cacheResult = cache.get(clazz); + if (cacheResult != null) { + return cacheResult; + } + WorkList.<WorklistItem>newIdentityWorkList(new NotProcessedWorklistItem(clazz)) + .process(WorklistItem::accept); + assert cache.containsKey(clazz); + return cache.get(clazz); + } + + abstract class WorklistItem implements Consumer<WorkList<WorklistItem>> { + + protected final DexClass clazz; + + WorklistItem(DexClass clazz) { + this.clazz = clazz; + } + + Iterable<DexClass> getImmediateSupertypes() { + return IterableUtils.flatMap( + clazz.allImmediateSupertypes(), + supertype -> { + DexClass definition = appView.definitionFor(supertype); + return definition != null + ? Collections.singletonList(definition) + : Collections.emptyList(); + }); + } + } + + class NotProcessedWorklistItem extends WorklistItem { + + NotProcessedWorklistItem(DexClass clazz) { + super(clazz); + } + + @Override + public void accept(WorkList<WorklistItem> worklist) { + // Enqueue a worklist item to process the current class after processing its super classes. + worklist.addFirstIgnoringSeenSet(new ProcessedWorklistItem(clazz)); + // Enqueue all superclasses for processing. + for (DexClass supertype : getImmediateSupertypes()) { + if (!cache.containsKey(supertype)) { + worklist.addFirstIgnoringSeenSet(new NotProcessedWorklistItem(supertype)); + } + } + } + } + + class ProcessedWorklistItem extends WorklistItem { + + ProcessedWorklistItem(DexClass clazz) { + super(clazz); + } + + @Override + public void accept(WorkList<WorklistItem> worklist) { + cache.put( + clazz, + Iterables.any( + getImmediateSupertypes(), + supertype -> + cache.get(supertype) + || (supertype.isLibraryClass() + && redundantBridgeRemovalOptions + .isPlatformReflectingOnDefaultConstructorInSubclasses( + supertype.asLibraryClass())))); + } + } + } }
diff --git a/src/main/java/com/android/tools/r8/profile/AbstractProfile.java b/src/main/java/com/android/tools/r8/profile/AbstractProfile.java index 87b5656..ff8c494 100644 --- a/src/main/java/com/android/tools/r8/profile/AbstractProfile.java +++ b/src/main/java/com/android/tools/r8/profile/AbstractProfile.java
@@ -16,8 +16,8 @@ boolean containsMethodRule(DexMethod method); <E1 extends Exception, E2 extends Exception> void forEachRule( - ThrowingConsumer<ClassRule, E1> classRuleConsumer, - ThrowingConsumer<MethodRule, E2> methodRuleConsumer) + ThrowingConsumer<? super ClassRule, E1> classRuleConsumer, + ThrowingConsumer<? super MethodRule, E2> methodRuleConsumer) throws E1, E2; ClassRule getClassRule(DexType type);
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java index 7ab727d..e42e8ba 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -71,8 +71,8 @@ @Override public <E1 extends Exception, E2 extends Exception> void forEachRule( - ThrowingConsumer<ArtProfileClassRule, E1> classRuleConsumer, - ThrowingConsumer<ArtProfileMethodRule, E2> methodRuleConsumer) + ThrowingConsumer<? super ArtProfileClassRule, E1> classRuleConsumer, + ThrowingConsumer<? super ArtProfileMethodRule, E2> methodRuleConsumer) throws E1, E2 { for (ArtProfileRule rule : rules.values()) { rule.accept(classRuleConsumer, methodRuleConsumer);
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java index 625d92f..55590a0 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java
@@ -32,8 +32,8 @@ @Override public <E1 extends Exception, E2 extends Exception> void accept( - ThrowingConsumer<ArtProfileClassRule, E1> classRuleConsumer, - ThrowingConsumer<ArtProfileMethodRule, E2> methodRuleConsumer) + ThrowingConsumer<? super ArtProfileClassRule, E1> classRuleConsumer, + ThrowingConsumer<? super ArtProfileMethodRule, E2> methodRuleConsumer) throws E1 { classRuleConsumer.accept(this); }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java index 1440b8b..4b417c9 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
@@ -35,8 +35,8 @@ @Override public <E1 extends Exception, E2 extends Exception> void accept( - ThrowingConsumer<ArtProfileClassRule, E1> classRuleConsumer, - ThrowingConsumer<ArtProfileMethodRule, E2> methodRuleConsumer) + ThrowingConsumer<? super ArtProfileClassRule, E1> classRuleConsumer, + ThrowingConsumer<? super ArtProfileMethodRule, E2> methodRuleConsumer) throws E2 { methodRuleConsumer.accept(this); }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java index bb0bcc1..39f541f 100644 --- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java +++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java
@@ -14,8 +14,8 @@ public abstract class ArtProfileRule implements Comparable<ArtProfileRule>, AbstractProfileRule { public abstract <E1 extends Exception, E2 extends Exception> void accept( - ThrowingConsumer<ArtProfileClassRule, E1> classRuleConsumer, - ThrowingConsumer<ArtProfileMethodRule, E2> methodRuleConsumer) + ThrowingConsumer<? super ArtProfileClassRule, E1> classRuleConsumer, + ThrowingConsumer<? super ArtProfileMethodRule, E2> methodRuleConsumer) throws E1, E2; public abstract <T, E1 extends Exception, E2 extends Exception> T apply(
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java b/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java new file mode 100644 index 0000000..b6181dc --- /dev/null +++ b/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
@@ -0,0 +1,117 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace; + +import static com.google.common.base.Predicates.alwaysTrue; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.LineReader; +import com.android.tools.r8.naming.MapVersion; +import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal; +import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer; +import com.android.tools.r8.retrace.internal.RetracePartitionException; +import com.android.tools.r8.utils.ChainableStringConsumer; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class PartitionedToProguardMappingConverter { + + private final StringConsumer consumer; + private final MappingPartitionFromKeySupplier partitionSupplier; + private final byte[] metadata; + private final DiagnosticsHandler diagnosticsHandler; + + private PartitionedToProguardMappingConverter( + StringConsumer consumer, + MappingPartitionFromKeySupplier partitionSupplier, + byte[] metadata, + DiagnosticsHandler diagnosticsHandler) { + this.consumer = consumer; + this.partitionSupplier = partitionSupplier; + this.metadata = metadata; + this.diagnosticsHandler = diagnosticsHandler; + } + + public void run() throws RetracePartitionException { + MappingPartitionMetadataInternal metadataInternal = + MappingPartitionMetadataInternal.deserialize( + metadata, MapVersion.MAP_VERSION_UNKNOWN, diagnosticsHandler); + if (!metadataInternal.canGetPartitionKeys()) { + throw new RetracePartitionException("Cannot obtain all partition keys from metadata"); + } + // TODO(b/274893426): Account for preamble. + ClassNameMapper classNameMapper = ClassNameMapper.builder().build(); + for (String partitionKey : metadataInternal.getPartitionKeys()) { + LineReader reader = + new ProguardMapReaderWithFilteringInputBuffer( + new ByteArrayInputStream(partitionSupplier.get(partitionKey)), alwaysTrue(), true); + try { + classNameMapper = + ClassNameMapper.mapperFromLineReaderWithFiltering( + reader, metadataInternal.getMapVersion(), diagnosticsHandler, true, true) + .combine(classNameMapper); + } catch (IOException e) { + throw new RetracePartitionException(e); + } + } + classNameMapper.sorted().write(new ProguardMapWriter(consumer, diagnosticsHandler)); + } + + private static class ProguardMapWriter implements ChainableStringConsumer { + + private final StringConsumer consumer; + private final DiagnosticsHandler diagnosticsHandler; + + private ProguardMapWriter(StringConsumer consumer, DiagnosticsHandler diagnosticsHandler) { + this.consumer = consumer; + this.diagnosticsHandler = diagnosticsHandler; + } + + @Override + public ProguardMapWriter accept(String string) { + consumer.accept(string, diagnosticsHandler); + return this; + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private StringConsumer consumer; + private MappingPartitionFromKeySupplier partitionSupplier; + private byte[] metadata; + private DiagnosticsHandler diagnosticsHandler; + + public Builder setConsumer(StringConsumer consumer) { + this.consumer = consumer; + return this; + } + + public Builder setPartitionSupplier(MappingPartitionFromKeySupplier partitionSupplier) { + this.partitionSupplier = partitionSupplier; + return this; + } + + public Builder setMetadata(byte[] metadata) { + this.metadata = metadata; + return this; + } + + public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) { + this.diagnosticsHandler = diagnosticsHandler; + return this; + } + + public PartitionedToProguardMappingConverter build() { + return new PartitionedToProguardMappingConverter( + consumer, partitionSupplier, metadata, diagnosticsHandler); + } + } +}
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 8f619ac..3d2740a 100644 --- a/src/main/java/com/android/tools/r8/retrace/Retrace.java +++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -425,7 +425,10 @@ run(mappedArgs, retraceDiagnosticsHandler); } catch (Throwable t) { throw failWithFakeEntry( - retraceDiagnosticsHandler, t, RetraceFailedException::new, RetraceAbortException.class); + retraceDiagnosticsHandler, + t, + (message, cause, ignore) -> new RetraceFailedException(message, cause), + RetraceAbortException.class); } }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java index ad53968..b31d984 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
@@ -5,11 +5,22 @@ package com.android.tools.r8.retrace.internal; public enum MappingPartitionKeyStrategy { - OBFUSCATED_TYPE_NAME_AS_KEY(0); + OBFUSCATED_TYPE_NAME_AS_KEY(0), + OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS(1); - final int serializedKey; + private static final MappingPartitionKeyStrategy DEFAULT_STRATEGY = OBFUSCATED_TYPE_NAME_AS_KEY; + + private final int serializedKey; MappingPartitionKeyStrategy(int serializedKey) { this.serializedKey = serializedKey; } + + public int getSerializedKey() { + return serializedKey; + } + + public static MappingPartitionKeyStrategy getDefaultStrategy() { + return DEFAULT_STRATEGY; + } }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java index 6c2e3ca..0cee013 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
@@ -5,16 +5,19 @@ package com.android.tools.r8.retrace.internal; import static com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY; +import static com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS; import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.naming.MapVersion; import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.retrace.MappingPartitionMetadata; +import com.android.tools.r8.retrace.internal.MetadataPartitionCollection.LazyMetadataPartitionCollection; import com.android.tools.r8.utils.ExceptionDiagnostic; import com.google.common.primitives.Ints; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.util.Collection; public interface MappingPartitionMetadataInternal extends MappingPartitionMetadata { @@ -22,26 +25,56 @@ MapVersion getMapVersion(); - byte ZERO_BYTE = (byte) 0; + default boolean canGetPartitionKeys() { + return false; + } - static MappingPartitionMetadataInternal createFromBytes( + default Collection<String> getPartitionKeys() { + return null; + } + + byte ZERO_BYTE = (byte) 0; + // Magic byte put into the metadata + byte[] MAGIC = new byte[] {(byte) 0xAA, (byte) 0xA8}; + + static int magicOffset() { + return MAGIC.length; + } + + static MappingPartitionMetadataInternal deserialize( byte[] bytes, MapVersion fallBackMapVersion, DiagnosticsHandler diagnosticsHandler) { if (bytes == null) { - return obfuscatedTypeNameAsKey(fallBackMapVersion); - } else if (bytes.length > 2) { - int serializedStrategyId = Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[0], bytes[1]); - MapVersion mapVersion = MapVersion.fromName(new String(bytes, 2, bytes.length - 2)); - if (serializedStrategyId == OBFUSCATED_TYPE_NAME_AS_KEY.serializedKey) { - return obfuscatedTypeNameAsKey(mapVersion); + return ObfuscatedTypeNameAsKeyMetadata.create(fallBackMapVersion); + } + if (bytes.length > 2) { + if (startsWithMagic(bytes)) { + int serializedKey = + Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[magicOffset()], bytes[magicOffset() + 1]); + if (serializedKey == OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS.getSerializedKey()) { + return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.deserialize(bytes); + } + } else if (OBFUSCATED_TYPE_NAME_AS_KEY.getSerializedKey() + == Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[0], bytes[1])) { + return ObfuscatedTypeNameAsKeyMetadata.deserialize(bytes); } } - RuntimeException exception = new RuntimeException("Unable to build key strategy from metadata"); + // If we arrived here then we could not deserialize the metadata. + RetracePartitionException exception = + new RetracePartitionException("Unknown map partition strategy for metadata"); diagnosticsHandler.error(new ExceptionDiagnostic(exception)); throw exception; } - static ObfuscatedTypeNameAsKeyMetadata obfuscatedTypeNameAsKey(MapVersion mapVersion) { - return new ObfuscatedTypeNameAsKeyMetadata(mapVersion); + private static boolean startsWithMagic(byte[] bytes) { + if (bytes.length < MAGIC.length) { + return false; + } + for (int i = 0; i < MAGIC.length; i++) { + if (bytes[i] != MAGIC[i]) { + return false; + } + } + return true; } class ObfuscatedTypeNameAsKeyMetadata implements MappingPartitionMetadataInternal { @@ -62,12 +95,14 @@ return mapVersion; } + // The format is: + // <type:short><map-version> @Override public byte[] getBytes() { try { ByteArrayOutputStream temp = new ByteArrayOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(temp); - dataOutputStream.writeShort(OBFUSCATED_TYPE_NAME_AS_KEY.serializedKey); + dataOutputStream.writeShort(OBFUSCATED_TYPE_NAME_AS_KEY.getSerializedKey()); dataOutputStream.writeBytes(mapVersion.getName()); dataOutputStream.close(); return temp.toByteArray(); @@ -75,5 +110,83 @@ throw new RuntimeException(e); } } + + public static ObfuscatedTypeNameAsKeyMetadata deserialize(byte[] bytes) { + MapVersion mapVersion = MapVersion.fromName(new String(bytes, 2, bytes.length - 2)); + return create(mapVersion); + } + + public static ObfuscatedTypeNameAsKeyMetadata create(MapVersion mapVersion) { + return new ObfuscatedTypeNameAsKeyMetadata(mapVersion); + } + } + + class ObfuscatedTypeNameAsKeyMetadataWithPartitionNames + implements MappingPartitionMetadataInternal { + + private final MapVersion mapVersion; + private final MetadataPartitionCollection metadataPartitionCollection; + + private ObfuscatedTypeNameAsKeyMetadataWithPartitionNames( + MapVersion mapVersion, MetadataPartitionCollection metadataPartitionCollection) { + this.mapVersion = mapVersion; + this.metadataPartitionCollection = metadataPartitionCollection; + } + + public static ObfuscatedTypeNameAsKeyMetadataWithPartitionNames create( + MapVersion mapVersion, MetadataPartitionCollection metadataPartitionCollection) { + return new ObfuscatedTypeNameAsKeyMetadataWithPartitionNames( + mapVersion, metadataPartitionCollection); + } + + @Override + public String getKey(ClassReference classReference) { + return classReference.getTypeName(); + } + + @Override + public MapVersion getMapVersion() { + return mapVersion; + } + + @Override + public boolean canGetPartitionKeys() { + return true; + } + + @Override + public Collection<String> getPartitionKeys() { + return metadataPartitionCollection.getPartitionKeys(); + } + + // The format is: + // <type:short><map-version-length:short><map-version>[<partition_key>] + @Override + public byte[] getBytes() { + try { + ByteArrayOutputStream temp = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(temp); + dataOutputStream.write(MAGIC); + dataOutputStream.writeShort(OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS.getSerializedKey()); + String name = mapVersion.getName(); + dataOutputStream.writeShort(name.length()); + dataOutputStream.writeBytes(name); + dataOutputStream.write(metadataPartitionCollection.serialize()); + dataOutputStream.close(); + return temp.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static ObfuscatedTypeNameAsKeyMetadataWithPartitionNames deserialize(byte[] bytes) { + int start = magicOffset(); + int length = Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[start + 2], bytes[start + 3]); + MapVersion mapVersion = MapVersion.fromName(new String(bytes, start + 4, length)); + int partitionCollectionStartIndex = start + 4 + length; + return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.create( + mapVersion, + new LazyMetadataPartitionCollection(bytes, partitionCollectionStartIndex, bytes.length)); + } } }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MetadataPartitionCollection.java b/src/main/java/com/android/tools/r8/retrace/internal/MetadataPartitionCollection.java new file mode 100644 index 0000000..aef50f0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/retrace/internal/MetadataPartitionCollection.java
@@ -0,0 +1,72 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace.internal; + +import com.android.tools.r8.utils.StringUtils; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; + +public class MetadataPartitionCollection { + + private static final char SEPARATOR = ';'; + + private final Collection<String> partitionKeys; + + private MetadataPartitionCollection(Collection<String> partitionKeys) { + this.partitionKeys = partitionKeys; + } + + public Collection<String> getPartitionKeys() { + return partitionKeys; + } + + public byte[] serialize() { + return StringUtils.join(SEPARATOR + "", partitionKeys).getBytes(StandardCharsets.UTF_8); + } + + public static MetadataPartitionCollection create(Collection<String> partitionKeys) { + return new MetadataPartitionCollection(partitionKeys); + } + + public static MetadataPartitionCollection createLazy( + byte[] bytes, int partitionCollectionStartIndex, int partitionCollectionEndIndex) { + return new LazyMetadataPartitionCollection( + bytes, partitionCollectionStartIndex, partitionCollectionEndIndex); + } + + public static class LazyMetadataPartitionCollection extends MetadataPartitionCollection { + + private final byte[] bytes; + private final int partitionCollectionStartIndex; + private final int partitionCollectionEndIndex; + private MetadataPartitionCollection metadataPartitionCollection = null; + + public LazyMetadataPartitionCollection( + byte[] bytes, int partitionCollectionStartIndex, int partitionCollectionEndIndex) { + super(Collections.emptyList()); + this.bytes = bytes; + this.partitionCollectionStartIndex = partitionCollectionStartIndex; + this.partitionCollectionEndIndex = partitionCollectionEndIndex; + } + + @Override + public Collection<String> getPartitionKeys() { + if (metadataPartitionCollection == null) { + metadataPartitionCollection = deserialize(); + } + return metadataPartitionCollection.getPartitionKeys(); + } + + private MetadataPartitionCollection deserialize() { + String allKeys = + new String( + bytes, + partitionCollectionStartIndex, + partitionCollectionEndIndex - partitionCollectionStartIndex); + return create(StringUtils.split(allKeys, SEPARATOR)); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java index d37d45c..7d663fb 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
@@ -65,7 +65,7 @@ return mappingPartitionMetadataCache; } return mappingPartitionMetadataCache = - MappingPartitionMetadataInternal.createFromBytes( + MappingPartitionMetadataInternal.deserialize( metadata, fallbackMapVersion, diagnosticsHandler); } @@ -77,6 +77,7 @@ } private void registerKeyUse(String key) { + // TODO(b/274735214): only call the register partition if we have a partition for it. if (!builtKeys.contains(key) && pendingKeys.add(key)) { registerPartitionCallback.register(key); } @@ -103,9 +104,14 @@ } for (String pendingKey : pendingKeys) { try { + byte[] suppliedPartition = partitionSupplier.get(pendingKey); + // TODO(b/274735214): only expect a partition if have generated one for the key. + if (suppliedPartition == null) { + continue; + } LineReader reader = new ProguardMapReaderWithFilteringInputBuffer( - new ByteArrayInputStream(partitionSupplier.get(pendingKey)), alwaysTrue(), true); + new ByteArrayInputStream(suppliedPartition), alwaysTrue(), true); classNameMapper = ClassNameMapper.mapperFromLineReaderWithFiltering( reader, metadata.getMapVersion(), diagnosticsHandler, true, allowExperimental)
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java index 5fd87ef..06ede47 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
@@ -18,19 +18,22 @@ import com.android.tools.r8.retrace.ProguardMapPartitioner; import com.android.tools.r8.retrace.ProguardMapPartitionerBuilder; import com.android.tools.r8.retrace.ProguardMapProducer; +import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal.ObfuscatedTypeNameAsKeyMetadata; +import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal.ObfuscatedTypeNameAsKeyMetadataWithPartitionNames; import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer; import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringMappedBuffer; import com.android.tools.r8.utils.ExceptionDiagnostic; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.TriConsumer; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -42,22 +45,46 @@ private final DiagnosticsHandler diagnosticsHandler; private final boolean allowEmptyMappedRanges; private final boolean allowExperimentalMapping; + private final MappingPartitionKeyStrategy mappingPartitionKeyStrategy; private ProguardMapPartitionerOnClassNameToText( ProguardMapProducer proguardMapProducer, Consumer<MappingPartition> mappingPartitionConsumer, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges, - boolean allowExperimentalMapping) { + boolean allowExperimentalMapping, + MappingPartitionKeyStrategy mappingPartitionKeyStrategy) { this.proguardMapProducer = proguardMapProducer; this.mappingPartitionConsumer = mappingPartitionConsumer; this.diagnosticsHandler = diagnosticsHandler; this.allowEmptyMappedRanges = allowEmptyMappedRanges; this.allowExperimentalMapping = allowExperimentalMapping; + this.mappingPartitionKeyStrategy = mappingPartitionKeyStrategy; } - @Override - public MappingPartitionMetadata run() throws IOException { + private ClassNameMapper getPartitionsFromProguardMapProducer( + TriConsumer<ClassNameMapper, ClassNamingForNameMapper, String> consumer) throws IOException { + if (proguardMapProducer instanceof ProguardMapProducerInternal) { + return getPartitionsFromInternalProguardMapProducer(consumer); + } else { + return getPartitionsFromStringBackedProguardMapProducer(consumer); + } + } + + private ClassNameMapper getPartitionsFromInternalProguardMapProducer( + TriConsumer<ClassNameMapper, ClassNamingForNameMapper, String> consumer) { + ClassNameMapper classNameMapper = + ((ProguardMapProducerInternal) proguardMapProducer).getClassNameMapper(); + classNameMapper + .getClassNameMappings() + .forEach( + (key, mappingForClass) -> + consumer.accept(classNameMapper, mappingForClass, mappingForClass.toString())); + return classNameMapper; + } + + private ClassNameMapper getPartitionsFromStringBackedProguardMapProducer( + TriConsumer<ClassNameMapper, ClassNamingForNameMapper, String> consumer) throws IOException { PartitionLineReader reader = new PartitionLineReader( proguardMapProducer.isFileBacked() @@ -69,8 +96,11 @@ // mappings. ClassNameMapper classMapper = ClassNameMapper.mapperFromLineReaderWithFiltering( - reader, MapVersion.MAP_VERSION_UNKNOWN, diagnosticsHandler, true, true); - // We can then iterate over all sections. + reader, + MapVersion.MAP_VERSION_UNKNOWN, + diagnosticsHandler, + allowEmptyMappedRanges, + allowExperimentalMapping); reader.forEachClassMapping( (classMapping, entries) -> { try { @@ -78,47 +108,76 @@ ClassNameMapper classNameMapper = ClassNameMapper.mapperFromString( payload, null, allowEmptyMappedRanges, allowExperimentalMapping, false); - if (classNameMapper.getClassNameMappings().size() != 1) { + Map<String, ClassNamingForNameMapper> classNameMappings = + classNameMapper.getClassNameMappings(); + if (classNameMappings.size() != 1) { diagnosticsHandler.error( new StringDiagnostic("Multiple class names in payload\n: " + payload)); return; } - Entry<String, ClassNamingForNameMapper> currentClassMapping = - classNameMapper.getClassNameMappings().entrySet().iterator().next(); - ClassNamingForNameMapper value = currentClassMapping.getValue(); - Set<String> seenMappings = new HashSet<>(); - StringBuilder payloadWithClassReferences = new StringBuilder(); - value.visitAllFullyQualifiedReferences( - holder -> { - if (seenMappings.add(holder)) { - payloadWithClassReferences.append( - getSourceFileMapping(holder, classMapper.getSourceFile(holder))); - } - }); - payloadWithClassReferences.append(payload); - mappingPartitionConsumer.accept( - new MappingPartitionImpl( - currentClassMapping.getKey(), - payloadWithClassReferences.toString().getBytes(StandardCharsets.UTF_8))); + consumer.accept(classMapper, classNameMappings.values().iterator().next(), payload); } catch (IOException e) { diagnosticsHandler.error(new ExceptionDiagnostic(e)); } }); + return classMapper; + } + + @Override + public MappingPartitionMetadata run() throws IOException { + HashSet<String> keys = new LinkedHashSet<>(); + // We can then iterate over all sections. + ClassNameMapper classMapper = + getPartitionsFromProguardMapProducer( + (classNameMapper, classNamingForNameMapper, payload) -> { + Set<String> seenMappings = new HashSet<>(); + StringBuilder payloadWithClassReferences = new StringBuilder(); + Map<String, String> lookupMap = + classNameMapper.getObfuscatedToOriginalMapping().inverse; + classNamingForNameMapper.visitAllFullyQualifiedReferences( + holder -> { + if (seenMappings.add(holder)) { + payloadWithClassReferences.append( + getSourceFileMapping( + holder, + lookupMap.get(holder), + classNameMapper.getSourceFile(holder))); + } + }); + payloadWithClassReferences.append(payload); + mappingPartitionConsumer.accept( + new MappingPartitionImpl( + classNamingForNameMapper.renamedName, + payloadWithClassReferences.toString().getBytes(StandardCharsets.UTF_8))); + keys.add(classNamingForNameMapper.renamedName); + }); MapVersion mapVersion = MapVersion.MAP_VERSION_UNKNOWN; MapVersionMappingInformation mapVersionInfo = classMapper.getFirstMapVersionInformation(); if (mapVersionInfo != null) { mapVersion = mapVersionInfo.getMapVersion(); } - return MappingPartitionMetadataInternal.obfuscatedTypeNameAsKey(mapVersion); + if (mappingPartitionKeyStrategy == MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY) { + return ObfuscatedTypeNameAsKeyMetadata.create(mapVersion); + } else if (mappingPartitionKeyStrategy + == MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS) { + return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.create( + mapVersion, MetadataPartitionCollection.create(keys)); + } else { + RetracePartitionException retraceError = + new RetracePartitionException("Unknown mapping partitioning strategy"); + diagnosticsHandler.error(new ExceptionDiagnostic(retraceError)); + throw retraceError; + } } - private String getSourceFileMapping(String className, String sourceFile) { + private String getSourceFileMapping( + String className, String obfuscatedClassName, String sourceFile) { if (sourceFile == null) { return ""; } return className + " -> " - + className + + (obfuscatedClassName == null ? className : obfuscatedClassName) + ":" + "\n # " + FileNameInformation.build(sourceFile).serialize() @@ -129,13 +188,11 @@ private final ProguardMapReaderWithFiltering lineReader; private final Map<String, List<String>> readSections = new LinkedHashMap<>(); - private final List<String> preamble; private List<String> currentList; public PartitionLineReader(ProguardMapReaderWithFiltering lineReader) { this.lineReader = lineReader; currentList = new ArrayList<>(); - preamble = currentList; } @Override @@ -166,11 +223,11 @@ implements ProguardMapPartitionerBuilder< ProguardMapPartitionerBuilderImpl, ProguardMapPartitionerOnClassNameToText> { - private ProguardMapProducer proguardMapProducer; - private Consumer<MappingPartition> mappingPartitionConsumer; - private final DiagnosticsHandler diagnosticsHandler; - private boolean allowEmptyMappedRanges = false; - private boolean allowExperimentalMapping = false; + protected ProguardMapProducer proguardMapProducer; + protected Consumer<MappingPartition> mappingPartitionConsumer; + protected final DiagnosticsHandler diagnosticsHandler; + protected boolean allowEmptyMappedRanges = false; + protected boolean allowExperimentalMapping = false; public ProguardMapPartitionerBuilderImpl(DiagnosticsHandler diagnosticsHandler) { this.diagnosticsHandler = diagnosticsHandler; @@ -211,7 +268,38 @@ mappingPartitionConsumer, diagnosticsHandler, allowEmptyMappedRanges, - allowExperimentalMapping); + allowExperimentalMapping, + MappingPartitionKeyStrategy.getDefaultStrategy()); + } + } + + // This class should not be exposed to clients and is only used from tests to control the + // partitioning strategy. + public static class ProguardMapPartitionerBuilderImplInternal + extends ProguardMapPartitionerBuilderImpl { + + private MappingPartitionKeyStrategy mappingPartitionKeyStrategy = + MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS; + + public ProguardMapPartitionerBuilderImplInternal(DiagnosticsHandler diagnosticsHandler) { + super(diagnosticsHandler); + } + + public ProguardMapPartitionerBuilderImplInternal setMappingPartitionKeyStrategy( + MappingPartitionKeyStrategy mappingPartitionKeyStrategy) { + this.mappingPartitionKeyStrategy = mappingPartitionKeyStrategy; + return this; + } + + @Override + public ProguardMapPartitionerOnClassNameToText build() { + return new ProguardMapPartitionerOnClassNameToText( + proguardMapProducer, + mappingPartitionConsumer, + diagnosticsHandler, + allowEmptyMappedRanges, + allowExperimentalMapping, + mappingPartitionKeyStrategy); } } }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapProducerInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapProducerInternal.java new file mode 100644 index 0000000..6650458 --- /dev/null +++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapProducerInternal.java
@@ -0,0 +1,29 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace.internal; + +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.retrace.ProguardMapProducer; +import java.io.IOException; +import java.io.InputStream; + +public class ProguardMapProducerInternal implements ProguardMapProducer { + + private final ClassNameMapper classNameMapper; + + public ProguardMapProducerInternal(ClassNameMapper classNameMapper) { + this.classNameMapper = classNameMapper; + } + + public ClassNameMapper getClassNameMapper() { + return classNameMapper; + } + + @Override + public InputStream get() throws IOException { + throw new Unreachable("Should never get on ProguardMapProducerInternal"); + } +}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java index 14f41ed..d791a5c 100644 --- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java +++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
@@ -8,8 +8,8 @@ import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.naming.LineReader; import com.android.tools.r8.naming.MapVersion; -import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker; -import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker.VerifyMappingFileHashResult; +import com.android.tools.r8.naming.ProguardMapChecker; +import com.android.tools.r8.naming.ProguardMapChecker.VerifyMappingFileHashResult; import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation; import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.retrace.InvalidMappingFileException;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java new file mode 100644 index 0000000..7e6e924 --- /dev/null +++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace.internal; + +import com.android.tools.r8.Keep; + +@Keep +public class RetracePartitionException extends RuntimeException { + + public RetracePartitionException(String message) { + super(message); + } + + public RetracePartitionException(Exception e) { + super(e); + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java index efff258..f9cda2f 100644 --- a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java +++ b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java
@@ -45,6 +45,13 @@ return get(member.getReference()); } + public boolean isMaterializableInAllContexts( + AppView<AppInfoWithLiveness> appView, DexClassAndMember<?, ?> member) { + AbstractValue assumeValue = get(member).getAssumeValue(); + return assumeValue.isSingleValue() + && assumeValue.asSingleValue().isMaterializableInAllContexts(appView); + } + public boolean isSideEffectFree(DexMember<?, ?> member) { return get(member).isSideEffectFree(); }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java index b430854..8715d7d 100644 --- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java +++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -278,19 +278,26 @@ MethodLookupResult lookupResult = graphLens().lookupInvokeDirect(method, getContext()); assert lookupResult.getType().isDirect(); DexMethod rewrittenMethod = lookupResult.getReference(); - BooleanBox seenMethod = new BooleanBox(); - appView - .contextIndependentDefinitionForWithResolutionResult(rewrittenMethod.getHolderType()) - .forEachClassResolutionResult( - holder -> { - DexClassAndMethod target = rewrittenMethod.lookupMemberOnClass(holder); - if (target != null) { - handleRewrittenMethodReference(rewrittenMethod, target); - seenMethod.set(); - } - }); - if (seenMethod.isFalse()) { - handleRewrittenMethodReference(rewrittenMethod, (DexClassAndMethod) null); + if (getContext().getHolder().originatesFromDexResource()) { + handleRewrittenMethodResolution( + rewrittenMethod, + appInfo().unsafeResolveMethodDueToDexFormat(rewrittenMethod), + SingleResolutionResult::getResolutionPair); + } else { + BooleanBox seenMethod = new BooleanBox(); + appView + .contextIndependentDefinitionForWithResolutionResult(rewrittenMethod.getHolderType()) + .forEachClassResolutionResult( + holder -> { + DexClassAndMethod target = rewrittenMethod.lookupMemberOnClass(holder); + if (target != null) { + handleRewrittenMethodReference(rewrittenMethod, target); + seenMethod.set(); + } + }); + if (seenMethod.isFalse()) { + handleRewrittenMethodReference(rewrittenMethod, (DexClassAndMethod) null); + } } }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java index 7a77e25..653e567 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -14,8 +14,11 @@ import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer; import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.ProgramConsumer; +import com.android.tools.r8.ProguardMapConsumer; import com.android.tools.r8.ResourceException; import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.naming.MultiProguardMapConsumer; +import com.android.tools.r8.naming.ProguardMapStringConsumer; import com.android.tools.r8.origin.Origin; import com.google.common.io.ByteStreams; import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; @@ -33,7 +36,7 @@ private boolean closed = false; private ProgramConsumer programConsumer = null; - private StringConsumer proguardMapConsumer = null; + private ProguardMapConsumer proguardMapConsumer = null; public AndroidAppConsumers() { // Nothing to do. @@ -45,7 +48,8 @@ public AndroidAppConsumers(InternalOptions options) { options.programConsumer = wrapProgramConsumer(options.programConsumer); - options.proguardMapConsumer = wrapProguardMapConsumer(options.proguardMapConsumer); + options.proguardMapConsumer = + wrapProguardMapConsumer(options.proguardMapConsumer, options.reporter); } public ProgramConsumer wrapProgramConsumer(ProgramConsumer consumer) { @@ -65,30 +69,37 @@ return programConsumer; } - public StringConsumer wrapProguardMapConsumer(StringConsumer consumer) { + public ProguardMapConsumer wrapProguardMapConsumer( + ProguardMapConsumer consumer, DiagnosticsHandler diagnosticsHandler) { assert proguardMapConsumer == null; if (consumer != null) { proguardMapConsumer = - new StringConsumer.ForwardingConsumer(consumer) { - StringBuilder stringBuilder = null; + MultiProguardMapConsumer.builder() + .addProguardMapConsumer(consumer) + .addProguardMapConsumer( + ProguardMapStringConsumer.builder() + .setStringConsumer( + new StringConsumer() { + StringBuilder stringBuilder = null; - @Override - public void accept(String string, DiagnosticsHandler handler) { - super.accept(string, handler); - if (stringBuilder == null) { - stringBuilder = new StringBuilder(); - } - stringBuilder.append(string); - } + @Override + public void accept(String string, DiagnosticsHandler handler) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append(string); + } - @Override - public void finished(DiagnosticsHandler handler) { - super.finished(handler); - if (stringBuilder != null) { - builder.setProguardMapOutputData(stringBuilder.toString()); - } - } - }; + @Override + public void finished(DiagnosticsHandler handler) { + if (stringBuilder != null) { + builder.setProguardMapOutputData(stringBuilder.toString()); + } + } + }) + .setDiagnosticsHandler(diagnosticsHandler) + .build()) + .build(); } return proguardMapConsumer; }
diff --git a/src/main/java/com/android/tools/r8/utils/CancelCompilationException.java b/src/main/java/com/android/tools/r8/utils/CancelCompilationException.java new file mode 100644 index 0000000..0070efe --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/CancelCompilationException.java
@@ -0,0 +1,6 @@ +// Copyright (c) 2023, 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; + +public class CancelCompilationException extends RuntimeException {}
diff --git a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java index 934bb8d..f5eea66 100644 --- a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java +++ b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
@@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Set; import java.util.function.Consumer; @@ -33,6 +34,12 @@ } } + public static <T extends Comparable<T>> Collection<T> sort(Collection<T> items) { + ArrayList<T> ts = new ArrayList<>(items); + Collections.sort(ts); + return ts; + } + public static <T> Collection<T> sort(Collection<T> items, Comparator<T> comparator) { ArrayList<T> ts = new ArrayList<>(items); ts.sort(comparator);
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java index ef5c3ca..959a25e 100644 --- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.InternalCompilationFailedExceptionUtils; import com.android.tools.r8.ResourceException; import com.android.tools.r8.StringConsumer; import com.android.tools.r8.Version; @@ -19,7 +20,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -87,13 +87,16 @@ private static CompilationFailedException failCompilation( Reporter reporter, Throwable topMostException) { return failWithFakeEntry( - reporter, topMostException, CompilationFailedException::new, AbortException.class); + reporter, + topMostException, + InternalCompilationFailedExceptionUtils::create, + AbortException.class); } public static <T extends Exception, A extends Exception> T failWithFakeEntry( DiagnosticsHandler diagnosticsHandler, Throwable topMostException, - BiFunction<String, Throwable, T> newException, + TriFunction<String, Throwable, Boolean, T> newException, Class<A> abortException) { // Find inner-most cause of the failure and compute origin, position and reported for the path. boolean hasBeenReported = false; @@ -122,8 +125,13 @@ innerMostCause.addSuppressed(topMostException); } + boolean cancelled = + topMostException instanceof CancelCompilationException + || innerMostCause instanceof CancelCompilationException; + assert !cancelled || topMostException == innerMostCause; + // If no abort is seen, the exception is not reported, so report it now. - if (!hasBeenReported) { + if (!cancelled && !hasBeenReported) { diagnosticsHandler.error(new ExceptionDiagnostic(innerMostCause, origin, position)); } @@ -136,7 +144,7 @@ message.append(", origin: ").append(origin); } // Create the final exception object. - T rethrow = newException.apply(message.toString(), innerMostCause); + T rethrow = newException.apply(message.toString(), innerMostCause, cancelled); // Replace its stack by the cause stack and insert version info at the top. String filename = "Version_" + Version.LABEL + ".java"; StackTraceElement versionElement = @@ -244,6 +252,8 @@ Origin origin, Position position, Supplier<T> action) { try { return action.get(); + } catch (CancelCompilationException e) { + throw e; } catch (RuntimeException e) { throw OriginAttachmentException.wrap(e, origin, position); }
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 f9bd041..8ce6323 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.utils.AndroidApiLevel.B; import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault; +import com.android.tools.r8.CancelCompilationChecker; import com.android.tools.r8.ClassFileConsumer; import com.android.tools.r8.CompilationMode; import com.android.tools.r8.DataResourceConsumer; @@ -16,6 +17,7 @@ import com.android.tools.r8.GlobalSyntheticsConsumer; import com.android.tools.r8.MapIdProvider; import com.android.tools.r8.ProgramConsumer; +import com.android.tools.r8.ProguardMapConsumer; import com.android.tools.r8.SourceFileProvider; import com.android.tools.r8.StringConsumer; import com.android.tools.r8.SyntheticInfoConsumer; @@ -74,6 +76,7 @@ import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.naming.MapVersion; import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer; +import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalOptions; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; import com.android.tools.r8.profile.art.ArtProfileOptions; @@ -165,6 +168,27 @@ return itemFactory; } + // Internal state signifying that the compilation is cancelled. + // The state can only ever transition from false to true. + private final AtomicBoolean cancelled = new AtomicBoolean(false); + public CancelCompilationChecker cancelCompilationChecker = null; + + public boolean checkIfCancelled() { + if (cancelCompilationChecker == null) { + assert !cancelled.get(); + return false; + } + if (cancelled.get()) { + return true; + } + if (cancelCompilationChecker.cancel()) { + cancelled.set(true); + return true; + } + // Return the cancelled value in case another thread has cancelled. + return cancelled.get(); + } + public boolean hasProguardConfiguration() { return proguardConfiguration != null; } @@ -862,6 +886,8 @@ private final OpenClosedInterfacesOptions openClosedInterfacesOptions = new OpenClosedInterfacesOptions(); private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions(); + private final RedundantBridgeRemovalOptions redundantBridgeRemovalOptions = + new RedundantBridgeRemovalOptions(this); private final KotlinOptimizationOptions kotlinOptimizationOptions = new KotlinOptimizationOptions(); private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions(); @@ -930,6 +956,10 @@ return cfCodeAnalysisOptions; } + public RedundantBridgeRemovalOptions getRedundantBridgeRemovalOptions() { + return redundantBridgeRemovalOptions; + } + public DumpInputFlags getDumpInputFlags() { return dumpInputFlags; } @@ -1018,7 +1048,7 @@ // If null, no proguard map needs to be computed. // If non null it must be and passed to the consumer. - public StringConsumer proguardMapConsumer = null; + public ProguardMapConsumer proguardMapConsumer = null; // If null, no usage information needs to be computed. // If non-null, it must be and is passed to the consumer. @@ -2884,8 +2914,7 @@ } public boolean canHaveNonReboundConstructorInvoke() { - // TODO(b/246679983): Turned off while diagnosing b/246679983. - return false && isGeneratingDex() && minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L); + return isGeneratingDex() && minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L); } // b/238399429 Some art 6 vms have issues with multiple monitors in the same method
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java index a561718..9885ab6 100644 --- a/src/main/java/com/android/tools/r8/utils/StringUtils.java +++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -248,7 +248,11 @@ } public static String unixLines(String... lines) { - return lines(Arrays.asList(lines), "\n"); + return unixLines(Arrays.asList(lines)); + } + + public static String unixLines(List<String> lines) { + return lines(lines, "\n"); } public static String withNativeLineSeparator(String s) {
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java index 3947cae..dc7db26 100644 --- a/src/main/java/com/android/tools/r8/utils/Timing.java +++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -62,11 +62,60 @@ return Timing.EMPTY; } + private static class TimingWithCancellation extends Timing { + private final InternalOptions options; + private final Timing timing; + + TimingWithCancellation(InternalOptions options, Timing timing) { + super("<cancel>", false); + this.options = options; + this.timing = timing; + } + + @Override + public TimingMerger beginMerger(String title, int numberOfThreads) { + return timing.beginMerger(title, numberOfThreads); + } + + @Override + public void begin(String title) { + if (options.checkIfCancelled()) { + throw new CancelCompilationException(); + } + timing.begin(title); + } + + @Override + public <E extends Exception> void time(String title, ThrowingAction<E> action) throws E { + timing.time(title, action); + } + + @Override + public <T, E extends Exception> T time(String title, ThrowingSupplier<T, E> supplier) throws E { + return timing.time(title, supplier); + } + + @Override + public void end() { + timing.end(); + } + + @Override + public void report() { + timing.report(); + } + } + public static Timing create(String title, InternalOptions options) { // We also create a timer when running assertions to validate wellformedness of the node stack. - return options.printTimes || InternalOptions.assertionsEnabled() - ? new Timing(title, options.printMemory) - : Timing.empty(); + Timing timing = + options.printTimes || InternalOptions.assertionsEnabled() + ? new Timing(title, options.printMemory) + : Timing.empty(); + if (options.cancelCompilationChecker != null) { + return new TimingWithCancellation(options, timing); + } + return timing; } public static Timing create(String title, boolean printMemory) {
diff --git a/src/test/java/com/android/tools/r8/CommandTestBase.java b/src/test/java/com/android/tools/r8/CommandTestBase.java index 6c72b39..1ea2212 100644 --- a/src/test/java/com/android/tools/r8/CommandTestBase.java +++ b/src/test/java/com/android/tools/r8/CommandTestBase.java
@@ -96,7 +96,7 @@ try { command.getReporter().failIfPendingErrors(); } catch (RuntimeException e) { - throw new CompilationFailedException(); + throw InternalCompilationFailedExceptionUtils.createForTesting(); } }); fail("Failure expected"); @@ -144,7 +144,7 @@ try { command.getReporter().failIfPendingErrors(); } catch (RuntimeException e) { - throw new CompilationFailedException(); + throw InternalCompilationFailedExceptionUtils.createForTesting(); } }); fail("Failure expected"); @@ -190,7 +190,7 @@ try { command.getReporter().failIfPendingErrors(); } catch (RuntimeException e) { - throw new CompilationFailedException(); + throw InternalCompilationFailedExceptionUtils.createForTesting(); } }); fail("Expected failure"); @@ -209,7 +209,7 @@ try { command.getReporter().failIfPendingErrors(); } catch (RuntimeException e) { - throw new CompilationFailedException(); + throw InternalCompilationFailedExceptionUtils.createForTesting(); } }); fail("Expected failure");
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java index f22ce21..87b5338 100644 --- a/src/test/java/com/android/tools/r8/D8CommandTest.java +++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -33,6 +33,7 @@ import com.android.tools.r8.utils.ExtractMarkerUtils; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.ZipUtils; import com.google.common.collect.ImmutableList; @@ -812,7 +813,8 @@ // Verify we found the same rule. StartupProfile startupProfile = startupProfileBuilder.build(); - Collection<StartupProfileRule> startupItems = startupProfile.getRules(); + Collection<StartupProfileRule> startupItems = + ListUtils.newArrayList(consumer -> startupProfile.forEachRule(consumer::accept)); assertEquals(1, startupItems.size()); StartupProfileRule startupItem = startupItems.iterator().next(); startupItem.accept(
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java index 2808f05..444d14b 100644 --- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -197,7 +197,7 @@ return new ExternalR8TestCompileResult( getState(), outputJar, processResult, proguardMap, getMinApiLevel(), getOutputMode()); } catch (IOException e) { - throw new CompilationFailedException(e); + throw InternalCompilationFailedExceptionUtils.createForTesting(e); } }
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java index e2d8b20..4b42d31 100644 --- a/src/test/java/com/android/tools/r8/L8CommandTest.java +++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.StringConsumer.FileConsumer; import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; import com.android.tools.r8.dex.Marker.Tool; +import com.android.tools.r8.naming.ProguardMapStringConsumer; import com.android.tools.r8.origin.EmbeddedOrigin; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.references.Reference; @@ -186,8 +187,10 @@ assertNotNull(parsedCommand.getR8Command()); InternalOptions internalOptions = parsedCommand.getR8Command().getInternalOptions(); assertNotNull(internalOptions); - assertTrue(internalOptions.proguardMapConsumer instanceof StringConsumer.FileConsumer); - FileConsumer proguardMapConsumer = (FileConsumer) internalOptions.proguardMapConsumer; + assertTrue(internalOptions.proguardMapConsumer instanceof ProguardMapStringConsumer); + ProguardMapStringConsumer mapStringConsumer = + (ProguardMapStringConsumer) internalOptions.proguardMapConsumer; + FileConsumer proguardMapConsumer = (FileConsumer) mapStringConsumer.getStringConsumer(); assertEquals(pgMap, proguardMapConsumer.getOutputPath()); }
diff --git a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java index f827134..e38ecf7 100644 --- a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java +++ b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
@@ -25,6 +25,7 @@ @RunWith(Parameterized.class) public class ProguardMapMarkerTest extends TestBase { + private static final int EXPECTED_NUMBER_OF_KEYS_DEX = 6; private static final int EXPECTED_NUMBER_OF_KEYS_CF = 5; private static final String CLASS_FILE = @@ -85,11 +86,10 @@ .setMinApiLevel(minApiLevel.getLevel()) .setProguardMapConsumer( ToolHelper.consumeString( - proguardMap -> { - proguardMapIds.fromMap = - verifyMarkersGetPgMapId( - proguardMap, minApiLevel.getLevel(), EXPECTED_NUMBER_OF_KEYS_DEX); - })) + proguardMap -> + proguardMapIds.fromMap = + verifyMarkersGetPgMapId( + proguardMap, minApiLevel.getLevel(), EXPECTED_NUMBER_OF_KEYS_DEX))) .build()); verifyProguardMapIds(proguardMapIds); } @@ -154,21 +154,28 @@ } String key = comment.substring(0, colonIndex).trim(); String value = comment.substring(colonIndex + 1).trim(); - if (key.equals(ProguardMapSupplier.MARKER_KEY_COMPILER)) { - assertEquals("R8", value); - } else if (key.equals(ProguardMapSupplier.MARKER_KEY_COMPILER_VERSION)) { - assertEquals(Version.LABEL, value); - } else if (key.equals(ProguardMapSupplier.MARKER_KEY_MIN_API)) { - assertNotNull(minApiLevel); - assertEquals(minApiLevel.intValue(), Integer.parseInt(value)); - } else if (key.equals(ProguardMapSupplier.MARKER_KEY_COMPILER_HASH)) { - assertEquals(VersionProperties.INSTANCE.getSha(), value); - } else if (key.equals(ProguardMapSupplier.MARKER_KEY_PG_MAP_ID)) { - proguardMapId = value; - } else if (key.equals(ProguardMapSupplier.MARKER_KEY_PG_MAP_HASH)) { - proguardMapHash = value; - } else { - continue; + switch (key) { + case "compiler": + assertEquals("R8", value); + break; + case "compiler_version": + assertEquals(Version.LABEL, value); + break; + case "min_api": + assertNotNull(minApiLevel); + assertEquals(minApiLevel.intValue(), Integer.parseInt(value)); + break; + case "compiler_hash": + assertEquals(VersionProperties.INSTANCE.getSha(), value); + break; + case "pg_map_id": + proguardMapId = value; + break; + case "pg_map_hash": + proguardMapHash = value; + break; + default: + continue; } assertFalse(keysFound.contains(key)); keysFound.add(key);
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java index d91646a..ac6e335 100644 --- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java +++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -114,14 +114,14 @@ ProcessBuilder pbuilder = new ProcessBuilder(command); ProcessResult result = ToolHelper.runProcess(pbuilder, getStdoutForTesting()); if (result.exitCode != 0) { - throw new CompilationFailedException(result.toString()); + throw InternalCompilationFailedExceptionUtils.createForTesting(result.toString()); } String proguardMap = Files.exists(mapFile) ? FileUtils.readTextFile(mapFile, Charsets.UTF_8) : ""; return new ProguardTestCompileResult( result, getState(), outJar, getMinApiLevel(), proguardMap); } catch (IOException e) { - throw new CompilationFailedException(e); + throw InternalCompilationFailedExceptionUtils.createForTesting(e); } }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java index 45322ff..74d850c 100644 --- a/src/test/java/com/android/tools/r8/TestBase.java +++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -2026,4 +2026,15 @@ } return byteRead1 == byteRead2; } + + protected void assertListsAreEqual(List<String> expected, List<String> actual) { + int minLines = Math.min(expected.size(), actual.size()); + for (int i = 0; i < minLines; i++) { + assertEquals(expected.get(i), actual.get(i)); + } + if (expected.size() != actual.size()) { + assertEquals( + "", expected.size() > actual.size() ? expected.get(minLines) : actual.get(minLines)); + } + } }
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java index c22f43e..206a8ab 100644 --- a/src/test/java/com/android/tools/r8/TestParameters.java +++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -58,8 +58,7 @@ * runtime error. */ public boolean canHaveNonReboundConstructorInvoke() { - // TODO(b/246679983): Turned off while diagnosing b/246679983. - return false && isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L); + return isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L); } public boolean canUseDefaultAndStaticInterfaceMethods() {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/ProtectedBridgeRemovalTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/ProtectedBridgeRemovalTest.java new file mode 100644 index 0000000..6747779 --- /dev/null +++ b/src/test/java/com/android/tools/r8/bridgeremoval/ProtectedBridgeRemovalTest.java
@@ -0,0 +1,110 @@ +// Copyright (c) 2023, 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.bridgeremoval; + +import com.android.tools.r8.NeverInline; +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.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ProtectedBridgeRemovalTest extends TestBase { + + private static final String A_DESCRIPTOR = "LA;"; + private static final String EXPECTED_OUTPUT = StringUtils.lines("IllegalAccessError", "A.foo()"); + + private static List<byte[]> programClassFileData; + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @BeforeClass + public static void setup() throws Exception { + programClassFileData = + ImmutableList.of( + transformer(Main.class) + .replaceClassDescriptorInMethodInstructions(descriptor(A.class), A_DESCRIPTOR) + .transform(), + transformer(A.class).setClassDescriptor(A_DESCRIPTOR).transform(), + transformer(B.class) + .replaceClassDescriptorInMethodInstructions(descriptor(A.class), A_DESCRIPTOR) + .setBridge(B.class.getDeclaredMethod("foo")) + .setSuper(A_DESCRIPTOR) + .transform()); + } + + @Test + public void testJvm() throws Exception { + parameters.assumeJvmTestParameters(); + testForJvm(parameters) + .addProgramClassFileData(programClassFileData) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClassFileData(programClassFileData) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .enableNoVerticalClassMergingAnnotations() + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + public static class Main { + + public static void main(String[] args) { + for (A a : new A[] {new A(), new B()}) { + if (a instanceof B) { + B b = (B) a; + b.foo(); + } else { + try { + a.foo(); + } catch (IllegalAccessError e) { + System.out.println("IllegalAccessError"); + } + } + } + } + } + + @NoVerticalClassMerging + public static class /*other package.*/ A { + + @NeverInline + protected void foo() { + System.out.println("A.foo()"); + } + } + + public static class B extends A { + + @NeverInline + @Override + protected /*bridge*/ void foo() { + super.foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java index 1bbaf31..71c9dc4 100644 --- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java +++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.compilerapi.androidplatformbuild.AndroidPlatformBuildApiTest; import com.android.tools.r8.compilerapi.artprofiles.ArtProfilesForRewritingApiTest; import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest; +import com.android.tools.r8.compilerapi.cancelcompilationchecker.CancelCompilationCheckerTest; import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest; import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest; import com.android.tools.r8.compilerapi.diagnostics.ProguardKeepRuleDiagnosticsApiTest; @@ -62,7 +63,7 @@ ExtractMarkerApiTest.ApiTest.class); private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY = - ImmutableList.of(); + ImmutableList.of(CancelCompilationCheckerTest.ApiTest.class); private final TemporaryFolder temp;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/cancelcompilationchecker/CancelCompilationCheckerTest.java b/src/test/java/com/android/tools/r8/compilerapi/cancelcompilationchecker/CancelCompilationCheckerTest.java new file mode 100644 index 0000000..8afacb4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/compilerapi/cancelcompilationchecker/CancelCompilationCheckerTest.java
@@ -0,0 +1,123 @@ +// Copyright (c) 2023, 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.compilerapi.cancelcompilationchecker; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.tools.r8.CancelCompilationChecker; +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.D8; +import com.android.tools.r8.D8Command; +import com.android.tools.r8.DexIndexedConsumer; +import com.android.tools.r8.R8; +import com.android.tools.r8.R8Command; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.compilerapi.CompilerApiTest; +import com.android.tools.r8.compilerapi.CompilerApiTestRunner; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.IntBox; +import java.util.function.BooleanSupplier; +import org.junit.Test; + +public class CancelCompilationCheckerTest extends CompilerApiTestRunner { + + public CancelCompilationCheckerTest(TestParameters parameters) { + super(parameters); + } + + @Override + public Class<? extends CompilerApiTest> binaryTestClass() { + return ApiTest.class; + } + + @Test + public void testD8() throws Exception { + // Use an integer box to delay the cancel until some time internal in the compiler. + IntBox i = new IntBox(); + try { + new ApiTest(ApiTest.PARAMETERS).runD8(() -> i.incrementAndGet() > 10); + fail("excepted cancelled"); + } catch (CompilationFailedException e) { + assertTrue(e.wasCancelled()); + } + } + + @Test + public void testR8() throws Exception { + // Use an integer box to delay the cancel until some time internal in the compiler. + IntBox i = new IntBox(); + try { + new ApiTest(ApiTest.PARAMETERS).runR8(() -> i.incrementAndGet() > 20); + fail("excepted cancelled"); + } catch (CompilationFailedException e) { + assertTrue(e.wasCancelled()); + } + } + + public static class ApiTest extends CompilerApiTest { + + public ApiTest(Object parameters) { + super(parameters); + } + + public void runD8(BooleanSupplier supplier) throws Exception { + D8.run( + D8Command.builder() + .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown()) + .addLibraryFiles(getJava8RuntimeJar()) + .setProgramConsumer(DexIndexedConsumer.emptyConsumer()) + .setCancelCompilationChecker( + new CancelCompilationChecker() { + @Override + public boolean cancel() { + return supplier.getAsBoolean(); + } + }) + .build()); + } + + public void runR8(BooleanSupplier supplier) throws Exception { + R8.run( + R8Command.builder() + .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown()) + .addLibraryFiles(getJava8RuntimeJar()) + .setDisableTreeShaking(true) + .setDisableMinification(true) + .setProgramConsumer(DexIndexedConsumer.emptyConsumer()) + .setCancelCompilationChecker( + new CancelCompilationChecker() { + @Override + public boolean cancel() { + return supplier.getAsBoolean(); + } + }) + .build()); + } + + @Test + public void testD8() throws Exception { + try { + runD8(() -> true); + } catch (CompilationFailedException e) { + if (e.wasCancelled()) { + return; + } + } + throw new AssertionError("expected cancelled"); + } + + @Test + public void testR8() throws Exception { + try { + runR8(() -> true); + } catch (CompilationFailedException e) { + if (e.wasCancelled()) { + return; + } + } + throw new AssertionError("expected cancelled"); + } + } +}
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 455bfe4..0373c01 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java +++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.utils.AndroidApiLevel; import java.io.IOException; import java.nio.file.Path; +import java.util.List; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,7 +35,7 @@ } private int getNumberOfDebugInfos(Path file) throws IOException { - DexSection[] dexSections = DexParser.parseMapFrom(file); + List<DexSection> dexSections = DexParser.parseMapFrom(file); for (DexSection dexSection : dexSections) { if (dexSection.type == Constants.TYPE_DEBUG_INFO_ITEM) { return dexSection.length;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java index 2b5de3f..0ad53ed 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -516,7 +516,8 @@ GenerateLintFiles.createForTesting( libraryDesugaringSpecification.getSpecification(), libraryDesugaringSpecification.getDesugarJdkLibs(), - out); + out, + ToolHelper.getAndroidJar(AndroidApiLevel.U)); AndroidApiLevel compileApi = desugaredApi.run(); return new CodeInspector( out.resolve("compile_api_level_" + compileApi.getLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java index 446b84e..aeedca9 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -41,6 +41,8 @@ @RunWith(Parameterized.class) public class LintFilesTest extends DesugaredLibraryTestBase { + private static final String ANDROID_JAR_34 = "third_party/android_jar/lib-v34/android.jar"; + private final LibraryDesugaringSpecification libraryDesugaringSpecification; private List<String> lintContents; @@ -237,12 +239,19 @@ spec == JDK8 ? ToolHelper.DESUGARED_JDK_8_LIB_JAR : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar(); - new GenerateHtmlDoc(spec.getSpecification().toString(), jdkLibJar.toString(), html.toString()) + new GenerateHtmlDoc( + spec.getSpecification().toString(), + jdkLibJar.toString(), + html.toString(), + ANDROID_JAR_34) .run(spec + ".html"); Path lint = top.resolve("lint_" + spec); Files.createDirectories(lint); new GenerateLintFiles( - spec.getSpecification().toString(), jdkLibJar.toString(), lint.toString()) + spec.getSpecification().toString(), + jdkLibJar.toString(), + lint.toString(), + ANDROID_JAR_34) .run(); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java index dd7c1cf..eebc725 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -12,6 +12,7 @@ import static org.junit.Assert.assertEquals; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMember; @@ -129,7 +130,8 @@ @Test public void test() throws Exception { SupportedClasses supportedClasses = - new SupportedClassesGenerator(new InternalOptions()) + new SupportedClassesGenerator( + new InternalOptions(), ToolHelper.getAndroidJar(AndroidApiLevel.U)) .run(librarySpecification.getDesugarJdkLibs(), librarySpecification.getSpecification()); for (AndroidApiLevel api : getRelevantApiLevels()) { @@ -205,10 +207,6 @@ && api.isLessThan(AndroidApiLevel.T)) { expectedFailures.addAll(FAILURES_TO_ARRAY); } - if (librarySpecification == JDK8 && api.isLessThan(AndroidApiLevel.T)) { - // Interestingly that was added somehow to JDK8 desugared library at some point... - expectedFailures.addAll(FAILURES_TO_ARRAY); - } if (jdk11NonMinimal && api.isGreaterThanOrEqualTo(AndroidApiLevel.O) && api.isLessThan(AndroidApiLevel.U)) { @@ -218,10 +216,6 @@ if (librarySpecification == JDK8 && api.isGreaterThanOrEqualTo(AndroidApiLevel.O)) { expectedFailures.addAll(FAILURES_ERA); } - if (jdk11NonMinimal && api.isGreaterThanOrEqualTo(AndroidApiLevel.T)) { - // The method is present, but not in android.jar... - expectedFailures.addAll(FAILURES_ERA); - } return expectedFailures; } }
diff --git a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java index 28a01e0..25b028a 100644 --- a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java +++ b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
@@ -33,6 +33,7 @@ import com.android.tools.r8.utils.BitUtils; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.DexVersion; +import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.ZipUtils; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; @@ -106,7 +107,7 @@ } @Test - public void testD8Experiment() throws Exception { + public void testD8ExperimentSimpleMerge() throws Exception { Path outputFromDexing = testForD8(Backend.DEX) .addProgramFiles(inputA) @@ -116,10 +117,23 @@ .compile() .writeToZip(); validateSingleContainerDex(outputFromDexing); + + Path outputFromMerging = + testForD8(Backend.DEX) + .addProgramFiles(outputFromDexing) + .addOptionsModification( + options -> options.getTestingOptions().dexContainerExperiment = true) + .compile() + .writeToZip(); + validateSingleContainerDex(outputFromMerging); + + // Identical DEX after re-merging. + assertArrayEquals( + unzipContent(outputFromDexing).get(0), unzipContent(outputFromMerging).get(0)); } @Test - public void testD8Experiment2() throws Exception { + public void testD8ExperimentMoreMerge() throws Exception { Path outputA = testForD8(Backend.DEX) .addProgramFiles(inputA) @@ -139,6 +153,27 @@ .compile() .writeToZip(); validateSingleContainerDex(outputB); + + Path outputBoth = + testForD8(Backend.DEX) + .addProgramFiles(inputA, inputB) + .setMinApi(AndroidApiLevel.L) + .addOptionsModification( + options -> options.getTestingOptions().dexContainerExperiment = true) + .compile() + .writeToZip(); + validateSingleContainerDex(outputBoth); + + Path outputMerged = + testForD8(Backend.DEX) + .addProgramFiles(outputA, outputB) + .addOptionsModification( + options -> options.getTestingOptions().dexContainerExperiment = true) + .compile() + .writeToZip(); + validateSingleContainerDex(outputMerged); + + assertArrayEquals(unzipContent(outputBoth).get(0), unzipContent(outputMerged).get(0)); } private void validateDex(Path output, int expectedDexes, DexVersion expectedVersion) @@ -168,10 +203,14 @@ int dataSize = buffer.getInt(offset + DATA_SIZE_OFFSET); int dataOffset = buffer.getInt(offset + DATA_OFF_OFFSET); int file_size = buffer.getInt(offset + FILE_SIZE_OFFSET); - if (!expectedVersion.isContainerDex()) { + if (expectedVersion.isContainerDex()) { + assertEquals(0, dataSize); + assertEquals(0, dataOffset); + } else { assertEquals(file_size, dataOffset + dataSize); } offset += expectedVersion.isContainerDex() ? file_size : dataOffset + dataSize; + assertEquals(file_size, offset - ListUtils.last(sections)); } assertEquals(buffer.capacity(), offset);
diff --git a/src/test/java/com/android/tools/r8/naming/MappingFileHashTest.java b/src/test/java/com/android/tools/r8/naming/MappingFileHashTest.java index efc6cf7..1468bd8 100644 --- a/src/test/java/com/android/tools/r8/naming/MappingFileHashTest.java +++ b/src/test/java/com/android/tools/r8/naming/MappingFileHashTest.java
@@ -8,8 +8,7 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; -import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker; -import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker.VerifyMappingFileHashResult; +import com.android.tools.r8.naming.ProguardMapChecker.VerifyMappingFileHashResult; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized;
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 0446cd5..82a318d 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
@@ -11,6 +11,7 @@ import com.android.tools.r8.SingleTestRunResult; import com.android.tools.r8.ToolHelper.DexVm; import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.retrace.MappingSupplier; import com.android.tools.r8.retrace.ProguardMapProducer; import com.android.tools.r8.retrace.ProguardMappingSupplier; import com.android.tools.r8.retrace.Retrace; @@ -385,17 +386,21 @@ } public StackTrace retrace(String map, boolean allowExperimentalMapping) { + return retrace( + ProguardMappingSupplier.builder() + .setProguardMapProducer(ProguardMapProducer.fromString(map)) + .setAllowExperimental(allowExperimentalMapping) + .build()); + } + + public StackTrace retrace(MappingSupplier<?> mappingSupplier) { Box<List<String>> box = new Box<>(); List<String> stackTrace = stackTraceLines.stream().map(line -> line.originalLine).collect(Collectors.toList()); stackTrace.add(0, exceptionLine); Retrace.run( RetraceCommand.builder() - .setMappingSupplier( - ProguardMappingSupplier.builder() - .setProguardMapProducer(ProguardMapProducer.fromString(map)) - .setAllowExperimental(allowExperimentalMapping) - .build()) + .setMappingSupplier(mappingSupplier) .setStackTrace(stackTrace) .setRetracedStackTraceConsumer(box::set) .build());
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java index 2d38860..c83d81d 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java
@@ -75,15 +75,22 @@ } private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) { - inspect(profileInspector, inspector, false); + inspect(profileInspector, inspector, false, false); } private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) { - inspect(profileInspector, inspector, parameters.isCfRuntime()); + inspect( + profileInspector, + inspector, + parameters.canHaveNonReboundConstructorInvoke(), + parameters.isCfRuntime()); } public void inspect( - ArtProfileInspector profileInspector, CodeInspector inspector, boolean canUseLambdas) { + ArtProfileInspector profileInspector, + CodeInspector inspector, + boolean canHaveNonReboundConstructorInvoke, + boolean canUseLambdas) { ClassSubject mainClassSubject = inspector.clazz(Main.class); assertThat(mainClassSubject, isPresent()); @@ -96,21 +103,26 @@ assertThat(lambdaClassSubject, notIf(isPresent(), canUseLambdas)); MethodSubject lambdaInitializerSubject = lambdaClassSubject.uniqueInstanceInitializer(); - assertThat(lambdaInitializerSubject, notIf(isPresent(), canUseLambdas)); + assertThat( + lambdaInitializerSubject, + notIf(isPresent(), canHaveNonReboundConstructorInvoke || canUseLambdas)); MethodSubject lambdaMainMethodSubject = lambdaClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual); assertThat(lambdaMainMethodSubject, notIf(isPresent(), canUseLambdas)); if (canUseLambdas) { - profileInspector.assertContainsMethodRule(mainMethodSubject).assertContainsNoOtherRules(); + profileInspector.assertContainsMethodRule(mainMethodSubject); } else { profileInspector .assertContainsClassRules(lambdaClassSubject) - .assertContainsMethodRules( - mainMethodSubject, lambdaInitializerSubject, lambdaMainMethodSubject) - .assertContainsNoOtherRules(); + .assertContainsMethodRules(mainMethodSubject, lambdaMainMethodSubject) + .applyIf( + !canHaveNonReboundConstructorInvoke, + i -> i.assertContainsMethodRule(lambdaInitializerSubject)); } + + profileInspector.assertContainsNoOtherRules(); } static class Main {
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java index 7d9e9ab..577518d 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java
@@ -104,17 +104,22 @@ private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) throws Exception { - inspect(profileInspector, inspector, parameters.canUseNestBasedAccessesWhenDesugaring()); + inspect(profileInspector, inspector, false, parameters.canUseNestBasedAccessesWhenDesugaring()); } private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) throws Exception { - inspect(profileInspector, inspector, parameters.canUseNestBasedAccesses()); + inspect( + profileInspector, + inspector, + parameters.canHaveNonReboundConstructorInvoke(), + parameters.canUseNestBasedAccesses()); } private void inspect( ArtProfileInspector profileInspector, CodeInspector inspector, + boolean canHaveNonReboundConstructorInvoke, boolean canUseNestBasedAccesses) throws Exception { ClassSubject nestMemberClassSubject = inspector.clazz(NestMember.class); @@ -128,7 +133,7 @@ syntheticConstructorArgumentClassSubject, notIf(isPresent(), canUseNestBasedAccesses)); MethodSubject instanceInitializer = nestMemberClassSubject.init(); - assertThat(instanceInitializer, isPresent()); + assertThat(instanceInitializer, notIf(isPresent(), canHaveNonReboundConstructorInvoke)); MethodSubject instanceInitializerWithSyntheticArgumentSubject = syntheticConstructorArgumentClassSubject.isPresent()
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java index 5f1886d..6d03704 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -149,6 +149,7 @@ profileInspector, inspector, SyntheticItemsTestUtils.syntheticRecordTagClass(), + false, parameters.canUseNestBasedAccessesWhenDesugaring(), parameters.canUseRecordsWhenDesugaring()); } @@ -158,6 +159,7 @@ profileInspector, inspector, RECORD_REFERENCE, + parameters.canHaveNonReboundConstructorInvoke(), parameters.canUseNestBasedAccesses(), parameters.canUseRecords()); } @@ -166,6 +168,7 @@ ArtProfileInspector profileInspector, CodeInspector inspector, ClassReference recordClassReference, + boolean canHaveNonReboundConstructorInvoke, boolean canUseNestBasedAccesses, boolean canUseRecords) { ClassSubject mainClassSubject = inspector.clazz(MAIN_REFERENCE); @@ -177,11 +180,14 @@ ClassSubject recordTagClassSubject = inspector.clazz(recordClassReference); assertThat(recordTagClassSubject, notIf(isPresent(), canUseRecords)); if (!canUseRecords) { - assertEquals(1, recordTagClassSubject.allMethods().size()); + assertEquals( + canHaveNonReboundConstructorInvoke ? 0 : 1, recordTagClassSubject.allMethods().size()); } MethodSubject recordTagInstanceInitializerSubject = recordTagClassSubject.init(); - assertThat(recordTagInstanceInitializerSubject, notIf(isPresent(), canUseRecords)); + assertThat( + recordTagInstanceInitializerSubject, + notIf(isPresent(), canHaveNonReboundConstructorInvoke || canUseRecords)); ClassSubject personRecordClassSubject = inspector.clazz(PERSON_REFERENCE); assertThat(personRecordClassSubject, isPresent()); @@ -290,11 +296,13 @@ hashCodeHelperClassSubject, toStringHelperClassSubject) .assertContainsMethodRules( - recordTagInstanceInitializerSubject, equalsHelperMethodSubject, getFieldsAsObjectsMethodSubject, hashCodeHelperMethodSubject, - toStringHelperMethodSubject)) + toStringHelperMethodSubject) + .applyIf( + !canHaveNonReboundConstructorInvoke, + j -> j.assertContainsMethodRule(recordTagInstanceInitializerSubject))) .assertContainsNoOtherRules(); } }
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java index 385f94b..ef6b3a3 100644 --- a/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java +++ b/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java
@@ -68,6 +68,7 @@ public void inspect( ArtProfileInspector profileInspector, CodeInspector inspector, + boolean canHaveNonReboundConstructorInvoke, boolean canUseLambdas, boolean canAccessModifyLambdaImplementationMethods) { ClassSubject mainClassSubject = inspector.clazz(Main.class); @@ -106,7 +107,9 @@ assertThat(lambdaClassSubject, notIf(isPresent(), canUseLambdas)); MethodSubject lambdaInitializerSubject = lambdaClassSubject.uniqueInstanceInitializer(); - assertThat(lambdaInitializerSubject, notIf(isPresent(), canUseLambdas)); + assertThat( + lambdaInitializerSubject, + notIf(isPresent(), canHaveNonReboundConstructorInvoke || canUseLambdas)); MethodSubject lambdaMainMethodSubject = lambdaClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual); @@ -119,7 +122,9 @@ MethodSubject otherLambdaInitializerSubject = otherLambdaClassSubject.uniqueInstanceInitializer(); - assertThat(otherLambdaInitializerSubject, notIf(isPresent(), canUseLambdas)); + assertThat( + otherLambdaInitializerSubject, + notIf(isPresent(), canHaveNonReboundConstructorInvoke || canUseLambdas)); MethodSubject otherLambdaMainMethodSubject = otherLambdaClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual); @@ -149,8 +154,12 @@ // interface method implementation does not need to be included in the profile. profileInspector .assertContainsClassRules(lambdaClassSubject, otherLambdaClassSubject) - .assertContainsMethodRules( - mainMethodSubject, lambdaInitializerSubject, otherLambdaInitializerSubject) + .assertContainsMethodRules(mainMethodSubject) + .applyIf( + !canHaveNonReboundConstructorInvoke, + i -> + i.assertContainsMethodRules( + lambdaInitializerSubject, otherLambdaInitializerSubject)) .assertContainsNoOtherRules(); break; case IMPLEMENTATION_METHOD: @@ -221,11 +230,16 @@ } private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) { - artProfileInputOutput.inspect(profileInspector, inspector, false, true); + artProfileInputOutput.inspect(profileInspector, inspector, false, false, true); } private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) { - artProfileInputOutput.inspect(profileInspector, inspector, parameters.isCfRuntime(), false); + artProfileInputOutput.inspect( + profileInspector, + inspector, + parameters.canHaveNonReboundConstructorInvoke(), + parameters.isCfRuntime(), + false); } static class Main {
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/R8ZipContainerMappingFileTest.java b/src/test/java/com/android/tools/r8/retrace/partition/R8ZipContainerMappingFileTest.java new file mode 100644 index 0000000..211121c --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/partition/R8ZipContainerMappingFileTest.java
@@ -0,0 +1,154 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace.partition; + +import static com.android.tools.r8.naming.retrace.StackTrace.isSame; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.ProguardMapConsumer; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ProguardMapPartitionConsumer; +import com.android.tools.r8.naming.retrace.StackTrace; +import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; +import com.android.tools.r8.retrace.PartitionMappingSupplier; +import com.android.tools.r8.retrace.partition.testclasses.R8ZipContainerMappingFileTestClasses; +import com.android.tools.r8.retrace.partition.testclasses.R8ZipContainerMappingFileTestClasses.Main; +import com.android.tools.r8.utils.ZipUtils.ZipBuilder; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class R8ZipContainerMappingFileTest extends TestBase { + + private static final String SOURCE_FILE = "R8ZipContainerMappingFileTestClasses.java"; + + private final StackTrace EXPECTED = + StackTrace.builder() + .add( + StackTraceLine.builder() + .setClassName(typeName(R8ZipContainerMappingFileTestClasses.Thrower.class)) + .setMethodName("throwError") + .setFileName(SOURCE_FILE) + .setLineNumber(13) + .build()) + .add( + StackTraceLine.builder() + .setClassName(typeName(R8ZipContainerMappingFileTestClasses.Main.class)) + .setMethodName("main") + .setFileName(SOURCE_FILE) + .setLineNumber(21) + .build()) + .build(); + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addInnerClasses(R8ZipContainerMappingFileTestClasses.class) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(RuntimeException.class) + .inspectStackTrace( + stackTrace -> { + assertThat(stackTrace, isSame(EXPECTED)); + }); + } + + @Test + public void testR8() throws Exception { + Path pgMapFile = temp.newFile("mapping.zip").toPath(); + DiagnosticsHandler diagnosticsHandler = new TestDiagnosticMessagesImpl(); + ProguardMapConsumer partitionZipConsumer = + createPartitionZipConsumer(pgMapFile, diagnosticsHandler); + StackTrace originalStackTrace = + testForR8(parameters.getBackend()) + .addInnerClasses(R8ZipContainerMappingFileTestClasses.class) + .setMinApi(parameters) + .addKeepMainRule(Main.class) + .addKeepAttributeSourceFile() + .addKeepAttributeLineNumberTable() + .addOptionsModification(options -> options.proguardMapConsumer = partitionZipConsumer) + .run(parameters.getRuntime(), Main.class) + .assertFailureWithErrorThatThrows(RuntimeException.class) + .getOriginalStackTrace(); + + assertTrue(Files.exists(pgMapFile)); + assertThat( + originalStackTrace.retrace(createMappingSupplierFromPartitionZip(pgMapFile)), + isSame(EXPECTED)); + } + + private ProguardMapConsumer createPartitionZipConsumer( + Path pgMapFile, DiagnosticsHandler diagnosticsHandler) throws IOException { + ZipBuilder zipBuilder = ZipBuilder.builder(pgMapFile); + return ProguardMapPartitionConsumer.builder() + .setMappingPartitionConsumer( + mappingPartition -> { + try { + zipBuilder.addBytes(mappingPartition.getKey(), mappingPartition.getPayload()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .setMetadataConsumer( + metadata -> { + try { + zipBuilder.addBytes("METADATA", metadata.getBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .setFinishedConsumer( + () -> { + try { + zipBuilder.build(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .setDiagnosticsHandler(diagnosticsHandler) + .build(); + } + + private PartitionMappingSupplier createMappingSupplierFromPartitionZip(Path pgMapFile) + throws IOException { + ZipFile zipFile = new ZipFile(pgMapFile.toFile()); + byte[] metadata = ByteStreams.toByteArray(zipFile.getInputStream(zipFile.getEntry("METADATA"))); + return PartitionMappingSupplier.builder() + .setMetadata(metadata) + .setMappingPartitionFromKeySupplier( + key -> { + try { + // TODO(b/274735214): The key should exist. + ZipEntry entry = zipFile.getEntry(key); + return entry == null + ? null + : ByteStreams.toByteArray(zipFile.getInputStream(entry)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .build(); + } +}
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java new file mode 100644 index 0000000..83287ff --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
@@ -0,0 +1,91 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace.partition; + +import static org.junit.Assert.assertNotNull; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.retrace.MappingPartitionMetadata; +import com.android.tools.r8.retrace.PartitionedToProguardMappingConverter; +import com.android.tools.r8.retrace.ProguardMapProducer; +import com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy; +import com.android.tools.r8.retrace.internal.ProguardMapPartitionerOnClassNameToText.ProguardMapPartitionerBuilderImplInternal; +import com.android.tools.r8.utils.StringUtils; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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 RetracePartitionAndJoinIdentityTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public RetracePartitionAndJoinIdentityTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + @Test + public void testPartitionAndJoin() throws Exception { + Path mappingFile = + ToolHelper.RETRACE_MAPS_DIR.resolve( + "ad5c3e88ef2bae5ef324eb225fbc57345cd57863-r8lib.jar.map"); + ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromPath(mappingFile); + TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); + Map<String, byte[]> partitions = new HashMap<>(); + MappingPartitionMetadata metadataData = + new ProguardMapPartitionerBuilderImplInternal(diagnosticsHandler) + .setMappingPartitionKeyStrategy( + MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS) + .setProguardMapProducer(proguardMapProducer) + .setPartitionConsumer( + partition -> partitions.put(partition.getKey(), partition.getPayload())) + .build() + .run(); + assertNotNull(metadataData); + diagnosticsHandler.assertNoMessages(); + + StringBuilder builder = new StringBuilder(); + PartitionedToProguardMappingConverter.builder() + .setMetadata(metadataData.getBytes()) + .setDiagnosticsHandler(diagnosticsHandler) + .setConsumer((string, handler) -> builder.append(string)) + .setPartitionSupplier(partitions::get) + .build() + .run(); + List<String> joinedMapLines = StringUtils.splitLines(builder.toString()); + // TODO(b/274735214): Partitioning does not capture the preamble of the mapping file yet, so we + // discard it before checking equality. + List<String> lines = Files.readAllLines(mappingFile); + List<String> filteredLines = lines.subList(computeFirstLine(lines), lines.size()); + assertListsAreEqual(filteredLines, joinedMapLines); + } + + private int computeFirstLine(List<String> lines) { + int firstLine = 0; + for (int i = 0; i < lines.size(); i++) { + String currentLine = lines.get(i).trim(); + if (!currentLine.startsWith("#") + && currentLine.contains(" -> ") + && currentLine.endsWith(":")) { + firstLine = i; + break; + } + } + return firstLine; + } +}
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java new file mode 100644 index 0000000..376d072 --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java
@@ -0,0 +1,89 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace.partition; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.MapVersion; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.retrace.MappingPartitionMetadata; +import com.android.tools.r8.retrace.ProguardMapProducer; +import com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy; +import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal; +import com.android.tools.r8.retrace.internal.ProguardMapPartitionerOnClassNameToText.ProguardMapPartitionerBuilderImplInternal; +import com.android.tools.r8.utils.StringUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +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 RetracePartitionMetadataPartitionNamesTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public RetracePartitionMetadataPartitionNamesTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + public ClassReference obfuscatedClass1 = Reference.classFromTypeName("a.a"); + public ClassReference obfuscatedClass2 = Reference.classFromTypeName("b.b"); + public ClassReference obfuscatedClass3 = Reference.classFromTypeName("c.c"); + + public String mapping = + StringUtils.unixLines( + "# { id: 'com.android.tools.r8.mapping', version: '2.0' }", + "some.class1 -> " + obfuscatedClass1.getTypeName() + ":", + " void field -> a", + "some.class2 -> " + obfuscatedClass2.getTypeName() + ":", + " void field -> a", + "some.class3 -> " + obfuscatedClass3.getTypeName() + ":", + " void field -> a"); + + @Test + public void test() throws Exception { + List<String> expectedPartitionKeys = new ArrayList<>(); + expectedPartitionKeys.add(obfuscatedClass1.getTypeName()); + expectedPartitionKeys.add(obfuscatedClass2.getTypeName()); + expectedPartitionKeys.add(obfuscatedClass3.getTypeName()); + + ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping); + DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {}; + Map<String, byte[]> partitions = new HashMap<>(); + MappingPartitionMetadata metadataData = + new ProguardMapPartitionerBuilderImplInternal(diagnosticsHandler) + .setMappingPartitionKeyStrategy( + MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS) + .setProguardMapProducer(proguardMapProducer) + .setPartitionConsumer( + partition -> partitions.put(partition.getKey(), partition.getPayload())) + .build() + .run(); + assertNotNull(metadataData); + assertEquals(new HashSet<>(expectedPartitionKeys), partitions.keySet()); + + byte[] bytes = metadataData.getBytes(); + MappingPartitionMetadataInternal mappingPartitionMetadata = + MappingPartitionMetadataInternal.deserialize( + bytes, MapVersion.MAP_VERSION_NONE, diagnosticsHandler); + assertTrue(mappingPartitionMetadata.canGetPartitionKeys()); + assertEquals(expectedPartitionKeys, mappingPartitionMetadata.getPartitionKeys()); + } +}
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataUnknownTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataUnknownTest.java new file mode 100644 index 0000000..01159fa --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataUnknownTest.java
@@ -0,0 +1,53 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace.partition; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.MapVersion; +import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal; +import com.android.tools.r8.retrace.internal.RetracePartitionException; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +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 RetracePartitionMetadataUnknownTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public RetracePartitionMetadataUnknownTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + @Test + public void test() throws Exception { + ByteArrayOutputStream temp = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(temp); + dataOutputStream.writeShort(Short.MAX_VALUE); + dataOutputStream.close(); + byte[] bytes = temp.toByteArray(); + DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {}; + RetracePartitionException retracePartitionException = + assertThrows( + RetracePartitionException.class, + () -> + MappingPartitionMetadataInternal.deserialize( + bytes, MapVersion.MAP_VERSION_NONE, diagnosticsHandler)); + assertEquals( + "Unknown map partition strategy for metadata", retracePartitionException.getMessage()); + } +}
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/testclasses/R8ZipContainerMappingFileTestClasses.java b/src/test/java/com/android/tools/r8/retrace/partition/testclasses/R8ZipContainerMappingFileTestClasses.java new file mode 100644 index 0000000..e81d031 --- /dev/null +++ b/src/test/java/com/android/tools/r8/retrace/partition/testclasses/R8ZipContainerMappingFileTestClasses.java
@@ -0,0 +1,24 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.retrace.partition.testclasses; + +public class R8ZipContainerMappingFileTestClasses { + + public static class Thrower { + + public static void throwError() { + if (System.currentTimeMillis() > 0) { + throw new RuntimeException("Hello World"); + } + } + } + + public static class Main { + + public static void main(String[] args) { + Thrower.throwError(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/UnusedParameterAfterAssumeValuesFieldRuleTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/UnusedParameterAfterAssumeValuesFieldRuleTest.java new file mode 100644 index 0000000..f4f4aec --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/UnusedParameterAfterAssumeValuesFieldRuleTest.java
@@ -0,0 +1,82 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking.assumevalues; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +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.FoundMethodSubject; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class UnusedParameterAfterAssumeValuesFieldRuleTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addKeepRules("-assumevalues class * { int f return 42; }") + .setMinApi(parameters) + .compile() + .inspect( + inspector -> { + ClassSubject aClassSubject = inspector.clazz(A.class); + assertThat(aClassSubject, isPresent()); + + List<FoundMethodSubject> aConstructorSubjects = + aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer); + if (parameters.canHaveNonReboundConstructorInvoke()) { + assertEquals(0, aConstructorSubjects.size()); + } else { + assertEquals(1, aConstructorSubjects.size()); + + FoundMethodSubject aConstructorSubject = aConstructorSubjects.get(0); + assertEquals(0, aConstructorSubject.getParameters().size()); + } + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("42"); + } + + static class Main { + + public static void main(String[] args) { + System.out.println(new A(args.length)); + } + } + + static class A { + + int f; + + A(int f) { + this.f = f; + } + + @Override + public String toString() { + return Integer.toString(f); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/constructor/ForwardingConstructorUsedFromPlatformShakingOnDexTest.java b/src/test/java/com/android/tools/r8/shaking/constructor/ForwardingConstructorUsedFromPlatformShakingOnDexTest.java index 534bbb9..7dacf64 100644 --- a/src/test/java/com/android/tools/r8/shaking/constructor/ForwardingConstructorUsedFromPlatformShakingOnDexTest.java +++ b/src/test/java/com/android/tools/r8/shaking/constructor/ForwardingConstructorUsedFromPlatformShakingOnDexTest.java
@@ -5,14 +5,21 @@ package com.android.tools.r8.shaking.constructor; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assume.assumeFalse; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.apimodel.ApiModelingTestHelper; +import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -22,48 +29,168 @@ @RunWith(Parameterized.class) public class ForwardingConstructorUsedFromPlatformShakingOnDexTest extends TestBase { + private static final String APPLICATION_INFO_DESCRIPTOR = "Landroid/content/pm/ApplicationInfo;"; + private static final String FRAGMENT_DESCRIPTOR = "Landroid/app/Fragment;"; + private static final String ZYGOTE_PRELOAD_DESCRIPTOR = "Landroid/app/ZygotePreload;"; + + private static final String EXPECTED_OUTPUT = + StringUtils.lines( + "Fragment.onCreate()", "MyFragment.onCreate()", "MyZygotePreload.doPreload()"); + + private static List<byte[]> transformedProgramClassFileData; + private static List<byte[]> transformedLibraryClassFileData; + @Parameter(0) + public boolean enableModeling; + + @Parameter(1) public TestParameters parameters; - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withAllRuntimesAndApiLevels().build(); + @Parameters(name = "{1}, modeling: {0}") + public static List<Object[]> data() { + return buildParameters( + BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build()); + } + + @BeforeClass + public static void setup() throws Exception { + transformedProgramClassFileData = getTransformedProgramClasses(); + transformedLibraryClassFileData = getTransformedLibraryClasses(); } @Test - public void test() throws Exception { + public void testRuntime() throws Exception { + assumeFalse(enableModeling); + testForRuntime(parameters) + .addProgramClassFileData(transformedProgramClassFileData) + .addProgramClassFileData(transformedLibraryClassFileData) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { testForR8(parameters.getBackend()) - .addProgramClasses(Main.class, MyFragment.class) - .addLibraryClasses(Fragment.class) + .addProgramClassFileData(transformedProgramClassFileData) + .addLibraryClassFileData(transformedLibraryClassFileData) .addLibraryFiles(parameters.getDefaultRuntimeLibrary()) .addKeepMainRule(Main.class) + .applyIf( + !enableModeling, + testBuilder -> + testBuilder.addOptionsModification( + options -> + options + .getRedundantBridgeRemovalOptions() + .clearNoConstructorShrinkingHierarchiesForTesting())) + // Since Fragment is first defined in API 11. + .apply(ApiModelingTestHelper::disableStubbingOfClasses) .enableNeverClassInliningAnnotations() .setMinApi(parameters) .compile() .inspect(this::inspect) - .addBootClasspathClasses(Fragment.class) + .addRunClasspathClassFileData(transformedLibraryClassFileData) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines("Instantiating"); + .applyIf( + enableModeling || !parameters.canHaveNonReboundConstructorInvoke(), + runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT), + runResult -> runResult.assertFailureWithErrorThatThrows(NoSuchMethodException.class)); + } + + private static List<byte[]> getTransformedProgramClasses() throws Exception { + return ImmutableList.of( + transformer(Main.class) + .replaceClassDescriptorInMethodInstructions( + descriptor(Fragment.class), FRAGMENT_DESCRIPTOR) + .replaceClassDescriptorInMethodInstructions( + descriptor(ZygotePreload.class), ZYGOTE_PRELOAD_DESCRIPTOR) + .transform(), + transformer(MyFragment.class) + .replaceClassDescriptorInMethodInstructions( + descriptor(Fragment.class), FRAGMENT_DESCRIPTOR) + .setSuper(FRAGMENT_DESCRIPTOR) + .transform(), + transformer(MyZygotePreload.class) + .replaceClassDescriptorInMembers( + descriptor(ApplicationInfo.class), APPLICATION_INFO_DESCRIPTOR) + .setImplementsClassDescriptors(ZYGOTE_PRELOAD_DESCRIPTOR) + .transform()); + } + + private static List<byte[]> getTransformedLibraryClasses() throws Exception { + return ImmutableList.of( + transformer(ApplicationInfo.class) + .setClassDescriptor(APPLICATION_INFO_DESCRIPTOR) + .transform(), + transformer(Fragment.class).setClassDescriptor(FRAGMENT_DESCRIPTOR).transform(), + transformer(Platform.class) + .replaceClassDescriptorInMembers(descriptor(Fragment.class), FRAGMENT_DESCRIPTOR) + .replaceClassDescriptorInMethodInstructions( + descriptor(Fragment.class), FRAGMENT_DESCRIPTOR) + .replaceClassDescriptorInMembers( + descriptor(ZygotePreload.class), ZYGOTE_PRELOAD_DESCRIPTOR) + .replaceClassDescriptorInMethodInstructions( + descriptor(ApplicationInfo.class), APPLICATION_INFO_DESCRIPTOR) + .replaceClassDescriptorInMethodInstructions( + descriptor(ZygotePreload.class), ZYGOTE_PRELOAD_DESCRIPTOR) + .transform(), + transformer(ZygotePreload.class) + .replaceClassDescriptorInMembers( + descriptor(ApplicationInfo.class), APPLICATION_INFO_DESCRIPTOR) + .setClassDescriptor(ZYGOTE_PRELOAD_DESCRIPTOR) + .transform()); } private void inspect(CodeInspector inspector) { ClassSubject myFragmentClassSubject = inspector.clazz(MyFragment.class); assertThat(myFragmentClassSubject, isPresent()); - assertThat(myFragmentClassSubject.init(), isPresent()); + assertThat( + myFragmentClassSubject.init(), + onlyIf(enableModeling || !parameters.canHaveNonReboundConstructorInvoke(), isPresent())); + + ClassSubject myZygotePreloadClassSubject = inspector.clazz(MyZygotePreload.class); + assertThat(myZygotePreloadClassSubject, isPresent()); + assertThat( + myZygotePreloadClassSubject.init(), + onlyIf(enableModeling || !parameters.canHaveNonReboundConstructorInvoke(), isPresent())); } + // Library classes. + + public abstract static class ApplicationInfo {} + public abstract static class Fragment { - public Fragment newInstance() throws Exception { - System.out.println("Instantiating"); - return getClass().getDeclaredConstructor().newInstance(); + public void onCreate() { + System.out.println("Fragment.onCreate()"); } } + public interface ZygotePreload { + + void doPreload(ApplicationInfo applicationInfo); + } + + public static class Platform { + + public static void accept(Fragment fragment) throws Exception { + Fragment newFragment = fragment.getClass().getDeclaredConstructor().newInstance(); + newFragment.onCreate(); + } + + public static void accept(ZygotePreload runnable) throws Exception { + ZygotePreload newZygotePreload = runnable.getClass().getDeclaredConstructor().newInstance(); + newZygotePreload.doPreload(null); + } + } + + // Program classes. + public static class Main { public static void main(String[] args) throws Exception { - new MyFragment().newInstance(); + Platform.accept(new MyFragment()); + Platform.accept(new MyZygotePreload()); } } @@ -71,5 +198,21 @@ public static class MyFragment extends Fragment { public MyFragment() {} + + @Override + public void onCreate() { + super.onCreate(); + System.out.println("MyFragment.onCreate()"); + } + } + + public static class MyZygotePreload implements ZygotePreload { + + public MyZygotePreload() {} + + @Override + public void doPreload(ApplicationInfo applicationInfo) { + System.out.println("MyZygotePreload.doPreload()"); + } } }
diff --git a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexFromStartupMethodRuleTest.java b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexFromStartupMethodRuleTest.java index 6034910..ae0fe36 100644 --- a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexFromStartupMethodRuleTest.java +++ b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexFromStartupMethodRuleTest.java
@@ -47,6 +47,7 @@ runTest(testForR8(parameters.getBackend()).addKeepAllClassesRule()); } + @SuppressWarnings("unchecked") private void runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testCompilerBuilder) throws Exception { testCompilerBuilder .addInnerClasses(getClass())
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceNonReboundConstructorCallTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceNonReboundConstructorCallTest.java new file mode 100644 index 0000000..06835db --- /dev/null +++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceNonReboundConstructorCallTest.java
@@ -0,0 +1,132 @@ +// Copyright (c) 2023, 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.tracereferences; + +import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.DiagnosticsChecker; +import com.android.tools.r8.DiagnosticsHandler; +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.references.MethodReference; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.MethodReferenceUtils; +import com.android.tools.r8.utils.ZipUtils.ZipBuilder; +import com.google.common.collect.ImmutableSet; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; +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 TraceReferenceNonReboundConstructorCallTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public TraceReferenceNonReboundConstructorCallTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + + static class MissingReferencesConsumer implements TraceReferencesConsumer { + + private Set<MethodReference> foundMethods = new HashSet<>(); + private Set<MethodReference> missingMethods = new HashSet<>(); + + @Override + public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) {} + + @Override + public void acceptField(TracedField tracedField, DiagnosticsHandler handler) {} + + @Override + public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) { + if (tracedMethod.isMissingDefinition()) { + missingMethods.add(tracedMethod.getReference()); + } else { + foundMethods.add(tracedMethod.getReference()); + } + } + } + + @Test + public void testCf() throws Throwable { + MissingReferencesConsumer consumer = + runTest( + ZipBuilder.builder(temp.newFile("source.jar").toPath()) + .addFilesRelative( + ToolHelper.getClassPathForTests(), + ToolHelper.getClassFileForTestClass(Main.class)) + .build()); + ImmutableSet<MethodReference> expectedFoundMethods = ImmutableSet.of(); + ImmutableSet<MethodReference> expectedMissingMethods = + ImmutableSet.of(MethodReferenceUtils.instanceConstructor(SubClass.class)); + assertEquals(expectedFoundMethods, consumer.foundMethods); + assertEquals(expectedMissingMethods, consumer.missingMethods); + } + + @Test + public void testDex() throws Throwable { + MissingReferencesConsumer consumer = + runTest( + testForD8(Backend.DEX) + .addProgramClasses(Main.class) + .release() + .setMinApi(AndroidApiLevel.B) + .compile() + .writeToZip()); + ImmutableSet<MethodReference> expectedFoundMethods = + ImmutableSet.of(MethodReferenceUtils.instanceConstructor(SuperClass.class)); + ImmutableSet<MethodReference> expectedMissingMethods = ImmutableSet.of(); + assertEquals(expectedFoundMethods, consumer.foundMethods); + assertEquals(expectedMissingMethods, consumer.missingMethods); + } + + private MissingReferencesConsumer runTest(Path sourceFile) throws Throwable { + Path dir = temp.newFolder().toPath(); + Path targetJar = + ZipBuilder.builder(dir.resolve("target.jar")) + .addFilesRelative( + ToolHelper.getClassPathForTests(), + ToolHelper.getClassFileForTestClass(SuperClass.class)) + .addBytes( + binaryName(SubClass.class) + CLASS_EXTENSION, + transformer(SubClass.class).removeMethodsWithName("<init>").transform()) + .build(); + DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker(); + MissingReferencesConsumer consumer = new MissingReferencesConsumer(); + TraceReferences.run( + TraceReferencesCommand.builder(diagnosticsChecker) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) + .addSourceFiles(sourceFile) + .addTargetFiles(targetJar) + .setConsumer(consumer) + .build()); + return consumer; + } + + static class SuperClass {} + + static class SubClass extends SuperClass { + + // Removed by transformer. + SubClass() {} + } + + public static class Main { + + public static void main(String[] args) { + new SubClass(); + } + } +}
diff --git a/third_party/r8mappings.tar.gz.sha1 b/third_party/r8mappings.tar.gz.sha1 index a00ac52..bb6bada 100644 --- a/third_party/r8mappings.tar.gz.sha1 +++ b/third_party/r8mappings.tar.gz.sha1
@@ -1 +1 @@ -baaf8e15c9f54677c30caf070a22319ccd46539c \ No newline at end of file +36c9b3f2335c084a6d5e270a7ac8fdcca0e72a74 \ No newline at end of file