Merge commit 'bc75ee8beb893ce438f76b6fae6eafc31cbfec8c' into dev-release
Change-Id: Ie3d1c549c2c849128721c090695b4979b95341b5
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
index 909ab68..df95ae9 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
@@ -177,7 +177,7 @@
test.maxParallelForks = processors.div(userDefinedCoresPerFork.toInt())
} else {
// On work machines this seems to give the best test execution time (without freezing).
- test.maxParallelForks = maxOf(processors.div(3), 1)
+ test.maxParallelForks = maxOf(processors.div(8), 1)
// On low cpu count machines (bots) we under subscribe, so increase the count.
if (processors == 32) {
test.maxParallelForks = 15
diff --git a/d8_r8/test_modules/testbase/build.gradle.kts b/d8_r8/test_modules/testbase/build.gradle.kts
index 336033b..cf20c77 100644
--- a/d8_r8/test_modules/testbase/build.gradle.kts
+++ b/d8_r8/test_modules/testbase/build.gradle.kts
@@ -110,6 +110,7 @@
dependsOn(gradle.includedBuild("shared").task(":downloadDeps"))
dependsOn(gradle.includedBuild("keepanno").task(":jar"))
dependsOn(gradle.includedBuild("resourceshrinker").task(":jar"))
+ dependsOn(gradle.includedBuild("resourceshrinker").task(":depsJar"))
from(testDependencies().map(::zipTree))
from(resourceShrinkerDepsJarTask.outputs.getFiles().map(::zipTree))
from(keepAnnoJarTask.outputs.getFiles().map(::zipTree))
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index ae366a3..ee5ff59 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -308,7 +308,7 @@
appView.setArtProfileCollection(
appView.getArtProfileCollection().withoutMissingItems(appView));
- assert appView.getStartupProfile().isEmpty();
+ appView.setStartupProfile(appView.getStartupProfile().withoutMissingItems(appView));
finalizeApplication(appView, executor, timing);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index eee535b..42672c6 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1575,6 +1575,7 @@
.setForceProguardCompatibility(forceProguardCompatibility)
.setFeatureSplitConfiguration(featureSplitConfiguration)
.setAndroidResourceProvider(androidResourceProvider)
+ .setPartialCompilationConfiguration(partialCompilationConfiguration)
.setProguardConfiguration(proguardConfiguration)
.setMainDexKeepRules(mainDexKeepRules)
.setDesugaredLibraryConfiguration(desugaredLibrarySpecification)
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java
index eccfd59..02c612c 100644
--- a/src/main/java/com/android/tools/r8/R8Partial.java
+++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -13,6 +13,8 @@
import com.android.tools.r8.partial.R8PartialProgramPartioning;
import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialD8SubCompilationConfiguration;
import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialR8SubCompilationConfiguration;
+import com.android.tools.r8.profile.art.ArtProfileOptions;
+import com.android.tools.r8.profile.startup.StartupOptions;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.FeatureSplitConsumers;
@@ -55,13 +57,6 @@
}
void runInternal(AndroidApp app, ExecutorService executor) throws IOException, ResourceException {
- if (!options.getArtProfileOptions().getArtProfileProviders().isEmpty()) {
- throw options.reporter.fatalError(
- "Partial shrinking does not support baseline profile rewriting");
- }
- if (!options.getStartupOptions().getStartupProfileProviders().isEmpty()) {
- throw options.reporter.fatalError("Partial shrinking does not support startup profiles");
- }
if (options.getProguardConfiguration().isProtoShrinkingEnabled()) {
throw options.reporter.fatalError("Partial shrinking does not support proto shrinking");
}
@@ -90,9 +85,8 @@
private R8PartialInput runProcessInputStep(AndroidApp androidApp, ExecutorService executor)
throws IOException {
- // TODO(b/388421578): Add support for generating R8 partial compile dumps.
DirectMappedDexApplication app =
- new ApplicationReader(androidApp, options, timing).readWithoutDumping(executor).toDirect();
+ new ApplicationReader(androidApp, options, timing).read(executor).toDirect();
R8PartialProgramPartioning partioning = R8PartialProgramPartioning.create(app);
return new R8PartialInput(
partioning.getD8Classes(),
@@ -120,13 +114,18 @@
options.partialCompilationConfiguration.d8DexOptionsConsumer.accept(d8Options);
R8PartialD8SubCompilationConfiguration subCompilationConfiguration =
new R8PartialD8SubCompilationConfiguration(input.getD8Types(), input.getR8Types(), timing);
+ d8Options.setArtProfileOptions(
+ new ArtProfileOptions(d8Options, options.getArtProfileOptions()));
d8Options.setFeatureSplitConfiguration(options.getFeatureSplitConfiguration());
+ d8Options.setStartupOptions(new StartupOptions(d8Options, options.getStartupOptions()));
d8Options.partialSubCompilationConfiguration = subCompilationConfiguration;
D8.runInternal(d8App, d8Options, executor);
return new R8PartialD8Result(
+ subCompilationConfiguration.getArtProfiles(),
subCompilationConfiguration.getClassToFeatureSplitMap(),
subCompilationConfiguration.getDexedOutputClasses(),
- subCompilationConfiguration.getDesugaredOutputClasses());
+ subCompilationConfiguration.getDesugaredOutputClasses(),
+ subCompilationConfiguration.getStartupProfile());
}
private void runR8Step(
@@ -191,8 +190,15 @@
options.partialCompilationConfiguration.r8OptionsConsumer.accept(r8Options);
r8Options.partialSubCompilationConfiguration =
new R8PartialR8SubCompilationConfiguration(
- d8Result.getClassToFeatureSplitMap(), d8Result.getDexedClasses(), timing);
+ d8Result.getArtProfiles(),
+ d8Result.getClassToFeatureSplitMap(),
+ d8Result.getDexedClasses(),
+ d8Result.getStartupProfile(),
+ timing);
+ r8Options.setArtProfileOptions(
+ new ArtProfileOptions(r8Options, options.getArtProfileOptions()));
r8Options.setFeatureSplitConfiguration(options.getFeatureSplitConfiguration());
+ r8Options.setStartupOptions(new StartupOptions(r8Options, options.getStartupOptions()));
r8Options.mapConsumer = options.mapConsumer;
if (options.androidResourceProvider != null) {
r8Options.androidResourceProvider = options.androidResourceProvider;
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 d7a0d7d..d46877f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -164,7 +164,9 @@
private void dumpApplication(DumpInputFlags dumpInputFlags) {
DumpOptions dumpOptions = options.dumpOptions;
- if (dumpOptions == null || !dumpInputFlags.shouldDump(dumpOptions)) {
+ if (dumpOptions == null
+ || options.partialSubCompilationConfiguration != null
+ || !dumpInputFlags.shouldDump(dumpOptions)) {
return;
}
Path dumpOutput = dumpInputFlags.getDumpPath();
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index a6cbf2e..c54b9ca 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -53,6 +53,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialR8SubCompilationConfiguration;
import com.android.tools.r8.profile.startup.StartupCompleteness;
import com.android.tools.r8.profile.startup.profile.StartupProfile;
import com.android.tools.r8.shaking.MainDexInfo;
@@ -67,6 +68,7 @@
import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexPerFileConsumer;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.OriginalSourceFiles;
import com.android.tools.r8.utils.PredicateUtils;
import com.android.tools.r8.utils.Reporter;
@@ -84,8 +86,8 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -94,6 +96,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -247,18 +250,12 @@
&& options.enableMainDexListCheck) {
distributor = new VirtualFile.MonoDexDistributor(this, classes, options);
} else {
- // Retrieve the startup order for writing the app. In R8, the startup order is created
- // up-front to guide optimizations through-out the compilation. In D8, the startup
- // order is only used for writing the app, so we create it here for the first time.
- StartupProfile startupProfile;
- if (options.getStartupOptions().isStartupLayoutOptimizationEnabled()) {
- startupProfile =
- appView.appInfo().hasClassHierarchy()
- ? appView.getStartupProfile()
- : StartupProfile.createInitialStartupProfileForD8(appView);
- } else {
- startupProfile = StartupProfile.empty();
- }
+ // Retrieve the startup order for writing the app. Use an empty startup profile if the startup
+ // profile should not be used for layout.
+ StartupProfile startupProfile =
+ options.getStartupOptions().isStartupLayoutOptimizationEnabled()
+ ? appView.getStartupProfile()
+ : StartupProfile.empty();
distributor =
new VirtualFile.FillFilesDistributor(
this, classes, options, executorService, startupProfile);
@@ -495,31 +492,43 @@
return OriginalSourceFiles.fromClasses();
}
if (!willComputeProguardMap()) {
- rewriteSourceFile(null);
+ rewriteSourceFile(appView.appInfo().classes(), null);
return OriginalSourceFiles.unreachable();
}
// Clear all source files so as not to collect the original files.
- Collection<DexProgramClass> classes = appView.appInfo().classes();
- Map<DexType, DexString> originalSourceFiles = new HashMap<>(classes.size());
- for (DexProgramClass clazz : classes) {
- DexString originalSourceFile = clazz.getSourceFile();
- if (originalSourceFile != null) {
- originalSourceFiles.put(clazz.getType(), originalSourceFile);
- clazz.setSourceFile(null);
- }
- }
+ Map<DexProgramClass, DexString> originalSourceFiles = computeOriginalSourceFiles();
// Add a lazy dex string computation to defer construction of the actual string.
lazyDexStrings.add(
new LazyDexString() {
@Override
public DexString internalCompute() {
- return rewriteSourceFile(delayedProguardMapId.get());
+ return rewriteSourceFile(originalSourceFiles.keySet(), delayedProguardMapId.get());
}
});
-
return OriginalSourceFiles.fromMap(originalSourceFiles);
}
+ private Map<DexProgramClass, DexString> computeOriginalSourceFiles() {
+ Collection<DexProgramClass> classes = appView.appInfo().classes();
+ if (options.partialSubCompilationConfiguration == null) {
+ return MapUtils.transform(
+ classes,
+ IdentityHashMap::new,
+ Function.identity(),
+ DexProgramClass::getAndClearSourceFile);
+ } else {
+ R8PartialR8SubCompilationConfiguration subCompilationConfiguration =
+ options.partialSubCompilationConfiguration.asR8();
+ return MapUtils.transform(
+ classes,
+ ignore ->
+ new IdentityHashMap<>(
+ classes.size() - subCompilationConfiguration.getDexingOutputClasses().size()),
+ clazz -> subCompilationConfiguration.isD8Definition(clazz) ? null : clazz,
+ DexProgramClass::getAndClearSourceFile);
+ }
+ }
+
public static SourceFileEnvironment createSourceFileEnvironment(ProguardMapId proguardMapId) {
if (proguardMapId == null) {
return new SourceFileEnvironment() {
@@ -547,13 +556,14 @@
};
}
- private DexString rewriteSourceFile(ProguardMapId proguardMapId) {
+ private DexString rewriteSourceFile(
+ Collection<DexProgramClass> classes, ProguardMapId proguardMapId) {
assert options.sourceFileProvider != null;
SourceFileEnvironment environment = createSourceFileEnvironment(proguardMapId);
String sourceFile = options.sourceFileProvider.get(environment);
DexString dexSourceFile =
sourceFile == null ? null : options.itemFactory.createString(sourceFile);
- appView.appInfo().classes().forEach(clazz -> clazz.setSourceFile(dexSourceFile));
+ classes.forEach(clazz -> clazz.setSourceFile(dexSourceFile));
return dexSourceFile;
}
diff --git a/src/main/java/com/android/tools/r8/dump/CompilerDump.java b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
index 5e9b815..1cf9373 100644
--- a/src/main/java/com/android/tools/r8/dump/CompilerDump.java
+++ b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
@@ -3,13 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dump;
+import static com.android.tools.r8.utils.AndroidApp.dumpR8ExcludeFileName;
+import static com.android.tools.r8.utils.AndroidApp.dumpR8IncludeFileName;
+
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ZipUtils;
import java.io.BufferedReader;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.Consumer;
public class CompilerDump {
@@ -82,6 +85,14 @@
return directory.resolve("desugared-library.json");
}
+ public Path getR8PartialIncludeFile() {
+ return directory.resolve(dumpR8IncludeFileName);
+ }
+
+ public Path getR8PartialExcludeFile() {
+ return directory.resolve(dumpR8ExcludeFileName);
+ }
+
public void sanitizeProguardConfig(ProguardConfigSanitizer sanitizer) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(getProguardConfigFile())) {
String next = reader.readLine();
@@ -95,10 +106,28 @@
public DumpOptions getBuildProperties() throws IOException {
if (Files.exists(getBuildPropertiesFile())) {
DumpOptions.Builder builder = new DumpOptions.Builder();
- DumpOptions.parse(
- FileUtils.readTextFile(getBuildPropertiesFile(), StandardCharsets.UTF_8), builder);
+ DumpOptions.parse(FileUtils.readTextFile(getBuildPropertiesFile()), builder);
return builder.build();
}
return null;
}
+
+ public List<String> getR8PartialIncludePatterns() throws IOException {
+ if (Files.exists(getR8PartialIncludeFile())) {
+ List<String> includePatterns = FileUtils.readAllLines(getR8PartialIncludeFile());
+ assert includePatterns.stream().noneMatch(String::isEmpty);
+ return includePatterns;
+ }
+ return null;
+ }
+
+ public List<String> getR8PartialExcludePatternsOrDefault(List<String> defaultValue)
+ throws IOException {
+ if (Files.exists(getR8PartialExcludeFile())) {
+ List<String> excludePatterns = FileUtils.readAllLines(getR8PartialExcludeFile());
+ assert excludePatterns.stream().noneMatch(String::isEmpty);
+ return excludePatterns;
+ }
+ return defaultValue;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 052a6de..31835aa 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.partial.R8PartialCompilationConfiguration;
import com.android.tools.r8.profile.art.ArtProfileProvider;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
@@ -82,6 +83,7 @@
private final boolean enableMissingLibraryApiModeling;
private final boolean isAndroidPlatformBuild;
private final AndroidResourceProvider androidResourceProvider;
+ private final R8PartialCompilationConfiguration partialCompilationConfiguration;
private final Map<String, String> systemProperties;
// TraceReferences only.
@@ -116,6 +118,7 @@
boolean dumpInputToFile,
String traceReferencesConsumer,
AndroidResourceProvider androidResourceProvider,
+ R8PartialCompilationConfiguration partialCompilationConfiguration,
Optional<Boolean> optimizedResourceShrinking) {
this.backend = backend;
this.tool = tool;
@@ -143,6 +146,7 @@
this.dumpInputToFile = dumpInputToFile;
this.traceReferencesConsumer = traceReferencesConsumer;
this.androidResourceProvider = androidResourceProvider;
+ this.partialCompilationConfiguration = partialCompilationConfiguration;
}
public String getBuildPropertiesFileContent() {
@@ -378,6 +382,14 @@
return androidResourceProvider;
}
+ public boolean hasPartialCompilationConfiguration() {
+ return partialCompilationConfiguration != null && partialCompilationConfiguration.isEnabled();
+ }
+
+ public R8PartialCompilationConfiguration getPartialCompilationConfiguration() {
+ return partialCompilationConfiguration;
+ }
+
public static class Builder {
// Initialize backend to DEX for backwards compatibility.
private Backend backend = Backend.DEX;
@@ -401,6 +413,7 @@
private Collection<ArtProfileProvider> artProfileProviders;
private Collection<StartupProfileProvider> startupProfileProviders;
private AndroidResourceProvider androidResourceProvider;
+ private R8PartialCompilationConfiguration partialCompilationConfiguration;
private Optional<Boolean> optimizedResourceShrinking = Optional.empty();
private boolean enableMissingLibraryApiModeling = false;
@@ -597,16 +610,19 @@
dumpInputToFile,
traceReferencesConsumer,
androidResourceProvider,
+ partialCompilationConfiguration,
optimizedResourceShrinking);
}
- public AndroidResourceProvider getAndroidResourceProvider() {
- return androidResourceProvider;
- }
-
public Builder setAndroidResourceProvider(AndroidResourceProvider androidResourceProvider) {
this.androidResourceProvider = androidResourceProvider;
return this;
}
+
+ public Builder setPartialCompilationConfiguration(
+ R8PartialCompilationConfiguration partialCompilationConfiguration) {
+ this.partialCompilationConfiguration = partialCompilationConfiguration;
+ return this;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 4c16fe2..939cd35 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -212,6 +212,16 @@
return definitionForWithoutExistenceAssert(type) != null;
}
+ public final boolean hasDefinitionForWithoutExistenceAssert(DexMember<?, ?> member) {
+ return member.isDefinedOnClass(definitionForWithoutExistenceAssert(member.getHolderType()));
+ }
+
+ public final boolean hasDefinitionForWithoutExistenceAssert(DexReference reference) {
+ return reference.isDexType()
+ ? hasDefinitionForWithoutExistenceAssert(reference.asDexType())
+ : hasDefinitionForWithoutExistenceAssert(reference.asDexMember());
+ }
+
@SuppressWarnings("ReferenceEquality")
public DexClass definitionForDesugarDependency(DexClass dependent, DexType type) {
if (dependent.type == type) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 17b3c2d..be9a796 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -281,7 +281,7 @@
return new AppView<>(
appInfo,
ArtProfileCollection.createInitialArtProfileCollection(appInfo, appInfo.options()),
- StartupProfile.empty(),
+ StartupProfile.createInitialStartupProfileForD8(appInfo.app()),
WholeProgramOptimizations.OFF,
mapper,
timing);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 14c4684..1e64e9e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -213,6 +213,12 @@
this.sourceFile = sourceFile;
}
+ public DexString getAndClearSourceFile() {
+ DexString sourceFile = getSourceFile();
+ setSourceFile(null);
+ return sourceFile;
+ }
+
public Iterable<DexClassAndField> classFields() {
return Iterables.transform(fields(), field -> DexClassAndField.create(this, field));
}
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 d60a682..0dc6918 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1274,7 +1274,11 @@
}
public static DexEncodedMethod createDesugaringForwardingMethod(
- DexClassAndMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) {
+ DexClassAndMethod target,
+ DexClass clazz,
+ DexMethod forwardMethod,
+ DexItemFactory factory,
+ boolean targetIsStatic) {
assert forwardMethod != null;
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
@@ -1285,16 +1289,23 @@
newFlags.unsetAbstract();
// Holder is companion class, or retarget method, not an interface.
boolean isInterfaceMethodReference = false;
+ ForwardMethodBuilder builder =
+ ForwardMethodBuilder.builder(factory).setNonStaticSource(newMethod);
+ if (targetIsStatic) {
+ builder.setStaticTarget(forwardMethod, isInterfaceMethodReference);
+ } else {
+ builder.setVirtualTarget(forwardMethod, isInterfaceMethodReference);
+ }
+ if (forwardMethod.getReturnType().isNotIdenticalTo(target.getReturnType())) {
+ assert target.getReturnType().isVoidType();
+ builder.setIgnoreTargetResult();
+ }
return syntheticBuilder()
.setMethod(newMethod)
.setAccessFlags(newFlags)
.setGenericSignature(MethodTypeSignature.noSignature())
.setAnnotations(DexAnnotationSet.empty())
- .setCode(
- ForwardMethodBuilder.builder(factory)
- .setNonStaticSource(newMethod)
- .setStaticTarget(forwardMethod, isInterfaceMethodReference)
- .buildCf())
+ .setCode(builder.buildCf())
.setApiLevelForDefinition(target.getDefinition().getApiLevelForDefinition())
.setApiLevelForCode(target.getDefinition().getApiLevelForCode())
.build();
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 d02a4cd..27f31fa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -73,6 +73,7 @@
public static final String throwableDescriptorString = "Ljava/lang/Throwable;";
public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
+ public static final String autoCloseableTagString = "Lcom/android/tools/r8/AutoCloseableTag;";
public static final String recordDescriptorString = "Ljava/lang/Record;";
public static final String desugarVarHandleDescriptorString =
"Lcom/android/tools/r8/DesugarVarHandle;";
@@ -260,6 +261,7 @@
public final DexString objectDescriptor = createString("Ljava/lang/Object;");
public final DexString recordDescriptor = createString(recordDescriptorString);
public final DexString recordTagDescriptor = createString(recordTagDescriptorString);
+ public final DexString autoCloseableTagDescriptor = createString(autoCloseableTagString);
public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
public final DexString classDescriptor = createString("Ljava/lang/Class;");
public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
@@ -441,6 +443,7 @@
public final DexType objectType = createStaticallyKnownType(objectDescriptor);
public final DexType recordType = createStaticallyKnownType(recordDescriptor);
public final DexType recordTagType = createStaticallyKnownType(recordTagDescriptor);
+ public final DexType autoCloseableTagType = createStaticallyKnownType(autoCloseableTagDescriptor);
public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor);
public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
public final DexType enumType = createStaticallyKnownType(enumDescriptor);
@@ -665,6 +668,7 @@
createStaticallyKnownType(androidContentContentProviderClientDescriptorString);
public final DexType androidDrmDrmManagerClientType =
createStaticallyKnownType(androidDrmDrmManagerClientDescriptorString);
+ public final DexType androidMediaMediaDrm = createStaticallyKnownType("Landroid/media/MediaDrm;");
public final DexType androidMediaMediaDrmType =
createStaticallyKnownType(androidMediaMediaDrmDescriptorString);
public final DexType androidMediaMediaMetadataRetrieverType =
@@ -894,6 +898,11 @@
createStaticallyKnownType(desugarMethodHandlesLookupDescriptorString);
public final DexType mockitoType = createStaticallyKnownType("Lorg/mockito/Mockito;");
+ public final DexType javaUtilConcurrentExecutorServiceType =
+ createStaticallyKnownType("Ljava/util/concurrent/ExecutorService;");
+ public final DexType javaUtilConcurrentForkJoinPoolType =
+ createStaticallyKnownType("Ljava/util/concurrent/ForkJoinPool;");
+
public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
public final IteratorMethods iteratorMethods = new IteratorMethods();
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index cae4a0e..620027f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -273,13 +273,23 @@
}
private String decode() throws UTFDataFormatException {
+ return decodeFromMutf8(content, javaLangStringLength);
+ }
+
+ public int decodePrefix(char[] out) throws UTFDataFormatException {
+ return decodePrefixFromMutf8(content, out);
+ }
+
+ public static String decodeFromMutf8(byte[] content, int javaLangStringLength)
+ throws UTFDataFormatException {
char[] out = new char[javaLangStringLength];
- int decodedLength = decodePrefix(out);
+ int decodedLength = decodePrefixFromMutf8(content, out);
return new String(out, 0, decodedLength);
}
// Inspired from /dex/src/main/java/com/android/dex/Mutf8.java
- public int decodePrefix(char[] out) throws UTFDataFormatException {
+ public static int decodePrefixFromMutf8(byte[] content, char[] out)
+ throws UTFDataFormatException {
int s = 0;
int p = 0;
int prefixLength = out.length;
@@ -354,11 +364,11 @@
return h;
}
- private UTFDataFormatException badByteException(int a) {
+ private static UTFDataFormatException badByteException(int a) {
return new UTFDataFormatException("bad byte: " + Integer.toHexString((char) (a & 0xff)) + ")");
}
- private UTFDataFormatException badSecondByteException(int a, int b) {
+ private static UTFDataFormatException badSecondByteException(int a, int b) {
return new UTFDataFormatException(
"bad second byte (first: "
+ Integer.toHexString((char) (a & 0xff))
@@ -367,7 +377,7 @@
+ ")");
}
- private UTFDataFormatException badSecondOrThirdByteException(int a, int b, int c) {
+ private static UTFDataFormatException badSecondOrThirdByteException(int a, int b, int c) {
return new UTFDataFormatException(
"bad second or third byte (first: "
+ Integer.toHexString((char) (a & 0xff))
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index a515a46..389f92d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -34,7 +34,7 @@
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.jar.CfApplicationClassWriter;
import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
@@ -129,12 +129,12 @@
parsingOptions);
// Read marker.
- if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
- && reader.getItem(CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
+ if (reader.getItemCount() > CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX
+ && reader.getItem(CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
try {
Object maybeMarker =
reader.readConst(
- CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
+ CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
new char[reader.getMaxStringLength()]);
if (maybeMarker instanceof String) {
application.getFactory().createMarkerString((String) maybeMarker);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
index 62462bd..ae47e34 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
@@ -5,6 +5,8 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
@@ -21,14 +23,27 @@
public class InterfaceCollection {
- @SuppressWarnings("ReferenceEquality")
public static boolean isKnownToImplement(
DexType iface, DexType implementor, InternalOptions options) {
if (options.canHaveZipFileWithMissingCloseableBug()
- && implementor == options.dexItemFactory().zipFileType
- && iface == options.dexItemFactory().closeableType) {
+ && implementor.isIdenticalTo(options.dexItemFactory().zipFileType)
+ && iface.isIdenticalTo(options.dexItemFactory().closeableType)) {
return false;
}
+ if (options.canHaveMissingImplementsAutoCloseableInterface()
+ && iface.isIdenticalTo(options.dexItemFactory().autoCloseableType)) {
+ BooleanBox booleanBox = new BooleanBox(true);
+ AutoCloseableRetargeterHelper.forEachAutoCloseableMissingSubimplementation(
+ type -> {
+ if (type.isIdenticalTo(implementor)) {
+ booleanBox.set(false);
+ }
+ },
+ options.getMinApiLevel(),
+ options.dexItemFactory(),
+ true);
+ return booleanBox.get();
+ }
return true;
}
@@ -44,10 +59,6 @@
|| isKnownToImplement(iface, implementor.getType(), options));
}
- public Builder addInterface(DexType iface, DexType implementor, InternalOptions options) {
- return addInterface(iface, isKnownToImplement(iface, implementor, options));
- }
-
public Builder addInterface(DexType type, boolean isKnown) {
interfaces.compute(
type,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
index 855b473..52386c8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -311,6 +311,13 @@
.isPossiblyFalse()) {
return false;
}
+ if (appView.options().canHaveMissingImplementsAutoCloseableInterface()
+ && instanceOfBaseType.isIdenticalTo(appView.dexItemFactory().autoCloseableType)
+ && instanceOfClass.isLibraryClass()) {
+ // Library classes may be messed up since they may implement AutoCloseable from a different
+ // api level than the api level they are introduced.
+ return false;
+ }
}
Value inValue = instanceOf.value();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index ee7aa09..04fb94e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -926,21 +926,24 @@
initializeMathExactApis(factory, factory.mathType);
- // android.content.res.ContentProviderClient
+ // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+ if (!appView.options().shouldDesugarAutoCloseable()) {
+ // android.content.res.ContentProviderClient
- // void android.content.ContentProviderClient.close()
- addProvider(
- new InvokeRewriter(
- factory.androidContentContentProviderClientMembers.close,
- ContentProviderClientMethodRewrites.rewriteClose()));
+ // void android.content.ContentProviderClient.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidContentContentProviderClientMembers.close,
+ ContentProviderClientMethodRewrites.rewriteClose()));
- // android.drm.DrmManagerClient
+ // android.drm.DrmManagerClient
- // void android.drm.DrmManagerClient.close()
- addProvider(
- new InvokeRewriter(
- factory.androidDrmDrmManagerClientMembers.close,
- DrmManagerClientMethodRewrites.rewriteClose()));
+ // void android.drm.DrmManagerClient.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidDrmDrmManagerClientMembers.close,
+ DrmManagerClientMethodRewrites.rewriteClose()));
+ }
}
/**
@@ -1197,11 +1200,14 @@
"stripTrailingZeros",
bigDecimal));
- // void android.drm.DrmManagerClient.close()
- addProvider(
- new InvokeRewriter(
- factory.androidMediaMetadataRetrieverMembers.close,
- MediaMetadataRetrieverMethodRewrites.rewriteClose()));
+ // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+ if (!appView.options().shouldDesugarAutoCloseable()) {
+ // void android.media.MetadataRetriever.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidMediaMetadataRetrieverMembers.close,
+ MediaMetadataRetrieverMethodRewrites.rewriteClose()));
+ }
}
private void initializeAndroidRObjectsMethodProviderWithSupplier(DexItemFactory factory) {
@@ -1448,13 +1454,16 @@
new InvokeRewriter(
factory.androidUtilSparseArrayMembers.set, SparseArrayMethodRewrites.rewriteSet()));
- // android.content.res.TypedArray
+ // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+ if (!appView.options().shouldDesugarAutoCloseable()) {
+ // android.content.res.TypedArray
- // void android.content.res.TypedArray.close()
- addProvider(
- new InvokeRewriter(
- factory.androidContentResTypedArrayMembers.close,
- TypedArrayMethodRewrites.rewriteClose()));
+ // void android.content.res.TypedArray.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidContentResTypedArrayMembers.close,
+ TypedArrayMethodRewrites.rewriteClose()));
+ }
}
private void initializeAndroidSv2MethodProviders(DexItemFactory factory) {
@@ -1874,17 +1883,20 @@
(Integer) versionCodeFull[1])));
}
- // void java.util.concurrent.ExecutorService.close()
- type = factory.createType("Ljava/util/concurrent/ExecutorService;");
- name = factory.createString("close");
- proto = factory.createProto(factory.voidType);
- method = factory.createMethod(type, proto, name);
- addProvider(
- new StatifyingMethodGenerator(
- method,
- BackportedMethods::ExecutorServiceMethods_closeExecutorService,
- "closeExecutorService",
- type));
+ // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+ if (!appView.options().shouldDesugarAutoCloseable()) {
+ // void java.util.concurrent.ExecutorService.close()
+ type = factory.createType("Ljava/util/concurrent/ExecutorService;");
+ name = factory.createString("close");
+ proto = factory.createProto(factory.voidType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(
+ new StatifyingMethodGenerator(
+ method,
+ BackportedMethods::ExecutorServiceMethods_closeExecutorService,
+ "closeExecutorService",
+ type));
+ }
}
private void initializeAndroidUMethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index ace4c1e..2363fe4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibRewriterEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
@@ -77,7 +78,8 @@
ApiInvokeOutlinerDesugaringEventConsumer,
VarHandleDesugaringEventConsumer,
DesugaredLibRewriterEventConsumer,
- TypeSwitchDesugaringEventConsumer {
+ TypeSwitchDesugaringEventConsumer,
+ AutoCloseableRetargeterEventConsumer {
public static CfInstructionDesugaringEventConsumer createForD8(
AppView<?> appView,
@@ -259,6 +261,11 @@
}
@Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
+ }
+
+ @Override
public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
clazz
.programMethods()
@@ -558,6 +565,11 @@
}
@Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ // Intentionally empty. The method will be hit by tracing if required.
+ }
+
+ @Override
public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
// Intentionally empty. The class will be hit by tracing if required.
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index f31a4a7..dd8e727 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPICallbackSynthesizer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarerPostProcessor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterPostProcessor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterPostProcessor;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
import com.android.tools.r8.ir.desugar.records.RecordClassDesugaring;
@@ -64,6 +65,9 @@
&& !appView.options().getLibraryDesugaringOptions().isDesugaredLibraryCompilation()) {
desugarings.add(new DesugaredLibraryRetargeterPostProcessor(appView));
}
+ if (appView.options().testing.enableAutoCloseableDesugaring) {
+ desugarings.add(new AutoCloseableRetargeterPostProcessor(appView));
+ }
if (interfaceMethodProcessorFacade != null) {
desugarings.add(interfaceMethodProcessorFacade);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 9cdb6e6..e1a3cd9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -11,10 +11,12 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.D8MethodProcessor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer.AutoCloseableRetargeterPostProcessingEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
@@ -35,7 +37,8 @@
public abstract class CfPostProcessingDesugaringEventConsumer
implements DesugaredLibraryRetargeterPostProcessingEventConsumer,
InterfaceProcessingDesugaringEventConsumer,
- DesugaredLibraryAPICallbackSynthesizorEventConsumer {
+ DesugaredLibraryAPICallbackSynthesizorEventConsumer,
+ AutoCloseableRetargeterPostProcessingEventConsumer {
public static CfPostProcessingDesugaringEventConsumer createForD8(
AppView<?> appView,
@@ -108,6 +111,12 @@
}
@Override
+ public void acceptAutoCloseableInterfaceInjection(
+ DexProgramClass clazz, DexClass newInterface) {
+ // Intentionally empty.
+ }
+
+ @Override
public void acceptEmulatedInterfaceMarkerInterface(
DexProgramClass clazz, DexClasspathClass newInterface) {
// Intentionally empty.
@@ -169,6 +178,17 @@
public void acceptGenericApiConversionStub(DexClasspathClass dexClasspathClass) {
// Intentionally empty.
}
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ addMethodToReprocess(method);
+ }
+
+ @Override
+ public void acceptAutoCloseableForwardingMethod(
+ ProgramMethod method, ProgramDefinition context) {
+ addMethodToReprocess(method);
+ }
}
public static class R8PostProcessingDesugaringEventConsumer
@@ -218,6 +238,12 @@
}
@Override
+ public void acceptAutoCloseableInterfaceInjection(
+ DexProgramClass clazz, DexClass newInterface) {
+ additions.injectInterface(clazz, newInterface);
+ }
+
+ @Override
public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
additions.addLiveClasspathClass(clazz);
}
@@ -271,5 +297,16 @@
public void acceptGenericApiConversionStub(DexClasspathClass clazz) {
additions.addLiveClasspathClass(clazz);
}
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ additions.addLiveMethod(method);
+ }
+
+ @Override
+ public void acceptAutoCloseableForwardingMethod(
+ ProgramMethod method, ProgramDefinition context) {
+ additions.addLiveMethod(method);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 4442dfe..604920f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicInstructionDesugaring;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryLibRewriter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.icce.AlwaysThrowingInstructionDesugaring;
@@ -120,6 +121,9 @@
if (desugaredLibraryRetargeter != null) {
desugarings.add(desugaredLibraryRetargeter);
}
+ if (appView.options().shouldDesugarAutoCloseable()) {
+ desugarings.add(new AutoCloseableRetargeter(appView));
+ }
disableDesugarer = DesugaredLibraryDisableDesugarer.create(appView);
if (disableDesugarer != null) {
desugarings.add(disableDesugarer);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 24224c7..02eda10 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -106,6 +106,7 @@
factory.createSynthesizedType("Ljava/util/OptionalLong;");
factory.createSynthesizedType("Ljava/util/Set;");
factory.createSynthesizedType("Ljava/util/concurrent/ExecutorService;");
+ factory.createSynthesizedType("Ljava/util/concurrent/ForkJoinPool;");
factory.createSynthesizedType("Ljava/util/concurrent/TimeUnit;");
factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReference;");
factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReferenceArray;");
@@ -2430,12 +2431,42 @@
CfLabel label13 = new CfLabel();
CfLabel label14 = new CfLabel();
CfLabel label15 = new CfLabel();
+ CfLabel label16 = new CfLabel();
+ CfLabel label17 = new CfLabel();
+ CfLabel label18 = new CfLabel();
return new CfCode(
method.holder,
4,
4,
ImmutableList.of(
label0,
+ new CfStaticFieldRead(
+ factory.createField(
+ factory.createType("Landroid/os/Build$VERSION;"),
+ factory.intType,
+ factory.createString("SDK_INT"))),
+ new CfConstNumber(23, ValueType.INT),
+ new CfIfCmp(IfType.LE, ValueType.INT, label3),
+ label1,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 184,
+ factory.createMethod(
+ factory.createType("Ljava/util/concurrent/ForkJoinPool;"),
+ factory.createProto(factory.createType("Ljava/util/concurrent/ForkJoinPool;")),
+ factory.createString("commonPool")),
+ false),
+ new CfIfCmp(IfType.NE, ValueType.OBJECT, label3),
+ label2,
+ new CfReturnVoid(),
+ label3,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(
+ new int[] {0},
+ new FrameType[] {
+ FrameType.initializedNonNullReference(
+ factory.createType("Ljava/util/concurrent/ExecutorService;"))
+ })),
new CfLoad(ValueType.OBJECT, 0),
new CfInvoke(
185,
@@ -2445,10 +2476,10 @@
factory.createString("isTerminated")),
true),
new CfStore(ValueType.INT, 1),
- label1,
+ label4,
new CfLoad(ValueType.INT, 1),
- new CfIf(IfType.NE, ValueType.INT, label14),
- label2,
+ new CfIf(IfType.NE, ValueType.INT, label17),
+ label5,
new CfLoad(ValueType.OBJECT, 0),
new CfInvoke(
185,
@@ -2457,10 +2488,10 @@
factory.createProto(factory.voidType),
factory.createString("shutdown")),
true),
- label3,
+ label6,
new CfConstNumber(0, ValueType.INT),
new CfStore(ValueType.INT, 2),
- label4,
+ label7,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -2471,8 +2502,8 @@
FrameType.intType()
})),
new CfLoad(ValueType.INT, 1),
- new CfIf(IfType.NE, ValueType.INT, label12),
- label5,
+ new CfIf(IfType.NE, ValueType.INT, label15),
+ label8,
new CfLoad(ValueType.OBJECT, 0),
new CfConstNumber(1, ValueType.LONG),
new CfStaticFieldRead(
@@ -2491,9 +2522,9 @@
factory.createString("awaitTermination")),
true),
new CfStore(ValueType.INT, 1),
- label6,
- new CfGoto(label4),
- label7,
+ label9,
+ new CfGoto(label7),
+ label10,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -2508,10 +2539,10 @@
FrameType.initializedNonNullReference(
factory.createType("Ljava/lang/InterruptedException;"))))),
new CfStore(ValueType.OBJECT, 3),
- label8,
+ label11,
new CfLoad(ValueType.INT, 2),
- new CfIf(IfType.NE, ValueType.INT, label11),
- label9,
+ new CfIf(IfType.NE, ValueType.INT, label14),
+ label12,
new CfLoad(ValueType.OBJECT, 0),
new CfInvoke(
185,
@@ -2521,10 +2552,10 @@
factory.createString("shutdownNow")),
true),
new CfStackInstruction(CfStackInstruction.Opcode.Pop),
- label10,
+ label13,
new CfConstNumber(1, ValueType.INT),
new CfStore(ValueType.INT, 2),
- label11,
+ label14,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -2534,8 +2565,8 @@
FrameType.intType(),
FrameType.intType()
})),
- new CfGoto(label4),
- label12,
+ new CfGoto(label7),
+ label15,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -2546,8 +2577,8 @@
FrameType.intType()
})),
new CfLoad(ValueType.INT, 2),
- new CfIf(IfType.EQ, ValueType.INT, label14),
- label13,
+ new CfIf(IfType.EQ, ValueType.INT, label17),
+ label16,
new CfInvoke(
184,
factory.createMethod(
@@ -2562,7 +2593,7 @@
factory.createProto(factory.voidType),
factory.createString("interrupt")),
false),
- label14,
+ label17,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1},
@@ -2572,13 +2603,13 @@
FrameType.intType()
})),
new CfReturnVoid(),
- label15),
+ label18),
ImmutableList.of(
new CfTryCatch(
- label5,
- label6,
+ label8,
+ label9,
ImmutableList.of(factory.createType("Ljava/lang/InterruptedException;")),
- ImmutableList.of(label7))),
+ ImmutableList.of(label10))),
ImmutableList.of());
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/PrefixRewritingNamingLens.java
index 77e22b9..91fd4a5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/PrefixRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/PrefixRewritingNamingLens.java
@@ -116,4 +116,9 @@
public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
return namingLens.verifyRenamingConsistentWithResolution(item);
}
+
+ @Override
+ public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+ return namingLens;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java
new file mode 100644
index 0000000..703067b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java
@@ -0,0 +1,186 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper.createCloseMethod;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper.lookupSuperIncludingInterfaces;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.IntConsumer;
+import org.objectweb.asm.Opcodes;
+
+public class AutoCloseableRetargeter implements CfInstructionDesugaring {
+
+ private final AppView<?> appView;
+ private final AutoCloseableRetargeterHelper data;
+
+ public AutoCloseableRetargeter(AppView<?> appView) {
+ this.appView = appView;
+ this.data =
+ new AutoCloseableRetargeterHelper(
+ appView.options().getMinApiLevel(), appView.dexItemFactory());
+ }
+
+ public DexMethod synthesizeDispatcher(
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ AutoCloseableRetargeterEventConsumer eventConsumer) {
+ DexItemFactory factory = appView.dexItemFactory();
+ LinkedHashMap<DexType, DexMethod> dispatchCases =
+ data.synthesizeDispatchCases(appView, context, eventConsumer, methodProcessingContext);
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.AUTOCLOSEABLE_DISPATCHER,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setProto(factory.createProto(factory.voidType, factory.objectType))
+ .setCode(
+ methodSig ->
+ new EmulateDispatchSyntheticCfCodeProvider(
+ methodSig.getHolderType(),
+ data.createThrowUnsupportedException(
+ appView,
+ context,
+ eventConsumer,
+ methodProcessingContext::createUniqueContext)
+ .getReference(),
+ createCloseMethod(factory, factory.autoCloseableType),
+ dispatchCases,
+ EmulateDispatchType.AUTO_CLOSEABLE,
+ appView)
+ .generateCfCode()));
+ eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+ return method.getReference();
+ }
+
+ @Override
+ public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
+ consumer.accept(Opcodes.INVOKEVIRTUAL);
+ consumer.accept(Opcodes.INVOKESPECIAL);
+ consumer.accept(Opcodes.INVOKEINTERFACE);
+ }
+
+ @Override
+ public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
+ if (instruction.isInvoke() && !instruction.isInvokeStatic()) {
+ return computeInvokeDescription(instruction, context);
+ }
+ return DesugarDescription.nothing();
+ }
+
+ private DesugarDescription computeInvokeDescription(
+ CfInstruction instruction, ProgramMethod context) {
+ if (appView
+ .getSyntheticItems()
+ .isSyntheticOfKind(context.getHolderType(), kinds -> kinds.AUTOCLOSEABLE_DISPATCHER)) {
+ return DesugarDescription.nothing();
+ }
+ CfInvoke cfInvoke = instruction.asInvoke();
+ DexMethod invokedMethod = cfInvoke.getMethod();
+ if (!data.hasCloseMethodName(invokedMethod) || invokedMethod.getArity() > 0) {
+ return DesugarDescription.nothing();
+ }
+ AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+ MethodResolutionResult resolutionResult =
+ appInfo.resolveMethodLegacy(invokedMethod, cfInvoke.isInterface());
+ if (!resolutionResult.isSingleResolution()) {
+ return DesugarDescription.nothing();
+ }
+ assert resolutionResult.getSingleTarget() != null;
+ DexMethod reference = resolutionResult.getSingleTarget().getReference();
+ if (data.shouldEmulateMethod(reference)) {
+ return computeNewTarget(reference, cfInvoke.isInvokeSuper(context.getHolderType()), context);
+ }
+ return DesugarDescription.nothing();
+ }
+
+ private DesugarDescription computeNewTarget(
+ DexMethod singleTarget, boolean superInvoke, ProgramMethod context) {
+ if (superInvoke) {
+ DexClassAndMethod superMethod =
+ lookupSuperIncludingInterfaces(appView, singleTarget, context.getContextClass());
+ if (superMethod != null && superMethod.isLibraryMethod()) {
+ DexType holderType = superMethod.getHolderType();
+ if (data.superTargetsToRewrite().contains(holderType)) {
+ return createWithTarget(
+ singleTarget,
+ (eventConsumer, methodProcessingContext) ->
+ data.synthesizeDispatchCase(
+ appView,
+ holderType,
+ context,
+ eventConsumer,
+ methodProcessingContext::createUniqueContext));
+ }
+ }
+ return DesugarDescription.nothing();
+ }
+ return createWithTarget(
+ singleTarget,
+ (eventConsumer, methodProcessingContext) ->
+ synthesizeDispatcher(context, methodProcessingContext, eventConsumer));
+ }
+
+ private DesugarDescription createWithTarget(
+ DexMethod target,
+ BiFunction<CfInstructionDesugaringEventConsumer, MethodProcessingContext, DexMethod>
+ methodProvider) {
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (position,
+ freshLocalProvider,
+ localStackAllocator,
+ desugaringInfo,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ desugarings,
+ dexItemFactory) -> {
+ DexMethod newInvokeTarget =
+ methodProvider.apply(eventConsumer, methodProcessingContext);
+ assert appView.definitionFor(newInvokeTarget.getHolderType()) != null;
+ assert !appView.definitionFor(newInvokeTarget.getHolderType()).isInterface();
+ List<CfInstruction> instructions = new ArrayList<>();
+ if (appView.getSyntheticItems().isSynthetic(newInvokeTarget.getHolderType())) {
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, false));
+ } else {
+ instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newInvokeTarget, false));
+ }
+ if (target.getReturnType().isVoidType()
+ && !newInvokeTarget.getReturnType().isVoidType()) {
+ instructions.add(new CfStackInstruction(Opcode.Pop));
+ }
+ return instructions;
+ })
+ .build();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java
new file mode 100644
index 0000000..9a29029
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface AutoCloseableRetargeterEventConsumer {
+
+ void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context);
+
+ interface AutoCloseableRetargeterPostProcessingEventConsumer
+ extends AutoCloseableRetargeterEventConsumer {
+
+ void acceptAutoCloseableForwardingMethod(ProgramMethod method, ProgramDefinition context);
+
+ void acceptAutoCloseableInterfaceInjection(DexProgramClass clazz, DexClass newInterface);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java
new file mode 100644
index 0000000..3597e57
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java
@@ -0,0 +1,234 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
+import com.android.tools.r8.ir.synthetic.ThrowCfCodeProvider;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableSet;
+import java.util.LinkedHashMap;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class AutoCloseableRetargeterHelper {
+
+ private final AndroidApiLevel minApiLevel;
+ private final DexItemFactory factory;
+ private final DexString close;
+ private final Set<DexMethod> methodsToEmulate;
+
+ public AutoCloseableRetargeterHelper(AndroidApiLevel minApiLevel, DexItemFactory factory) {
+ this.minApiLevel = minApiLevel;
+ this.factory = factory;
+ this.close = factory.createString("close");
+ this.methodsToEmulate = methodsToEmulate();
+ }
+
+ static DexMethod createCloseMethod(DexItemFactory factory, DexType holderType) {
+ return factory.createMethod(holderType, factory.createProto(factory.voidType), "close");
+ }
+
+ public boolean hasCloseMethodName(DexMethod method) {
+ return method.getName().isIdenticalTo(close);
+ }
+
+ public boolean shouldEmulateMethod(DexMethod method) {
+ return methodsToEmulate.contains(method);
+ }
+
+ // This includes all library types which implements AutoCloseable#close() and their subtypes.
+ // We exclude android.media.MediaDrm which is final and rewritten by the backportedMethodRewriter.
+ private Set<DexMethod> methodsToEmulate() {
+ ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+ forEachAutoCloseableMissingSubimplementation(
+ type -> {
+ if (!type.isIdenticalTo(factory.androidMediaMediaDrmType)) {
+ builder.add(createCloseMethod(factory, type));
+ }
+ });
+ builder.add(createCloseMethod(factory, factory.autoCloseableType));
+ return builder.build();
+ }
+
+ // This includes all library types which implements directly AutoCloseable#close() including
+ // android.media.MediaDrm.
+ public static void forEachAutoCloseableMissingSubimplementation(
+ Consumer<DexType> consumer,
+ AndroidApiLevel minApiLevel,
+ DexItemFactory factory,
+ boolean withSubtypes) {
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.V)) {
+ consumer.accept(factory.javaUtilConcurrentExecutorServiceType);
+ consumer.accept(factory.javaUtilConcurrentForkJoinPoolType);
+ if (withSubtypes) {
+ consumer.accept(factory.createType("Ljava/util/concurrent/ScheduledExecutorService;"));
+ consumer.accept(factory.createType("Ljava/util/concurrent/AbstractExecutorService;"));
+ consumer.accept(factory.createType("Ljava/util/concurrent/ThreadPoolExecutor;"));
+ consumer.accept(factory.createType("Ljava/util/concurrent/ScheduledThreadPoolExecutor;"));
+ }
+ }
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.R)) {
+ consumer.accept(factory.androidContentResTypedArrayType);
+ }
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.P)) {
+ consumer.accept(factory.androidMediaMediaMetadataRetrieverType);
+ }
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.O_MR1)) {
+ consumer.accept(factory.androidMediaMediaDrmType);
+ }
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.M)) {
+ consumer.accept(factory.androidDrmDrmManagerClientType);
+ consumer.accept(factory.androidContentContentProviderClientType);
+ }
+ }
+
+ private void forEachAutoCloseableMissingSubimplementation(Consumer<DexType> consumer) {
+ forEachAutoCloseableMissingSubimplementation(consumer, minApiLevel, factory, false);
+ }
+
+ // This includes all library types which implements directly AutoCloseable#close() including
+ // android.media.MediaDrm, however, android.media.MediaDrm is final and rewritten if called
+ // directly by the backported method rewriter.
+ public LinkedHashMap<DexType, DexMethod> synthesizeDispatchCases(
+ AppView<?> appView,
+ ProgramMethod context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ MethodProcessingContext methodProcessingContext) {
+ LinkedHashMap<DexType, DexMethod> map = new LinkedHashMap<>();
+ forEachAutoCloseableMissingSubimplementation(
+ type -> {
+ // ForkJoinPool has an optimized version of ExecutorService.close. ForkJoinPool is not
+ // present in 19 (added in 21) so R8 cannot use instanceof ForkJoinPool in the emulated
+ // dispatch. We rely on ForkJoinPool implementing ExecutorService and use that path.
+ if (type.isNotIdenticalTo(factory.javaUtilConcurrentForkJoinPoolType)) {
+ map.put(
+ type,
+ synthesizeDispatchCase(
+ appView,
+ type,
+ context,
+ eventConsumer,
+ methodProcessingContext::createUniqueContext));
+ }
+ });
+ return map;
+ }
+
+ public Set<DexType> superTargetsToRewrite() {
+ ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+ forEachAutoCloseableMissingSubimplementation(builder::add);
+ return builder.build();
+ }
+
+ public DexMethod synthesizeDispatchCase(
+ AppView<?> appView,
+ DexType type,
+ ProgramDefinition context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier) {
+ assert superTargetsToRewrite().contains(type);
+ if (type.isIdenticalTo(factory.javaUtilConcurrentExecutorServiceType)
+ || type.isIdenticalTo(factory.javaUtilConcurrentForkJoinPoolType)) {
+ // For ForkJoinPool.close R8 uses the less efficient ExecutorService.close.
+ // ExecutorService.close does not however use unreachable apis and ExecutorService is present
+ // at Android api 19.
+ return synthesizeExecutorServiceDispatchCase(
+ appView, context, eventConsumer, contextSupplier);
+ }
+ if (type.isIdenticalTo(factory.androidContentResTypedArrayType)) {
+ return factory.createMethod(type, factory.createProto(factory.voidType), "recycle");
+ }
+ if (type.isIdenticalTo(factory.androidContentContentProviderClientType)) {
+ return factory.createMethod(type, factory.createProto(factory.booleanType), "release");
+ }
+ assert ImmutableSet.of(
+ factory.androidMediaMediaMetadataRetrieverType,
+ factory.androidMediaMediaDrmType,
+ factory.androidDrmDrmManagerClientType)
+ .contains(type);
+ return factory.createMethod(type, factory.createProto(factory.voidType), "release");
+ }
+
+ private DexMethod synthesizeExecutorServiceDispatchCase(
+ AppView<?> appView,
+ ProgramDefinition context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier) {
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.AUTOCLOSEABLE_FORWARDER,
+ contextSupplier.get(),
+ appView,
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setProto(
+ factory.createProto(
+ factory.voidType, factory.javaUtilConcurrentExecutorServiceType))
+ .setCode(
+ methodSig ->
+ BackportedMethods.ExecutorServiceMethods_closeExecutorService(
+ factory, methodSig)));
+ eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+ return method.getReference();
+ }
+
+ ProgramMethod createThrowUnsupportedException(
+ AppView<?> appView,
+ ProgramDefinition context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier) {
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.THROW_IAE,
+ contextSupplier.get(),
+ appView,
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setProto(factory.createProto(factory.voidType, factory.objectType))
+ .setCode(
+ methodSig ->
+ new ThrowCfCodeProvider(
+ appView,
+ methodSig.getHolderType(),
+ factory.illegalArgumentExceptionType,
+ null)
+ .generateCfCode()));
+ eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+ return method;
+ }
+
+ static DexClassAndMethod lookupSuperIncludingInterfaces(
+ AppView<?> appView, DexMethod target, DexProgramClass context) {
+ DexClassAndMethod superMethod =
+ appView
+ .appInfoForDesugaring()
+ .lookupSuperTarget(target, context, appView, appView.appInfoForDesugaring());
+ if (superMethod != null) {
+ return superMethod;
+ }
+ return appView
+ .appInfoForDesugaring()
+ .lookupMaximallySpecificMethod(context.getContextClass(), target);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.java
new file mode 100644
index 0000000..b45317b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.java
@@ -0,0 +1,211 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper.lookupSuperIncludingInterfaces;
+
+import com.android.tools.r8.contexts.CompilationContext.MainThreadContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer.AutoCloseableRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+
+// The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
+// for inserting interfaces on library boundaries and forwarding methods in the program, and to
+// synthesize the interfaces and emulated dispatch classes in the desugared library.
+public class AutoCloseableRetargeterPostProcessor implements CfPostProcessingDesugaring {
+
+ private final AppView<?> appView;
+ private final AutoCloseableRetargeterHelper data;
+
+ public AutoCloseableRetargeterPostProcessor(AppView<?> appView) {
+ this.appView = appView;
+ this.data =
+ new AutoCloseableRetargeterHelper(
+ appView.options().getMinApiLevel(), appView.dexItemFactory());
+ }
+
+ @Override
+ public void postProcessingDesugaring(
+ Collection<DexProgramClass> programClasses,
+ CfPostProcessingDesugaringEventConsumer eventConsumer,
+ ExecutorService executorService) {
+ ensureInterfacesAndForwardingMethodsSynthesized(programClasses, eventConsumer);
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private void ensureInterfacesAndForwardingMethodsSynthesized(
+ Collection<DexProgramClass> programClasses,
+ AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer) {
+ ProcessorContext processorContext = appView.createProcessorContext();
+ MainThreadContext mainThreadContext = processorContext.createMainThreadContext();
+ for (DexProgramClass clazz : programClasses) {
+ if (clazz.superType == null) {
+ assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
+ continue;
+ }
+ if (implementsAutoCloseableAtLibraryBoundary(clazz)) {
+ ensureInterfacesAndForwardingMethodsSynthesized(eventConsumer, clazz, mainThreadContext);
+ }
+ }
+ }
+
+ private boolean implementsAutoCloseableAtLibraryBoundary(DexProgramClass clazz) {
+ if (clazz.interfaces.contains(appView.dexItemFactory().autoCloseableType)) {
+ return true;
+ }
+ WorkList<DexType> workList = collectLibrarySuperTypeAndInterfaces(clazz);
+ return libraryTypesImplementsAutoCloseable(workList, clazz);
+ }
+
+ private WorkList<DexType> collectLibrarySuperTypeAndInterfaces(DexProgramClass clazz) {
+ WorkList<DexType> workList = WorkList.newIdentityWorkList();
+ DexClass superclass = appView.definitionFor(clazz.superType);
+ // Only performs computation if superclass is a library class, but not object to filter out
+ // the most common case.
+ if (superclass != null
+ && superclass.isLibraryClass()
+ && !superclass.type.isIdenticalTo(appView.dexItemFactory().objectType)) {
+ workList.addIfNotSeen(superclass.type);
+ }
+ for (DexType itf : clazz.interfaces) {
+ DexClass superItf = appView.definitionFor(itf);
+ if (superItf != null) {
+ workList.addIfNotSeen(superItf.type);
+ }
+ }
+ return workList;
+ }
+
+ private boolean libraryTypesImplementsAutoCloseable(
+ WorkList<DexType> workList, DexProgramClass clazz) {
+ while (workList.hasNext()) {
+ DexType current = workList.next();
+ if (current.isIdenticalTo(appView.dexItemFactory().objectType)) {
+ continue;
+ }
+ DexClass currentClass = appView.definitionFor(current);
+ if (currentClass == null) {
+ reportInvalidSupertype(current, clazz);
+ continue;
+ }
+ if (currentClass.interfaces.contains(appView.dexItemFactory().autoCloseableType)) {
+ return true;
+ }
+ workList.addIfNotSeen(currentClass.superType);
+ workList.addIfNotSeen(currentClass.interfaces);
+ }
+ return false;
+ }
+
+ private void reportInvalidSupertype(DexType current, DexProgramClass origin) {
+ appView
+ .options()
+ .warningInvalidLibrarySuperclassForDesugar(
+ origin.getOrigin(),
+ origin.type,
+ current,
+ "missing",
+ ImmutableSet.of(getClose(origin.type)));
+ }
+
+ private void ensureInterfacesAndForwardingMethodsSynthesized(
+ AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer,
+ DexProgramClass clazz,
+ MainThreadContext mainThreadContext) {
+ // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
+ // methods.
+ // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+ // applies up to 24.
+ if (appView.isAlreadyLibraryDesugared(clazz)) {
+ return;
+ }
+ DexMethod close = getClose(clazz.type);
+ if (clazz.lookupVirtualMethod(close) == null) {
+ DexEncodedMethod newMethod =
+ createForwardingMethod(close, clazz, eventConsumer, mainThreadContext);
+ if (newMethod == null) {
+ // We don't support desugaring on all subtypes, in which case there is not need to inject
+ // the interface.
+ return;
+ }
+ clazz.addVirtualMethod(newMethod);
+ eventConsumer.acceptAutoCloseableForwardingMethod(new ProgramMethod(clazz, newMethod), clazz);
+ }
+ DexType autoCloseableType = appView.dexItemFactory().autoCloseableType;
+ if (clazz.interfaces.contains(autoCloseableType)) {
+ return;
+ }
+ clazz.addExtraInterfaces(
+ Collections.singletonList(new ClassTypeSignature(autoCloseableType)),
+ appView.dexItemFactory());
+ eventConsumer.acceptAutoCloseableInterfaceInjection(
+ clazz, appView.definitionFor(autoCloseableType));
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private DexEncodedMethod createForwardingMethod(
+ DexMethod target,
+ DexProgramClass clazz,
+ AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer,
+ MainThreadContext mainThreadContext) {
+ // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
+ // even if this results in invalid code, these classes are never desugared.
+ // In desugared library, emulated interface methods can be overridden by retarget lib members.
+ AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+ assert clazz.lookupVirtualMethod(target) == null;
+ DexClassAndMethod superMethod = lookupSuperIncludingInterfaces(appView, target, clazz);
+ if (superMethod == null
+ || !data.superTargetsToRewrite().contains(superMethod.getHolderType())) {
+ return null;
+ }
+ DexMethod forwardMethod =
+ data.synthesizeDispatchCase(
+ appView,
+ superMethod.getHolderType(),
+ clazz,
+ eventConsumer,
+ () -> mainThreadContext.createUniqueContext(clazz));
+ assert forwardMethod != null && forwardMethod != target;
+ DexClassAndMethod resolvedMethod =
+ appInfoForDesugaring
+ .resolveMethodLegacy(target, target.getHolderType().isInterface(appInfoForDesugaring))
+ .getResolutionPair();
+ assert resolvedMethod != null;
+ DexEncodedMethod desugaringForwardingMethod =
+ DexEncodedMethod.createDesugaringForwardingMethod(
+ resolvedMethod,
+ clazz,
+ forwardMethod,
+ appView.dexItemFactory(),
+ appView.getSyntheticItems().isSynthetic(forwardMethod.getHolderType()));
+ desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+ return desugaringForwardingMethod;
+ }
+
+ private DexMethod getClose(DexType holder) {
+ return appView
+ .dexItemFactory()
+ .createMethod(
+ holder,
+ appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
+ "close");
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
index c4be060..fa05f19 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
@@ -161,7 +161,7 @@
assert resolvedMethod != null;
DexEncodedMethod desugaringForwardingMethod =
DexEncodedMethod.createDesugaringForwardingMethod(
- resolvedMethod, clazz, forwardMethod, appView.dexItemFactory());
+ resolvedMethod, clazz, forwardMethod, appView.dexItemFactory(), true);
desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
return desugaringForwardingMethod;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
index 5777971..3413d51 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.synthesis.SyntheticClassBuilder;
import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
@@ -284,7 +285,12 @@
DexMethod itfMethod = emulatedInterfaceDispatchMethod(itfClass, descriptor);
assert descriptor.getDispatchCases().isEmpty();
return new EmulateDispatchSyntheticCfCodeProvider(
- methodSig.getHolderType(), forwardingMethod, itfMethod, new LinkedHashMap<>(), appView)
+ methodSig.getHolderType(),
+ forwardingMethod,
+ itfMethod,
+ new LinkedHashMap<>(),
+ EmulateDispatchType.ALL_STATIC,
+ appView)
.generateCfCode();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index c2a1998..2b89cbb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -1038,7 +1038,7 @@
// In desugared library, emulated interface methods can be overridden by retarget lib members.
DexEncodedMethod desugaringForwardingMethod =
DexEncodedMethod.createDesugaringForwardingMethod(
- target, clazz, forwardMethod, dexItemFactory);
+ target, clazz, forwardMethod, dexItemFactory, true);
if (!target.isProgramMethod() || target.getDefinition().isLibraryMethodOverride().isTrue()) {
desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
index 20086db..968ecad 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedInterfaceDescriptor;
import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer;
import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -105,6 +106,7 @@
companionMethod,
itfMethod,
extraDispatchCases,
+ EmulateDispatchType.ALL_STATIC,
appView)
.generateCfCode());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ListIterationRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ListIterationRewriter.java
index 84aa7a6..e9689e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ListIterationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ListIterationRewriter.java
@@ -121,7 +121,7 @@
public static boolean shouldEnable(
AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
TestingOptions opts = appView.options().testing;
- if (!appView.hasLiveness() || !opts.listIterationRewritingEnabled) {
+ if (!appView.hasLiveness()) {
return false;
}
if (opts.listIterationRewritingRewriteCustomIterators) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
index 9caa410..2604cef 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.ir.synthetic;
+import static com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType.ALL_STATIC;
+import static com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType.AUTO_CLOSEABLE;
+
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfIf;
@@ -29,8 +32,14 @@
public class EmulateDispatchSyntheticCfCodeProvider extends SyntheticCfCodeProvider {
+ public enum EmulateDispatchType {
+ ALL_STATIC,
+ AUTO_CLOSEABLE
+ }
+
private final DexMethod forwardingMethod;
private final DexMethod interfaceMethod;
+ private final EmulateDispatchType dispatchType;
private final LinkedHashMap<DexType, DexMethod> extraDispatchCases;
public EmulateDispatchSyntheticCfCodeProvider(
@@ -38,11 +47,13 @@
DexMethod forwardingMethod,
DexMethod interfaceMethod,
LinkedHashMap<DexType, DexMethod> extraDispatchCases,
+ EmulateDispatchType dispatchType,
AppView<?> appView) {
super(appView, holder);
this.forwardingMethod = forwardingMethod;
this.interfaceMethod = interfaceMethod;
this.extraDispatchCases = extraDispatchCases;
+ this.dispatchType = dispatchType;
}
@Override
@@ -89,8 +100,7 @@
// Call basic block.
instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0));
instructions.add(new CfCheckCast(dispatch.getKey()));
- loadExtraParameters(instructions);
- instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, dispatch.getValue(), false));
+ forwardCall(instructions, dispatch.getValue());
addReturn(instructions);
}
@@ -98,12 +108,28 @@
instructions.add(labels[nextLabel]);
instructions.add(frame.clone());
instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0));
- loadExtraParameters(instructions);
- instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, forwardingMethod, false));
+ forwardCall(instructions, forwardingMethod);
addReturn(instructions);
return standardCfCodeFromInstructions(instructions);
}
+ private void forwardCall(List<CfInstruction> instructions, DexMethod method) {
+ if (dispatchType == ALL_STATIC
+ || appView.getSyntheticItems().isSynthetic(method.getHolderType())) {
+ loadExtraParameters(instructions);
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+ return;
+ }
+ assert dispatchType == AUTO_CLOSEABLE;
+ instructions.add(new CfCheckCast(method.holder));
+ loadExtraParameters(instructions);
+ if (appView.definitionFor(method.getHolderType()).isInterface()) {
+ instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, method, true));
+ } else {
+ instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
+ }
+ }
+
private void loadExtraParameters(List<CfInstruction> instructions) {
int index = 1;
for (DexType type : interfaceMethod.proto.parameters.values) {
@@ -113,10 +139,10 @@
@SuppressWarnings("ReferenceEquality")
private void addReturn(List<CfInstruction> instructions) {
- if (interfaceMethod.proto.returnType == appView.dexItemFactory().voidType) {
+ if (interfaceMethod.getReturnType().isVoidType()) {
instructions.add(new CfReturnVoid());
} else {
- instructions.add(new CfReturn(ValueType.fromDexType(interfaceMethod.proto.returnType)));
+ instructions.add(new CfReturn(ValueType.fromDexType(interfaceMethod.getReturnType())));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index b8c5959..77a8cd8 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -67,6 +67,7 @@
private InvokeType invokeType = null;
private Boolean isInterface = null;
private boolean castResult = false;
+ private boolean ignoreTargetResult = false;
private boolean isConstructorDelegate = false;
private AppInfoWithClassHierarchy appInfoForCastArguments = null;
@@ -157,6 +158,11 @@
return this;
}
+ public ForwardMethodBuilder setIgnoreTargetResult() {
+ ignoreTargetResult = true;
+ return this;
+ }
+
public ForwardMethodBuilder setCastArguments(AppInfoWithClassHierarchy appInfo) {
appInfoForCastArguments = appInfo;
return this;
@@ -380,6 +386,9 @@
&& !targetMethod.getReturnType().isVoidType()) {
assert ValueType.fromDexType(sourceMethod.getReturnType())
== ValueType.fromDexType(targetMethod.getReturnType());
+ } else if (ignoreTargetResult) {
+ assert sourceMethod.getReturnType().isVoidType();
+ assert !targetMethod.getReturnType().isVoidType();
} else {
assert sourceMethod.getReturnType() == targetMethod.getReturnType();
}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java
new file mode 100644
index 0000000..bb33502
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java
@@ -0,0 +1,601 @@
+// Copyright (c) 2025, 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.jar;
+
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.SourceFileEnvironment;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.errors.CodeSizeOverflowDiagnostic;
+import com.android.tools.r8.errors.ConstantPoolOverflowDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeAnnotation;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RecordComponentInfo;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.utils.AsmUtils;
+import com.android.tools.r8.utils.ComparatorUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassTooLargeException;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodTooLargeException;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypePath;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.util.CheckClassAdapter;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceMethodVisitor;
+
+public class CfApplicationClassWriter {
+
+ // First item inserted into the constant pool is the marker string which generates an UTF8 to
+ // pool index #1 and a String entry to #2, referencing #1.
+ public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
+
+ private static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
+ private static final boolean PRINT_CF = false;
+ private static final boolean RUN_VERIFIER = false;
+
+ private final AppView<?> appView;
+ private final DexProgramClass clazz;
+ private final DexItemFactory factory;
+ private final NamingLens namingLens;
+ private final InternalOptions options;
+
+ CfApplicationClassWriter(AppView<?> appView, DexProgramClass clazz) {
+ this.appView = appView;
+ this.clazz = clazz;
+ this.factory = appView.dexItemFactory();
+ // For "pass through" classes which has already been library desugared use the identity lens.
+ this.namingLens =
+ appView.isAlreadyLibraryDesugared(clazz)
+ ? appView.getNamingLens().withoutDesugaredLibraryPrefixRewritingNamingLens()
+ : appView.getNamingLens();
+ this.options = appView.options();
+ }
+
+ private boolean isTypeMissing(DexType type) {
+ return !appView.appInfo().hasDefinitionForWithoutExistenceAssert(type);
+ }
+
+ void writeClassCatchingErrors(
+ ClassFileConsumer consumer,
+ LensCodeRewriterUtils rewriter,
+ Optional<String> markerString,
+ SourceFileEnvironment sourceFileEnvironment) {
+ assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
+ try {
+ writeClass(consumer, rewriter, markerString, sourceFileEnvironment);
+ } catch (ClassTooLargeException e) {
+ throw options.reporter.fatalError(
+ new ConstantPoolOverflowDiagnostic(
+ clazz.getOrigin(),
+ Reference.classFromBinaryName(e.getClassName()),
+ e.getConstantPoolCount()));
+ } catch (MethodTooLargeException e) {
+ throw options.reporter.fatalError(
+ new CodeSizeOverflowDiagnostic(
+ clazz.getOrigin(),
+ Reference.methodFromDescriptor(
+ Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
+ e.getMethodName(),
+ e.getDescriptor()),
+ e.getCodeSize()));
+ }
+ }
+
+ private void writeClass(
+ ClassFileConsumer consumer,
+ LensCodeRewriterUtils rewriter,
+ Optional<String> markerString,
+ SourceFileEnvironment sourceFileEnvironment) {
+ ClassWriter writer = new ClassWriter(0);
+ if (markerString.isPresent()) {
+ int markerStringPoolIndex = writer.newConst(markerString.get());
+ assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
+ }
+ String sourceFile;
+ if (options.sourceFileProvider == null) {
+ sourceFile = clazz.sourceFile != null ? clazz.sourceFile.toString() : null;
+ } else {
+ sourceFile = options.sourceFileProvider.get(sourceFileEnvironment);
+ }
+ String sourceDebug = getSourceDebugExtension(clazz.annotations());
+ writer.visitSource(sourceFile, sourceDebug);
+ CfVersion version = getClassFileVersion(clazz);
+ if (version.isGreaterThanOrEqualTo(CfVersion.V1_8)) {
+ // JDK8 and after ignore ACC_SUPER so unset it.
+ clazz.accessFlags.unsetSuper();
+ } else {
+ // In all other cases set the super bit as D8/R8 do not support targeting pre 1.0.2 JDKs.
+ if (!clazz.accessFlags.isInterface()) {
+ clazz.accessFlags.setSuper();
+ }
+ }
+ boolean allowInvalidCfAccessFlags =
+ clazz.getType().getDescriptor().endsWith(factory.createString("/package-info;"));
+ int access =
+ allowInvalidCfAccessFlags || options.testing.allowInvalidCfAccessFlags
+ ? clazz.accessFlags.materialize()
+ : clazz.accessFlags.getAsCfAccessFlags();
+ if (clazz.isDeprecated()) {
+ access = AsmUtils.withDeprecated(access);
+ }
+ String desc = namingLens.lookupDescriptor(clazz.type).toString();
+ String name = namingLens.lookupInternalName(clazz.type);
+ String signature = clazz.getClassSignature().toRenamedString(namingLens, this::isTypeMissing);
+ String superName = clazz.hasSuperType() ? namingLens.lookupInternalName(clazz.superType) : null;
+ String[] interfaces = new String[clazz.interfaces.values.length];
+ for (int i = 0; i < clazz.interfaces.values.length; i++) {
+ interfaces[i] = namingLens.lookupInternalName(clazz.interfaces.values[i]);
+ }
+ assert SyntheticNaming.verifyNotInternalSynthetic(name);
+ writer.visit(version.raw(), access, name, signature, superName, interfaces);
+ appView.getSyntheticItems().writeAttributeIfIntermediateSyntheticClass(writer, clazz, appView);
+ writeAnnotations(
+ writer::visitAnnotation, writer::visitTypeAnnotation, clazz.annotations().annotations);
+ ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
+
+ if (clazz.getEnclosingMethodAttribute() != null) {
+ clazz.getEnclosingMethodAttribute().write(writer, namingLens);
+ }
+
+ if (clazz.getNestHostClassAttribute() != null) {
+ clazz.getNestHostClassAttribute().write(writer, namingLens);
+ }
+
+ for (NestMemberClassAttribute entry : clazz.getNestMembersClassAttributes()) {
+ entry.write(writer, namingLens);
+ assert clazz.getNestHostClassAttribute() == null
+ : "A nest host cannot also be a nest member.";
+ }
+
+ for (PermittedSubclassAttribute entry : clazz.getPermittedSubclassAttributes()) {
+ entry.write(writer, namingLens);
+ }
+
+ if (clazz.isRecord()) {
+ // TODO(b/274888318): Strip record components if not kept.
+ for (RecordComponentInfo info : clazz.getRecordComponents()) {
+ info.write(writer, namingLens, this::isTypeMissing, this::writeAnnotation);
+ }
+ }
+
+ for (InnerClassAttribute entry : clazz.getInnerClasses()) {
+ entry.write(writer, namingLens, options);
+ }
+
+ clazz.forEachProgramStaticField(field -> writeField(field, writer));
+ clazz.forEachProgramInstanceField(field -> writeField(field, writer));
+ if (options.desugarSpecificOptions().sortMethodsOnCfOutput) {
+ List<ProgramMethod> programMethodSorted = new ArrayList<>();
+ clazz.forEachProgramMethod(programMethodSorted::add);
+ programMethodSorted.sort(this::compareMethodsThroughLens);
+ programMethodSorted.forEach(
+ method -> writeMethod(method, version, rewriter, writer, defaults));
+ } else {
+ clazz.forEachProgramMethod(
+ method -> writeMethod(method, version, rewriter, writer, defaults));
+ }
+ writer.visitEnd();
+
+ byte[] result = writer.toByteArray();
+ if (PRINT_CF) {
+ System.out.print(printCf(result));
+ System.out.flush();
+ }
+ if (RUN_VERIFIER) {
+ // Generally, this will fail with ClassNotFoundException,
+ // so don't assert that verifyCf() returns true.
+ verifyCf(result);
+ }
+ ExceptionUtils.withConsumeResourceHandler(
+ options.reporter, handler -> consumer.accept(ByteDataView.of(result), desc, handler));
+ }
+
+ private String getSourceDebugExtension(DexAnnotationSet annotations) {
+ DexValue debugExtensions =
+ getSystemAnnotationValue(annotations, factory.annotationSourceDebugExtension);
+ if (debugExtensions == null) {
+ return null;
+ }
+ return debugExtensions.asDexValueString().getValue().toString();
+ }
+
+ @SuppressWarnings("BadImport")
+ private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
+ DexValue value = getSystemAnnotationValue(annotations, factory.annotationDefault);
+ if (value == null) {
+ return ImmutableMap.of();
+ }
+ DexEncodedAnnotation annotation = value.asDexValueAnnotation().value;
+ Builder<DexString, DexValue> builder = ImmutableMap.builder();
+ for (DexAnnotationElement element : annotation.elements) {
+ builder.put(element.name, element.value);
+ }
+ return builder.build();
+ }
+
+ private String[] getExceptions(DexAnnotationSet annotations, NamingLens namingLens) {
+ DexValue value = getSystemAnnotationValue(annotations, factory.annotationThrows);
+ if (value == null) {
+ return null;
+ }
+ DexValue[] values = value.asDexValueArray().getValues();
+ String[] res = new String[values.length];
+ for (int i = 0; i < values.length; i++) {
+ res[i] = namingLens.lookupInternalName(values[i].asDexValueType().value);
+ }
+ return res;
+ }
+
+ private Object getStaticValue(DexEncodedField field) {
+ if (!field.accessFlags.isStatic() || !field.hasExplicitStaticValue()) {
+ return null;
+ }
+ return field.getStaticValue().asAsmEncodedObject();
+ }
+
+ private void writeField(ProgramField field, ClassWriter writer) {
+ int access = field.getAccessFlags().getAsCfAccessFlags();
+ if (field.getDefinition().isDeprecated()) {
+ access = AsmUtils.withDeprecated(access);
+ }
+ String name = namingLens.lookupName(field.getReference()).toString();
+ String desc = namingLens.lookupDescriptor(field.getReference().type).toString();
+ String signature =
+ field
+ .getDefinition()
+ .getGenericSignature()
+ .toRenamedString(namingLens, this::isTypeMissing);
+ Object value = getStaticValue(field.getDefinition());
+ FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
+ writeAnnotations(
+ visitor::visitAnnotation,
+ visitor::visitTypeAnnotation,
+ field.getAnnotations().getAnnotations());
+ visitor.visitEnd();
+ }
+
+ private void writeMethod(
+ ProgramMethod method,
+ CfVersion classFileVersion,
+ LensCodeRewriterUtils rewriter,
+ ClassWriter writer,
+ ImmutableMap<DexString, DexValue> defaults) {
+ DexEncodedMethod definition = method.getDefinition();
+ int access = definition.getAccessFlags().getAsCfAccessFlags();
+ if (definition.isDeprecated()) {
+ access = AsmUtils.withDeprecated(access);
+ }
+ String name = namingLens.lookupName(method.getReference()).toString();
+ String desc = definition.descriptor(namingLens);
+ String signature =
+ method
+ .getDefinition()
+ .getGenericSignature()
+ .toRenamedString(namingLens, this::isTypeMissing);
+ String[] exceptions = getExceptions(definition.annotations(), namingLens);
+ MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
+ if (defaults.containsKey(definition.getName())) {
+ AnnotationVisitor defaultVisitor = visitor.visitAnnotationDefault();
+ if (defaultVisitor != null) {
+ writeAnnotationElement(defaultVisitor, null, defaults.get(definition.getName()));
+ defaultVisitor.visitEnd();
+ }
+ }
+ writeMethodParametersAnnotation(visitor, definition.annotations().annotations);
+ writeAnnotations(
+ visitor::visitAnnotation,
+ visitor::visitTypeAnnotation,
+ definition.annotations().annotations);
+ writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
+ if (!definition.shouldNotHaveCode()) {
+ writeCode(method, classFileVersion, namingLens, rewriter, visitor);
+ }
+ visitor.visitEnd();
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private void writeMethodParametersAnnotation(MethodVisitor visitor, DexAnnotation[] annotations) {
+ for (DexAnnotation annotation : annotations) {
+ if (annotation.annotation.type == factory.annotationMethodParameters) {
+ assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
+ assert annotation.annotation.elements.length == 2;
+ assert annotation.annotation.elements[0].name.toString().equals("names");
+ assert annotation.annotation.elements[1].name.toString().equals("accessFlags");
+ DexValueArray names = annotation.annotation.elements[0].value.asDexValueArray();
+ DexValueArray accessFlags = annotation.annotation.elements[1].value.asDexValueArray();
+ assert names != null && accessFlags != null;
+ assert names.getValues().length == accessFlags.getValues().length;
+ for (int i = 0; i < names.getValues().length; i++) {
+ DexValueString name = names.getValues()[i].asDexValueString();
+ DexValueInt access = accessFlags.getValues()[i].asDexValueInt();
+ String nameString = name != null ? name.value.toString() : null;
+ visitor.visitParameter(nameString, access.value);
+ }
+ }
+ }
+ }
+
+ private void writeParameterAnnotations(
+ MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
+ // TODO(113565942): We currently assume that the annotable parameter count
+ // it the same for visible and invisible annotations. That doesn't actually
+ // seem to be the case in the class file format.
+ visitor.visitAnnotableParameterCount(parameterAnnotations.getAnnotableParameterCount(), true);
+ visitor.visitAnnotableParameterCount(parameterAnnotations.getAnnotableParameterCount(), false);
+ for (int i = 0; i < parameterAnnotations.size(); i++) {
+ int iFinal = i;
+ writeAnnotations(
+ (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
+ (typeRef, typePath, desc, visible) -> {
+ throw new Unreachable("Type annotations are not placed on parameters");
+ },
+ parameterAnnotations.get(i).annotations);
+ }
+ }
+
+ private void writeAnnotations(
+ AnnotationConsumer visitor,
+ TypeAnnotationConsumer typeAnnotationVisitor,
+ DexAnnotation[] annotations) {
+ for (DexAnnotation dexAnnotation : annotations) {
+ if (dexAnnotation.visibility == DexAnnotation.VISIBILITY_SYSTEM) {
+ // Annotations with VISIBILITY_SYSTEM are not annotations in CF, but are special
+ // annotations in Dex, i.e. default, enclosing class, enclosing method, member classes,
+ // signature, throws.
+ continue;
+ }
+ String desc = namingLens.lookupDescriptor(dexAnnotation.annotation.type).toString();
+ boolean visible = dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME;
+ DexTypeAnnotation dexTypeAnnotation = dexAnnotation.asTypeAnnotation();
+ AnnotationVisitor v =
+ dexTypeAnnotation == null
+ ? visitor.visit(desc, visible)
+ : typeAnnotationVisitor.visit(
+ dexTypeAnnotation.getTypeRef(), dexTypeAnnotation.getTypePath(), desc, visible);
+ if (v != null) {
+ writeAnnotation(v, dexAnnotation.annotation);
+ v.visitEnd();
+ }
+ }
+ }
+
+ private void writeAnnotation(AnnotationVisitor v, DexEncodedAnnotation annotation) {
+ for (DexAnnotationElement element : annotation.elements) {
+ writeAnnotationElement(v, element.name.toString(), element.value);
+ }
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private void writeAnnotationElement(AnnotationVisitor visitor, String name, DexValue value) {
+ switch (value.getValueKind()) {
+ case ANNOTATION:
+ {
+ DexValueAnnotation valueAnnotation = value.asDexValueAnnotation();
+ AnnotationVisitor innerVisitor =
+ visitor.visitAnnotation(
+ name, namingLens.lookupDescriptor(valueAnnotation.value.type).toString());
+ if (innerVisitor != null) {
+ writeAnnotation(innerVisitor, valueAnnotation.value);
+ innerVisitor.visitEnd();
+ }
+ }
+ break;
+
+ case ARRAY:
+ {
+ DexValue[] values = value.asDexValueArray().getValues();
+ AnnotationVisitor innerVisitor = visitor.visitArray(name);
+ if (innerVisitor != null) {
+ for (DexValue elementValue : values) {
+ writeAnnotationElement(innerVisitor, null, elementValue);
+ }
+ innerVisitor.visitEnd();
+ }
+ }
+ break;
+
+ case ENUM:
+ DexField enumField = value.asDexValueEnum().getValue();
+ // This must not be renamed, as the Java runtime will use Enum.valueOf to find the enum's
+ // referenced in annotations. See b/236691999 for details.
+ assert namingLens.lookupName(enumField) == enumField.name
+ || System.getProperty("com.android.tools.r8.tracereferences.obfuscateAllEnums")
+ != null
+ : "Enum field " + enumField.name + " renamed to " + namingLens.lookupName(enumField);
+ visitor.visitEnum(
+ name,
+ namingLens.lookupDescriptor(enumField.getType()).toString(),
+ enumField.name.toString());
+ break;
+
+ case FIELD:
+ throw new Unreachable("writeAnnotationElement of DexValueField");
+
+ case METHOD:
+ throw new Unreachable("writeAnnotationElement of DexValueMethod");
+
+ case METHOD_HANDLE:
+ throw new Unreachable("writeAnnotationElement of DexValueMethodHandle");
+
+ case METHOD_TYPE:
+ throw new Unreachable("writeAnnotationElement of DexValueMethodType");
+
+ case STRING:
+ visitor.visit(name, value.asDexValueString().getValue().toString());
+ break;
+
+ case TYPE:
+ visitor.visit(
+ name,
+ Type.getType(namingLens.lookupDescriptor(value.asDexValueType().value).toString()));
+ break;
+
+ default:
+ visitor.visit(name, value.getBoxedValue());
+ break;
+ }
+ }
+
+ private void writeCode(
+ ProgramMethod method,
+ CfVersion classFileVersion,
+ NamingLens namingLens,
+ LensCodeRewriterUtils rewriter,
+ MethodVisitor visitor) {
+ Code code = method.getDefinition().getCode();
+ assert code.isCfWritableCode();
+ assert code.estimatedDexCodeSizeUpperBoundInBytes() > 0;
+ if (!code.isCfWritableCode()) {
+ // This should never happen (see assertion above), but desugaring bugs may lead the
+ // CfApplicationWriter to try to write invalid code and we need the better error message.
+ throw new Unreachable(
+ "The CfApplicationWriter cannot write non cf writable code "
+ + code.getClass().getCanonicalName()
+ + " for method "
+ + method.getReference().toSourceString());
+ }
+ code.asCfWritableCode()
+ .writeCf(method, classFileVersion, appView, namingLens, rewriter, visitor);
+ }
+
+ private int compareTypesThroughLens(DexType a, DexType b) {
+ return namingLens.lookupDescriptor(a).compareTo(namingLens.lookupDescriptor(b));
+ }
+
+ private DexString returnTypeThroughLens(DexMethod method) {
+ return namingLens.lookupDescriptor(method.getReturnType());
+ }
+
+ private int compareMethodsThroughLens(ProgramMethod a, ProgramMethod b) {
+ // When writing class files, methods are only compared within the same class.
+ assert a.getHolder().equals(b.getHolder());
+ return Comparator.comparing(this::returnTypeThroughLens)
+ .thenComparing(DexMethod::getName)
+ // .thenComparingInt(m -> m.getProto().getArity()) // Done in arrayComp below.
+ .thenComparing(
+ m -> m.getProto().parameters.values,
+ ComparatorUtils.arrayComparator(this::compareTypesThroughLens))
+ .compare(a.getReference(), b.getReference());
+ }
+
+ private CfVersion getClassFileVersion(DexEncodedMethod method) {
+ if (!method.hasClassFileVersion()) {
+ // In this case bridges have been introduced for the Cf back-end,
+ // which do not have class file version.
+ assert options.getLibraryDesugaringOptions().isDesugaredLibraryCompilation()
+ || options.isDesugaring()
+ : "Expected class file version for " + method.getReference().toSourceString();
+ assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(
+ options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION));
+ // Any desugaring rewrites which cannot meet the default class file version after
+ // desugaring must upgrade the class file version during desugaring.
+ return options.isDesugaring()
+ ? options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION)
+ : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
+ }
+ return method.getClassFileVersion();
+ }
+
+ private CfVersion getClassFileVersion(DexProgramClass clazz) {
+ CfVersion version =
+ clazz.hasClassFileVersion()
+ ? clazz.getInitialClassFileVersion()
+ : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
+ for (DexEncodedMethod method : clazz.directMethods()) {
+ version = Ordered.max(version, getClassFileVersion(method));
+ }
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ version = Ordered.max(version, getClassFileVersion(method));
+ }
+ return version;
+ }
+
+ private DexValue getSystemAnnotationValue(DexAnnotationSet annotations, DexType type) {
+ DexAnnotation annotation = annotations.getFirstMatching(type);
+ if (annotation == null) {
+ return null;
+ }
+ assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
+ DexEncodedAnnotation encodedAnnotation = annotation.annotation;
+ assert encodedAnnotation.elements.length == 1;
+ return encodedAnnotation.elements[0].value;
+ }
+
+ private interface AnnotationConsumer {
+ AnnotationVisitor visit(String desc, boolean visible);
+ }
+
+ private interface TypeAnnotationConsumer {
+ AnnotationVisitor visit(int typeRef, TypePath typePath, String desc, boolean visible);
+ }
+
+ public static String printCf(byte[] result) {
+ ClassReader reader = new ClassReader(result);
+ ClassNode node = new ClassNode(ASM_VERSION);
+ reader.accept(node, ASM_VERSION);
+ StringWriter writer = new StringWriter();
+ for (MethodNode method : node.methods) {
+ writer.append(method.name).append(method.desc).append('\n');
+ TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
+ method.accept(visitor);
+ visitor.p.print(new PrintWriter(writer));
+ writer.append('\n');
+ }
+ return writer.toString();
+ }
+
+ @SuppressWarnings("DefaultCharset")
+ private static void verifyCf(byte[] result) {
+ ClassReader reader = new ClassReader(result);
+ PrintWriter pw = new PrintWriter(System.out);
+ CheckClassAdapter.verify(reader, false, pw);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index dc57f39..70b8e56 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -3,115 +3,41 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jar;
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import static com.android.tools.r8.utils.positions.LineNumberOptimizer.runAndWriteMap;
-import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.SourceFileEnvironment;
-import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.errors.CodeSizeOverflowDiagnostic;
-import com.android.tools.r8.errors.ConstantPoolOverflowDiagnostic;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeAnnotation;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.PermittedSubclassAttribute;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.RecordComponentInfo;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AsmUtils;
-import com.android.tools.r8.utils.ComparatorUtils;
-import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsCfConsumer;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OriginalSourceFiles;
-import com.android.tools.r8.utils.PredicateUtils;
-import com.android.tools.r8.utils.structural.Ordered;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMap.Builder;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
-import java.util.function.Predicate;
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassTooLargeException;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodTooLargeException;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.TypePath;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.util.CheckClassAdapter;
-import org.objectweb.asm.util.Textifier;
-import org.objectweb.asm.util.TraceMethodVisitor;
public class CfApplicationWriter {
- private static final boolean RUN_VERIFIER = false;
- private static final boolean PRINT_CF = false;
-
- // First item inserted into the constant pool is the marker string which generates an UTF8 to
- // pool index #1 and a String entry to #2, referencing #1.
- public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
-
private final DexApplication application;
private final AppView<?> appView;
private final InternalOptions options;
private final Optional<Marker> marker;
- private final Predicate<DexType> isTypeMissing;
-
- private static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
public CfApplicationWriter(AppView<?> appView, Marker marker) {
this.application = appView.appInfo().app();
this.appView = appView;
this.options = appView.options();
this.marker = Optional.ofNullable(marker);
- this.isTypeMissing =
- PredicateUtils.isNull(appView.appInfo()::definitionForWithoutExistenceAssert);
- }
-
- private NamingLens getNamingLens() {
- return appView.getNamingLens();
}
public void write(ClassFileConsumer consumer, ExecutorService executorService) {
@@ -179,536 +105,19 @@
}
}
for (DexProgramClass clazz : classes) {
- writeClassCatchingErrors(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
+ new CfApplicationClassWriter(appView, clazz)
+ .writeClassCatchingErrors(consumer, rewriter, markerString, sourceFileEnvironment);
}
if (!globalSyntheticClasses.isEmpty()) {
InternalGlobalSyntheticsCfConsumer globalsConsumer =
new InternalGlobalSyntheticsCfConsumer(options.getGlobalSyntheticsConsumer(), appView);
for (DexProgramClass clazz : globalSyntheticClasses) {
- writeClassCatchingErrors(
- clazz, globalsConsumer, rewriter, markerString, sourceFileEnvironment);
+ new CfApplicationClassWriter(appView, clazz)
+ .writeClassCatchingErrors(
+ globalsConsumer, rewriter, markerString, sourceFileEnvironment);
}
globalsConsumer.finished(appView);
}
ApplicationWriter.supplyAdditionalConsumers(appView, executorService, Collections.emptyList());
}
-
- private void writeClassCatchingErrors(
- DexProgramClass clazz,
- ClassFileConsumer consumer,
- LensCodeRewriterUtils rewriter,
- Optional<String> markerString,
- SourceFileEnvironment sourceFileEnvironment) {
- assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
- try {
- writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
- } catch (ClassTooLargeException e) {
- throw appView
- .options()
- .reporter
- .fatalError(
- new ConstantPoolOverflowDiagnostic(
- clazz.getOrigin(),
- Reference.classFromBinaryName(e.getClassName()),
- e.getConstantPoolCount()));
- } catch (MethodTooLargeException e) {
- throw appView
- .options()
- .reporter
- .fatalError(
- new CodeSizeOverflowDiagnostic(
- clazz.getOrigin(),
- Reference.methodFromDescriptor(
- Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
- e.getMethodName(),
- e.getDescriptor()),
- e.getCodeSize()));
- }
- }
-
- private void writeClass(
- DexProgramClass clazz,
- ClassFileConsumer consumer,
- LensCodeRewriterUtils rewriter,
- Optional<String> markerString,
- SourceFileEnvironment sourceFileEnvironment) {
- ClassWriter writer = new ClassWriter(0);
- if (markerString.isPresent()) {
- int markerStringPoolIndex = writer.newConst(markerString.get());
- assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
- }
- String sourceFile;
- if (options.sourceFileProvider == null) {
- sourceFile = clazz.sourceFile != null ? clazz.sourceFile.toString() : null;
- } else {
- sourceFile = options.sourceFileProvider.get(sourceFileEnvironment);
- }
- String sourceDebug = getSourceDebugExtension(clazz.annotations());
- writer.visitSource(sourceFile, sourceDebug);
- CfVersion version = getClassFileVersion(clazz);
- if (version.isGreaterThanOrEqualTo(CfVersion.V1_8)) {
- // JDK8 and after ignore ACC_SUPER so unset it.
- clazz.accessFlags.unsetSuper();
- } else {
- // In all other cases set the super bit as D8/R8 do not support targeting pre 1.0.2 JDKs.
- if (!clazz.accessFlags.isInterface()) {
- clazz.accessFlags.setSuper();
- }
- }
- boolean allowInvalidCfAccessFlags = false;
- if (clazz
- .getType()
- .getDescriptor()
- .endsWith(appView.dexItemFactory().createString("/package-info;"))) {
- allowInvalidCfAccessFlags = true;
- }
- int access =
- allowInvalidCfAccessFlags || options.testing.allowInvalidCfAccessFlags
- ? clazz.accessFlags.materialize()
- : clazz.accessFlags.getAsCfAccessFlags();
- if (clazz.isDeprecated()) {
- access = AsmUtils.withDeprecated(access);
- }
- String desc = getNamingLens().lookupDescriptor(clazz.type).toString();
- String name = getNamingLens().lookupInternalName(clazz.type);
- String signature = clazz.getClassSignature().toRenamedString(getNamingLens(), isTypeMissing);
- String superName =
- clazz.hasSuperType() ? getNamingLens().lookupInternalName(clazz.superType) : null;
- String[] interfaces = new String[clazz.interfaces.values.length];
- for (int i = 0; i < clazz.interfaces.values.length; i++) {
- interfaces[i] = getNamingLens().lookupInternalName(clazz.interfaces.values[i]);
- }
- assert SyntheticNaming.verifyNotInternalSynthetic(name);
- writer.visit(version.raw(), access, name, signature, superName, interfaces);
- appView.getSyntheticItems().writeAttributeIfIntermediateSyntheticClass(writer, clazz, appView);
- writeAnnotations(
- writer::visitAnnotation, writer::visitTypeAnnotation, clazz.annotations().annotations);
- ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
-
- if (clazz.getEnclosingMethodAttribute() != null) {
- clazz.getEnclosingMethodAttribute().write(writer, getNamingLens());
- }
-
- if (clazz.getNestHostClassAttribute() != null) {
- clazz.getNestHostClassAttribute().write(writer, getNamingLens());
- }
-
- for (NestMemberClassAttribute entry : clazz.getNestMembersClassAttributes()) {
- entry.write(writer, getNamingLens());
- assert clazz.getNestHostClassAttribute() == null
- : "A nest host cannot also be a nest member.";
- }
-
- for (PermittedSubclassAttribute entry : clazz.getPermittedSubclassAttributes()) {
- entry.write(writer, getNamingLens());
- }
-
- if (clazz.isRecord()) {
- // TODO(b/274888318): Strip record components if not kept.
- for (RecordComponentInfo info : clazz.getRecordComponents()) {
- info.write(writer, getNamingLens(), isTypeMissing, this::writeAnnotation);
- }
- }
-
- for (InnerClassAttribute entry : clazz.getInnerClasses()) {
- entry.write(writer, getNamingLens(), options);
- }
-
- for (DexEncodedField field : clazz.staticFields()) {
- writeField(field, writer);
- }
- for (DexEncodedField field : clazz.instanceFields()) {
- writeField(field, writer);
- }
- if (options.desugarSpecificOptions().sortMethodsOnCfOutput) {
- List<ProgramMethod> programMethodSorted = new ArrayList<>();
- clazz.forEachProgramMethod(programMethodSorted::add);
- programMethodSorted.sort(this::compareMethodsThroughLens);
- programMethodSorted.forEach(
- method -> writeMethod(method, version, rewriter, writer, defaults));
- } else {
- clazz.forEachProgramMethod(
- method -> writeMethod(method, version, rewriter, writer, defaults));
- }
- writer.visitEnd();
-
- byte[] result = writer.toByteArray();
- if (PRINT_CF) {
- System.out.print(printCf(result));
- System.out.flush();
- }
- if (RUN_VERIFIER) {
- // Generally, this will fail with ClassNotFoundException,
- // so don't assert that verifyCf() returns true.
- verifyCf(result);
- }
- ExceptionUtils.withConsumeResourceHandler(
- options.reporter, handler -> consumer.accept(ByteDataView.of(result), desc, handler));
- }
-
- private int compareTypesThroughLens(DexType a, DexType b) {
- return getNamingLens().lookupDescriptor(a).compareTo(getNamingLens().lookupDescriptor(b));
- }
-
- private DexString returnTypeThroughLens(DexMethod method) {
- return getNamingLens().lookupDescriptor(method.getReturnType());
- }
-
- private int compareMethodsThroughLens(ProgramMethod a, ProgramMethod b) {
- // When writing class files, methods are only compared within the same class.
- assert a.getHolder().equals(b.getHolder());
- return Comparator.comparing(this::returnTypeThroughLens)
- .thenComparing(DexMethod::getName)
- // .thenComparingInt(m -> m.getProto().getArity()) // Done in arrayComp below.
- .thenComparing(
- m -> m.getProto().parameters.values,
- ComparatorUtils.arrayComparator(this::compareTypesThroughLens))
- .compare(a.getReference(), b.getReference());
- }
-
- private CfVersion getClassFileVersion(DexEncodedMethod method) {
- if (!method.hasClassFileVersion()) {
- // In this case bridges have been introduced for the Cf back-end,
- // which do not have class file version.
- assert options.getLibraryDesugaringOptions().isDesugaredLibraryCompilation()
- || options.isDesugaring()
- : "Expected class file version for " + method.getReference().toSourceString();
- assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(
- options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION));
- // Any desugaring rewrites which cannot meet the default class file version after
- // desugaring must upgrade the class file version during desugaring.
- return options.isDesugaring()
- ? options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION)
- : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
- }
- return method.getClassFileVersion();
- }
-
- private CfVersion getClassFileVersion(DexProgramClass clazz) {
- CfVersion version =
- clazz.hasClassFileVersion()
- ? clazz.getInitialClassFileVersion()
- : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
- for (DexEncodedMethod method : clazz.directMethods()) {
- version = Ordered.max(version, getClassFileVersion(method));
- }
- for (DexEncodedMethod method : clazz.virtualMethods()) {
- version = Ordered.max(version, getClassFileVersion(method));
- }
- return version;
- }
-
- private DexValue getSystemAnnotationValue(DexAnnotationSet annotations, DexType type) {
- DexAnnotation annotation = annotations.getFirstMatching(type);
- if (annotation == null) {
- return null;
- }
- assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
- DexEncodedAnnotation encodedAnnotation = annotation.annotation;
- assert encodedAnnotation.elements.length == 1;
- return encodedAnnotation.elements[0].value;
- }
-
- private String getSourceDebugExtension(DexAnnotationSet annotations) {
- DexValue debugExtensions =
- getSystemAnnotationValue(
- annotations, application.dexItemFactory.annotationSourceDebugExtension);
- if (debugExtensions == null) {
- return null;
- }
- return debugExtensions.asDexValueString().getValue().toString();
- }
-
- @SuppressWarnings("BadImport")
- private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
- DexValue value =
- getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
- if (value == null) {
- return ImmutableMap.of();
- }
- DexEncodedAnnotation annotation = value.asDexValueAnnotation().value;
- Builder<DexString, DexValue> builder = ImmutableMap.builder();
- for (DexAnnotationElement element : annotation.elements) {
- builder.put(element.name, element.value);
- }
- return builder.build();
- }
-
- private String[] getExceptions(DexAnnotationSet annotations) {
- DexValue value =
- getSystemAnnotationValue(annotations, application.dexItemFactory.annotationThrows);
- if (value == null) {
- return null;
- }
- DexValue[] values = value.asDexValueArray().getValues();
- String[] res = new String[values.length];
- for (int i = 0; i < values.length; i++) {
- res[i] = getNamingLens().lookupInternalName(values[i].asDexValueType().value);
- }
- return res;
- }
-
- private Object getStaticValue(DexEncodedField field) {
- if (!field.accessFlags.isStatic() || !field.hasExplicitStaticValue()) {
- return null;
- }
- return field.getStaticValue().asAsmEncodedObject();
- }
-
- private void writeField(DexEncodedField field, ClassWriter writer) {
- int access = field.accessFlags.getAsCfAccessFlags();
- if (field.isDeprecated()) {
- access = AsmUtils.withDeprecated(access);
- }
- String name = getNamingLens().lookupName(field.getReference()).toString();
- String desc = getNamingLens().lookupDescriptor(field.getReference().type).toString();
- String signature = field.getGenericSignature().toRenamedString(getNamingLens(), isTypeMissing);
- Object value = getStaticValue(field);
- FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
- writeAnnotations(
- visitor::visitAnnotation, visitor::visitTypeAnnotation, field.annotations().annotations);
- visitor.visitEnd();
- }
-
- private void writeMethod(
- ProgramMethod method,
- CfVersion classFileVersion,
- LensCodeRewriterUtils rewriter,
- ClassWriter writer,
- ImmutableMap<DexString, DexValue> defaults) {
- // For "pass through" classes which has already been library desugared use the identity lens.
- NamingLens namingLens =
- appView.isAlreadyLibraryDesugared(method.getHolder())
- ? NamingLens.getIdentityLens()
- : getNamingLens();
- DexEncodedMethod definition = method.getDefinition();
- int access = definition.getAccessFlags().getAsCfAccessFlags();
- if (definition.isDeprecated()) {
- access = AsmUtils.withDeprecated(access);
- }
- String name = namingLens.lookupName(method.getReference()).toString();
- String desc = definition.descriptor(namingLens);
- String signature =
- method.getDefinition().getGenericSignature().toRenamedString(namingLens, isTypeMissing);
- String[] exceptions = getExceptions(definition.annotations());
- MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
- if (defaults.containsKey(definition.getName())) {
- AnnotationVisitor defaultVisitor = visitor.visitAnnotationDefault();
- if (defaultVisitor != null) {
- writeAnnotationElement(defaultVisitor, null, defaults.get(definition.getName()));
- defaultVisitor.visitEnd();
- }
- }
- writeMethodParametersAnnotation(visitor, definition.annotations().annotations);
- writeAnnotations(
- visitor::visitAnnotation,
- visitor::visitTypeAnnotation,
- definition.annotations().annotations);
- writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
- if (!definition.shouldNotHaveCode()) {
- writeCode(method, classFileVersion, namingLens, rewriter, visitor);
- }
- visitor.visitEnd();
- }
-
- @SuppressWarnings("ReferenceEquality")
- private void writeMethodParametersAnnotation(MethodVisitor visitor, DexAnnotation[] annotations) {
- for (DexAnnotation annotation : annotations) {
- if (annotation.annotation.type == appView.dexItemFactory().annotationMethodParameters) {
- assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
- assert annotation.annotation.elements.length == 2;
- assert annotation.annotation.elements[0].name.toString().equals("names");
- assert annotation.annotation.elements[1].name.toString().equals("accessFlags");
- DexValueArray names = annotation.annotation.elements[0].value.asDexValueArray();
- DexValueArray accessFlags = annotation.annotation.elements[1].value.asDexValueArray();
- assert names != null && accessFlags != null;
- assert names.getValues().length == accessFlags.getValues().length;
- for (int i = 0; i < names.getValues().length; i++) {
- DexValueString name = names.getValues()[i].asDexValueString();
- DexValueInt access = accessFlags.getValues()[i].asDexValueInt();
- String nameString = name != null ? name.value.toString() : null;
- visitor.visitParameter(nameString, access.value);
- }
- }
- }
- }
-
- private void writeParameterAnnotations(
- MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
- // TODO(113565942): We currently assume that the annotable parameter count
- // it the same for visible and invisible annotations. That doesn't actually
- // seem to be the case in the class file format.
- visitor.visitAnnotableParameterCount(
- parameterAnnotations.getAnnotableParameterCount(), true);
- visitor.visitAnnotableParameterCount(
- parameterAnnotations.getAnnotableParameterCount(), false);
- for (int i = 0; i < parameterAnnotations.size(); i++) {
- int iFinal = i;
- writeAnnotations(
- (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
- (typeRef, typePath, desc, visible) -> {
- throw new Unreachable("Type annotations are not placed on parameters");
- },
- parameterAnnotations.get(i).annotations);
- }
- }
-
- private interface AnnotationConsumer {
- AnnotationVisitor visit(String desc, boolean visible);
- }
-
- private interface TypeAnnotationConsumer {
- AnnotationVisitor visit(int typeRef, TypePath typePath, String desc, boolean visible);
- }
-
- private void writeAnnotations(
- AnnotationConsumer visitor,
- TypeAnnotationConsumer typeAnnotationVisitor,
- DexAnnotation[] annotations) {
- for (DexAnnotation dexAnnotation : annotations) {
- if (dexAnnotation.visibility == DexAnnotation.VISIBILITY_SYSTEM) {
- // Annotations with VISIBILITY_SYSTEM are not annotations in CF, but are special
- // annotations in Dex, i.e. default, enclosing class, enclosing method, member classes,
- // signature, throws.
- continue;
- }
- String desc = getNamingLens().lookupDescriptor(dexAnnotation.annotation.type).toString();
- boolean visible = dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME;
- DexTypeAnnotation dexTypeAnnotation = dexAnnotation.asTypeAnnotation();
- AnnotationVisitor v =
- dexTypeAnnotation == null
- ? visitor.visit(desc, visible)
- : typeAnnotationVisitor.visit(
- dexTypeAnnotation.getTypeRef(), dexTypeAnnotation.getTypePath(), desc, visible);
- if (v != null) {
- writeAnnotation(v, dexAnnotation.annotation);
- v.visitEnd();
- }
- }
- }
-
- private void writeAnnotation(AnnotationVisitor v, DexEncodedAnnotation annotation) {
- for (DexAnnotationElement element : annotation.elements) {
- writeAnnotationElement(v, element.name.toString(), element.value);
- }
- }
-
- @SuppressWarnings("ReferenceEquality")
- private void writeAnnotationElement(AnnotationVisitor visitor, String name, DexValue value) {
- switch (value.getValueKind()) {
- case ANNOTATION:
- {
- DexValueAnnotation valueAnnotation = value.asDexValueAnnotation();
- AnnotationVisitor innerVisitor =
- visitor.visitAnnotation(
- name, getNamingLens().lookupDescriptor(valueAnnotation.value.type).toString());
- if (innerVisitor != null) {
- writeAnnotation(innerVisitor, valueAnnotation.value);
- innerVisitor.visitEnd();
- }
- }
- break;
-
- case ARRAY:
- {
- DexValue[] values = value.asDexValueArray().getValues();
- AnnotationVisitor innerVisitor = visitor.visitArray(name);
- if (innerVisitor != null) {
- for (DexValue elementValue : values) {
- writeAnnotationElement(innerVisitor, null, elementValue);
- }
- innerVisitor.visitEnd();
- }
- }
- break;
-
- case ENUM:
- DexField enumField = value.asDexValueEnum().getValue();
- // This must not be renamed, as the Java runtime will use Enum.valueOf to find the enum's
- // referenced in annotations. See b/236691999 for details.
- assert getNamingLens().lookupName(enumField) == enumField.name
- || System.getProperty("com.android.tools.r8.tracereferences.obfuscateAllEnums")
- != null
- : "Enum field "
- + enumField.name
- + " renamed to "
- + getNamingLens().lookupName(enumField);
- visitor.visitEnum(
- name,
- getNamingLens().lookupDescriptor(enumField.getType()).toString(),
- enumField.name.toString());
- break;
-
- case FIELD:
- throw new Unreachable("writeAnnotationElement of DexValueField");
-
- case METHOD:
- throw new Unreachable("writeAnnotationElement of DexValueMethod");
-
- case METHOD_HANDLE:
- throw new Unreachable("writeAnnotationElement of DexValueMethodHandle");
-
- case METHOD_TYPE:
- throw new Unreachable("writeAnnotationElement of DexValueMethodType");
-
- case STRING:
- visitor.visit(name, value.asDexValueString().getValue().toString());
- break;
-
- case TYPE:
- visitor.visit(
- name,
- Type.getType(
- getNamingLens().lookupDescriptor(value.asDexValueType().value).toString()));
- break;
-
- default:
- visitor.visit(name, value.getBoxedValue());
- break;
- }
- }
-
- private void writeCode(
- ProgramMethod method,
- CfVersion classFileVersion,
- NamingLens namingLens,
- LensCodeRewriterUtils rewriter,
- MethodVisitor visitor) {
- Code code = method.getDefinition().getCode();
- assert code.isCfWritableCode();
- assert code.estimatedDexCodeSizeUpperBoundInBytes() > 0;
- if (!code.isCfWritableCode()) {
- // This should never happen (see assertion above), but desugaring bugs may lead the
- // CfApplicationWriter to try to write invalid code and we need the better error message.
- throw new Unreachable(
- "The CfApplicationWriter cannot write non cf writable code "
- + code.getClass().getCanonicalName()
- + " for method "
- + method.getReference().toSourceString());
- }
- code.asCfWritableCode()
- .writeCf(method, classFileVersion, appView, namingLens, rewriter, visitor);
- }
-
- public static String printCf(byte[] result) {
- ClassReader reader = new ClassReader(result);
- ClassNode node = new ClassNode(ASM_VERSION);
- reader.accept(node, ASM_VERSION);
- StringWriter writer = new StringWriter();
- for (MethodNode method : node.methods) {
- writer.append(method.name).append(method.desc).append('\n');
- TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
- method.accept(visitor);
- visitor.p.print(new PrintWriter(writer));
- writer.append('\n');
- }
- return writer.toString();
- }
-
- @SuppressWarnings("DefaultCharset")
- private static void verifyCf(byte[] result) {
- ClassReader reader = new ClassReader(result);
- PrintWriter pw = new PrintWriter(System.out);
- CheckClassAdapter.verify(reader, false, pw);
- }
}
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index d6028ef..64fb5dd 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -148,6 +148,11 @@
}
@Override
+ public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+ return this;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
renaming.forEach(
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index 3e551cf..bc8b12f 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -184,6 +184,8 @@
return true;
}
+ public abstract NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens();
+
public abstract static class NonIdentityNamingLens extends NamingLens {
private final DexItemFactory dexItemFactory;
@@ -251,5 +253,10 @@
public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
return true;
}
+
+ @Override
+ public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+ return this;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index d2f2e8a..78cabaa 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -559,7 +559,6 @@
private final Set<DexReference> unmappedReferences;
private final Map<DexString, DexType> classRenamingsMappingToDifferentName;
- @SuppressWarnings("ReferenceEquality")
ProguardMapMinifiedRenaming(
AppView<? extends AppInfoWithClassHierarchy> appView,
ClassRenaming classRenaming,
@@ -571,7 +570,7 @@
classRenamingsMappingToDifferentName = new HashMap<>();
classRenaming.classRenaming.forEach(
(type, dexString) -> {
- if (type.descriptor != dexString) {
+ if (type.getDescriptor().isNotIdenticalTo(dexString)) {
classRenamingsMappingToDifferentName.put(dexString, type);
}
});
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
index 517377d..f946bd6 100644
--- a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -18,7 +18,6 @@
// Naming lens for rewriting java.lang.Record to the internal RecordTag type.
public class RecordRewritingNamingLens extends NonIdentityNamingLens {
- private final DexItemFactory factory;
private final NamingLens namingLens;
public static void commitRecordRewritingNamingLens(AppView<?> appView) {
@@ -36,9 +35,12 @@
}
public RecordRewritingNamingLens(AppView<?> appView) {
- super(appView.dexItemFactory());
- this.factory = appView.dexItemFactory();
- this.namingLens = appView.getNamingLens();
+ this(appView.dexItemFactory(), appView.getNamingLens());
+ }
+
+ public RecordRewritingNamingLens(DexItemFactory factory, NamingLens namingLens) {
+ super(factory);
+ this.namingLens = namingLens;
}
private boolean isRenamed(DexType type) {
@@ -47,8 +49,8 @@
@SuppressWarnings("ReferenceEquality")
private DexString getRenaming(DexType type) {
- if (type == factory.recordType) {
- return factory.recordTagType.descriptor;
+ if (type == dexItemFactory().recordType) {
+ return dexItemFactory().recordTagType.descriptor;
}
return null;
}
@@ -96,4 +98,12 @@
public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
return namingLens.verifyRenamingConsistentWithResolution(item);
}
+
+ @Override
+ public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+ NamingLens newParent = namingLens.withoutDesugaredLibraryPrefixRewritingNamingLens();
+ return newParent != namingLens
+ ? new RecordRewritingNamingLens(dexItemFactory(), newParent)
+ : this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
index b72bb6b..13c0087 100644
--- a/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
@@ -20,7 +20,6 @@
// to com.android.tools.r8.DesugarMethodHandlesLookup.
public class VarHandleDesugaringRewritingNamingLens extends NonIdentityNamingLens {
- private final DexItemFactory factory;
private final NamingLens namingLens;
private final Map<DexType, DexString> mapping;
@@ -93,9 +92,13 @@
private VarHandleDesugaringRewritingNamingLens(
AppView<?> appView, Map<DexType, DexString> mapping) {
- super(appView.dexItemFactory());
- this.factory = appView.dexItemFactory();
- this.namingLens = appView.getNamingLens();
+ this(appView.dexItemFactory(), appView.getNamingLens(), mapping);
+ }
+
+ private VarHandleDesugaringRewritingNamingLens(
+ DexItemFactory factory, NamingLens namingLens, Map<DexType, DexString> mapping) {
+ super(factory);
+ this.namingLens = namingLens;
this.mapping = mapping;
}
@@ -105,8 +108,8 @@
@SuppressWarnings("ReferenceEquality")
private DexString getRenaming(DexType type) {
- assert type != factory.desugarMethodHandlesLookupType;
- assert type != factory.desugarVarHandleType;
+ assert type != dexItemFactory().desugarMethodHandlesLookupType;
+ assert type != dexItemFactory().desugarVarHandleType;
return mapping.get(type);
}
@@ -153,4 +156,12 @@
public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
return namingLens.verifyRenamingConsistentWithResolution(item);
}
+
+ @Override
+ public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+ NamingLens newParent = namingLens.withoutDesugaredLibraryPrefixRewritingNamingLens();
+ return newParent != namingLens
+ ? new VarHandleDesugaringRewritingNamingLens(dexItemFactory(), newParent, mapping)
+ : this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
index 40726bc..c3896f9 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
@@ -4,28 +4,27 @@
package com.android.tools.r8.partial;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.partial.predicate.AllClassesMatcher;
+import com.android.tools.r8.partial.predicate.ClassNameMatcher;
+import com.android.tools.r8.partial.predicate.ClassPrefixMatcher;
+import com.android.tools.r8.partial.predicate.PackageAndSubpackagePrefixMatcher;
+import com.android.tools.r8.partial.predicate.PackagePrefixMatcher;
+import com.android.tools.r8.partial.predicate.R8PartialPredicate;
+import com.android.tools.r8.partial.predicate.R8PartialPredicateCollection;
+import com.android.tools.r8.partial.predicate.UnnamedPackageMatcher;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.List;
import java.util.function.Consumer;
-import java.util.function.Predicate;
public class R8PartialCompilationConfiguration {
private final boolean enabled;
- private Path tempDir = null;
- private final List<Predicate<DexString>> includePredicates;
- private final List<Predicate<DexString>> excludePredicates;
+ private final R8PartialPredicateCollection includePredicates;
+ private final R8PartialPredicateCollection excludePredicates;
public Consumer<InternalOptions> d8DexOptionsConsumer = ConsumerUtils.emptyConsumer();
public Consumer<InternalOptions> r8OptionsConsumer = ConsumerUtils.emptyConsumer();
@@ -35,8 +34,8 @@
private R8PartialCompilationConfiguration(
boolean enabled,
- List<Predicate<DexString>> includePredicates,
- List<Predicate<DexString>> excludePredicates) {
+ R8PartialPredicateCollection includePredicates,
+ R8PartialPredicateCollection excludePredicates) {
assert !enabled || !includePredicates.isEmpty();
assert !enabled || excludePredicates != null;
this.enabled = enabled;
@@ -44,19 +43,16 @@
this.excludePredicates = excludePredicates;
}
+ public R8PartialPredicateCollection getIncludePredicates() {
+ return includePredicates;
+ }
+
+ public R8PartialPredicateCollection getExcludePredicates() {
+ return excludePredicates;
+ }
+
public boolean test(DexProgramClass clazz) {
- DexString name = clazz.getType().getDescriptor();
- for (Predicate<DexString> isR8ClassPredicate : includePredicates) {
- if (isR8ClassPredicate.test(name)) {
- for (Predicate<DexString> isD8ClassPredicate : excludePredicates) {
- if (isD8ClassPredicate.test(name)) {
- return false;
- }
- }
- return true;
- }
- }
- return false;
+ return includePredicates.test(clazz) && !excludePredicates.test(clazz);
}
public static R8PartialCompilationConfiguration disabledConfiguration() {
@@ -87,20 +83,11 @@
return enabled;
}
- public synchronized Path getTempDir() throws IOException {
- if (tempDir == null) {
- setTempDir(Files.createTempDirectory("r8PartialCompilation"));
- }
- return tempDir;
- }
-
- public void setTempDir(Path tempDir) {
- this.tempDir = tempDir;
- }
-
public static class Builder {
- private final List<Predicate<DexString>> includePredicates = new ArrayList<>();
- private final List<Predicate<DexString>> excludePredicates = new ArrayList<>();
+ private final R8PartialPredicateCollection includePredicates =
+ new R8PartialPredicateCollection();
+ private final R8PartialPredicateCollection excludePredicates =
+ new R8PartialPredicateCollection();
private Builder() {}
@@ -110,70 +97,26 @@
}
public Builder includeAll() {
- includePredicates.add(Predicates.alwaysTrue());
+ includePredicates.add(new AllClassesMatcher());
return this;
}
public Builder addJavaTypeIncludePattern(String pattern) {
includePredicates.add(
- createMatcher("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
+ createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
return this;
}
public Builder addJavaTypeExcludePattern(String pattern) {
excludePredicates.add(
- createMatcher("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
+ createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
return this;
}
- public Builder addDescriptorIncludePattern(String pattern) {
- includePredicates.add(createMatcher(pattern));
- return this;
- }
-
- public Builder addDescriptorExcludePattern(String pattern) {
- excludePredicates.add(createMatcher(pattern));
- return this;
- }
-
- private Predicate<DexString> createMatcher(String descriptorPrefix) {
- assert descriptorPrefix.startsWith("L");
- assert descriptorPrefix.indexOf('.') == -1;
-
- if (descriptorPrefix.equals("L**")) {
- return new AllClassesMatcher();
- } else if (descriptorPrefix.equals("L*")) {
- return new UnnamedPackageMatcher();
- } else if (descriptorPrefix.endsWith("/**")) {
- return new PackageAndSubpackagePrefixMatcher(
- descriptorPrefix.substring(0, descriptorPrefix.length() - 2));
- } else if (descriptorPrefix.endsWith("/*")) {
- return new PackagePrefixMatcher(
- descriptorPrefix.substring(0, descriptorPrefix.length() - 1));
- }
- if (descriptorPrefix.endsWith("*")) {
- return new ClassPrefixMatcher(descriptorPrefix.substring(0, descriptorPrefix.length() - 1));
- } else {
- return new ClassNameMatcher(descriptorPrefix + ';');
- }
- }
-
- public Builder includeClasses(Class<?>... classes) {
- return includeClasses(Arrays.asList(classes));
- }
-
public Builder includeClasses(Collection<Class<?>> classes) {
- classes.forEach(
- clazz ->
- includePredicates.add(
- descriptor ->
- descriptor.toString().equals(DescriptorUtils.javaClassToDescriptor(clazz))));
- return this;
- }
-
- public Builder includeJavaType(Predicate<String> include) {
- includePredicates.add(
- descriptor -> include.test(DescriptorUtils.descriptorToJavaType(descriptor.toString())));
+ for (Class<?> clazz : classes) {
+ includePredicates.add(new ClassNameMatcher(DescriptorUtils.javaClassToDescriptor(clazz)));
+ }
return this;
}
@@ -182,100 +125,30 @@
}
public Builder excludeClasses(Collection<Class<?>> classes) {
- classes.forEach(
- clazz ->
- excludePredicates.add(
- descriptor ->
- descriptor.toString().equals(DescriptorUtils.javaClassToDescriptor(clazz))));
+ for (Class<?> clazz : classes) {
+ excludePredicates.add(new ClassNameMatcher(DescriptorUtils.javaClassToDescriptor(clazz)));
+ }
return this;
}
- public Builder excludeJavaType(Predicate<String> exclude) {
- excludePredicates.add(
- descriptor -> exclude.test(DescriptorUtils.descriptorToJavaType(descriptor.toString())));
- return this;
- }
- }
-
- private static class AllClassesMatcher implements Predicate<DexString> {
-
- AllClassesMatcher() {}
-
- @Override
- public boolean test(DexString descriptor) {
- return true;
- }
- }
-
- private static class UnnamedPackageMatcher implements Predicate<DexString> {
-
- UnnamedPackageMatcher() {}
-
- @Override
- public boolean test(DexString descriptor) {
- return descriptor.indexOf('/') == -1;
- }
- }
-
- private static class PackageAndSubpackagePrefixMatcher implements Predicate<DexString> {
-
- private final byte[] descriptorPrefix;
-
- PackageAndSubpackagePrefixMatcher(String descriptorPrefix) {
- this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
- }
-
- @Override
- public boolean test(DexString descriptor) {
- return descriptor.startsWith(descriptorPrefix);
- }
- }
-
- private static class PackagePrefixMatcher implements Predicate<DexString> {
-
- private final byte[] descriptorPrefix;
- private final int descriptorPrefixLength;
-
- PackagePrefixMatcher(String descriptorPrefix) {
- this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
- this.descriptorPrefixLength = descriptorPrefix.length();
- }
-
- @Override
- public boolean test(DexString descriptor) {
- return descriptor.startsWith(descriptorPrefix)
- && descriptor.lastIndexOf('/') == descriptorPrefixLength - 1;
- }
- }
-
- private static class ClassPrefixMatcher implements Predicate<DexString> {
-
- private final byte[] descriptorPrefix;
- private final int descriptorPrefixLength;
-
- ClassPrefixMatcher(String descriptorPrefix) {
- this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
- this.descriptorPrefixLength = descriptorPrefix.length();
- }
-
- @Override
- public boolean test(DexString descriptor) {
- return descriptor.startsWith(descriptorPrefix)
- && descriptor.lastIndexOf('/') < descriptorPrefixLength - 1;
- }
- }
-
- private static class ClassNameMatcher implements Predicate<DexString> {
-
- private final String descriptor;
-
- ClassNameMatcher(String descriptor) {
- this.descriptor = descriptor;
- }
-
- @Override
- public boolean test(DexString descriptor) {
- return descriptor.toString().equals(this.descriptor);
+ private R8PartialPredicate createPredicate(String descriptorPrefix) {
+ assert descriptorPrefix.startsWith("L");
+ assert descriptorPrefix.indexOf('.') == -1;
+ if (descriptorPrefix.equals(AllClassesMatcher.PATTERN)) {
+ return new AllClassesMatcher();
+ } else if (descriptorPrefix.equals(UnnamedPackageMatcher.PATTERN)) {
+ return new UnnamedPackageMatcher();
+ } else if (descriptorPrefix.endsWith("/**")) {
+ return new PackageAndSubpackagePrefixMatcher(
+ descriptorPrefix.substring(0, descriptorPrefix.length() - 2));
+ } else if (descriptorPrefix.endsWith("/*")) {
+ return new PackagePrefixMatcher(
+ descriptorPrefix.substring(0, descriptorPrefix.length() - 1));
+ } else if (descriptorPrefix.endsWith("*")) {
+ return new ClassPrefixMatcher(descriptorPrefix.substring(0, descriptorPrefix.length() - 1));
+ } else {
+ return new ClassNameMatcher(descriptorPrefix + ';');
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java b/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
index 54ee91b..c4994e7 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
@@ -5,21 +5,33 @@
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.profile.art.ArtProfileCollection;
+import com.android.tools.r8.profile.startup.profile.StartupProfile;
import java.util.Collection;
public class R8PartialD8Result {
+ private final ArtProfileCollection artProfiles;
private final ClassToFeatureSplitMap classToFeatureSplitMap;
private final Collection<DexProgramClass> dexedClasses;
private final Collection<DexProgramClass> desugaredClasses;
+ private final StartupProfile startupProfile;
public R8PartialD8Result(
+ ArtProfileCollection artProfiles,
ClassToFeatureSplitMap classToFeatureSplitMap,
Collection<DexProgramClass> dexedClasses,
- Collection<DexProgramClass> desugaredClasses) {
+ Collection<DexProgramClass> desugaredClasses,
+ StartupProfile startupProfile) {
+ this.artProfiles = artProfiles;
this.classToFeatureSplitMap = classToFeatureSplitMap;
this.dexedClasses = dexedClasses;
this.desugaredClasses = desugaredClasses;
+ this.startupProfile = startupProfile;
+ }
+
+ public ArtProfileCollection getArtProfiles() {
+ return artProfiles;
}
public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
@@ -33,4 +45,8 @@
public Collection<DexProgramClass> getDesugaredClasses() {
return desugaredClasses;
}
+
+ public StartupProfile getStartupProfile() {
+ return startupProfile;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialInputToDumpFlags.java b/src/main/java/com/android/tools/r8/partial/R8PartialInputToDumpFlags.java
deleted file mode 100644
index ddfef8f..0000000
--- a/src/main/java/com/android/tools/r8/partial/R8PartialInputToDumpFlags.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2025, 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.partial;
-
-import com.android.tools.r8.dump.DumpOptions;
-import com.android.tools.r8.utils.DumpInputFlags;
-import java.nio.file.Path;
-
-public class R8PartialInputToDumpFlags extends DumpInputFlags {
-
- private final Path dumpFile;
-
- public R8PartialInputToDumpFlags(Path dumpFile) {
- this.dumpFile = dumpFile;
- }
-
- @Override
- public Path getDumpPath() {
- return dumpFile;
- }
-
- @Override
- public boolean shouldDump(DumpOptions options) {
- return true;
- }
-
- @Override
- public boolean shouldFailCompilation() {
- return false;
- }
-
- @Override
- public boolean shouldLogDumpInfoMessage() {
- return false;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
index cce90ee..cc39bf2 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
@@ -8,18 +8,29 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.conversion.MethodConversionOptions.Target;
+import com.android.tools.r8.profile.art.ArtProfile;
+import com.android.tools.r8.profile.art.ArtProfileCollection;
+import com.android.tools.r8.profile.art.ArtProfileMethodRule;
+import com.android.tools.r8.profile.startup.profile.StartupProfile;
import com.android.tools.r8.shaking.MissingClasses;
import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.Timing;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
public abstract class R8PartialSubCompilationConfiguration {
@@ -52,9 +63,11 @@
private final Set<DexType> d8Types;
private final Set<DexType> r8Types;
+ private ArtProfileCollection artProfiles;
private ClassToFeatureSplitMap classToFeatureSplitMap;
private Collection<DexProgramClass> dexedOutputClasses;
private Collection<DexProgramClass> desugaredOutputClasses;
+ private StartupProfile startupProfile;
public R8PartialD8SubCompilationConfiguration(
Set<DexType> d8Types, Set<DexType> r8Types, Timing timing) {
@@ -63,6 +76,11 @@
this.r8Types = r8Types;
}
+ public ArtProfileCollection getArtProfiles() {
+ assert artProfiles != null;
+ return artProfiles;
+ }
+
public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
return classToFeatureSplitMap;
}
@@ -77,6 +95,11 @@
return desugaredOutputClasses;
}
+ public StartupProfile getStartupProfile() {
+ assert startupProfile != null;
+ return startupProfile;
+ }
+
public MethodConversionOptions.Target getTargetFor(
ProgramDefinition definition, AppView<?> appView) {
DexType type = definition.getContextType();
@@ -111,6 +134,7 @@
}
public void writeApplication(AppView<AppInfo> appView) {
+ artProfiles = appView.getArtProfileCollection().transformForR8Partial(appView);
classToFeatureSplitMap =
appView.appInfo().getClassToFeatureSplitMap().commitSyntheticsForR8Partial(appView);
dexedOutputClasses = new ArrayList<>();
@@ -122,22 +146,34 @@
desugaredOutputClasses.add(clazz);
}
}
+ startupProfile = appView.getStartupProfile();
}
}
public static class R8PartialR8SubCompilationConfiguration
extends R8PartialSubCompilationConfiguration {
+ private ArtProfileCollection artProfiles;
private ClassToFeatureSplitMap classToFeatureSplitMap;
- private Collection<DexProgramClass> dexingOutputClasses;
+ private Map<DexType, DexProgramClass> dexingOutputClasses;
+ private StartupProfile startupProfile;
public R8PartialR8SubCompilationConfiguration(
+ ArtProfileCollection artProfiles,
ClassToFeatureSplitMap classToFeatureSplitMap,
Collection<DexProgramClass> dexingOutputClasses,
+ StartupProfile startupProfile,
Timing timing) {
super(timing);
+ this.artProfiles = artProfiles;
this.classToFeatureSplitMap = classToFeatureSplitMap;
- this.dexingOutputClasses = dexingOutputClasses;
+ this.dexingOutputClasses =
+ MapUtils.transform(dexingOutputClasses, IdentityHashMap::new, DexClass::getType);
+ this.startupProfile = startupProfile;
+ }
+
+ public ArtProfileCollection getArtProfiles() {
+ return artProfiles;
}
public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
@@ -146,30 +182,63 @@
public Collection<DexProgramClass> getDexingOutputClasses() {
assert dexingOutputClasses != null;
- return dexingOutputClasses;
+ return dexingOutputClasses.values();
+ }
+
+ public StartupProfile getStartupProfile() {
+ assert startupProfile != null;
+ return startupProfile;
+ }
+
+ public void amendCompleteArtProfile(ArtProfile.Builder artProfileBuilder) {
+ List<DexProgramClass> dexingOutputClassesSorted =
+ ListUtils.sort(dexingOutputClasses.values(), Comparator.comparing(DexClass::getType));
+ for (DexProgramClass clazz : dexingOutputClassesSorted) {
+ artProfileBuilder.addClassRule(clazz.getType());
+ clazz.forEachMethod(
+ method ->
+ artProfileBuilder.addMethodRule(
+ ArtProfileMethodRule.builder()
+ .setMethod(method.getReference())
+ .acceptMethodRuleInfoBuilder(
+ methodRuleInfoBuilder ->
+ methodRuleInfoBuilder.setIsHot().setIsStartup().setIsPostStartup())
+ .build()));
+ }
}
public void commitDexingOutputClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
- Set<DexType> dexingOutputTypes =
- SetUtils.mapIdentityHashSet(dexingOutputClasses, DexClass::getType);
DirectMappedDexApplication newApp =
appView
.app()
.asDirect()
.builder()
- .removeClasspathClasses(clazz -> dexingOutputTypes.contains(clazz.getType()))
- .addProgramClasses(dexingOutputClasses)
+ .removeClasspathClasses(clazz -> dexingOutputClasses.containsKey(clazz.getType()))
+ .addProgramClasses(dexingOutputClasses.values())
.build();
appView.rebuildAppInfo(newApp);
assert amendMissingClasses(appView);
- dexingOutputClasses = null;
+ }
+
+ public boolean hasD8DefinitionFor(DexReference reference) {
+ if (reference.isDexType()) {
+ return dexingOutputClasses.containsKey(reference.asDexType());
+ } else {
+ DexMember<?, ?> member = reference.asDexMember();
+ DexProgramClass holder = dexingOutputClasses.get(member.getHolderType());
+ return member.isDefinedOnClass(holder);
+ }
+ }
+
+ public boolean isD8Definition(ProgramDefinition definition) {
+ return hasD8DefinitionFor(definition.getReference());
}
private boolean amendMissingClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
if (appView.hasLiveness()) {
MissingClasses.Builder missingClassesBuilder =
appView.appInfo().getMissingClasses().builder();
- for (DexProgramClass clazz : dexingOutputClasses) {
+ for (DexProgramClass clazz : dexingOutputClasses.values()) {
clazz.forEachImmediateSuperClassMatching(
appView.app(),
(supertype, superclass) -> superclass == null,
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/AllClassesMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/AllClassesMatcher.java
new file mode 100644
index 0000000..4c794d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/AllClassesMatcher.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import com.android.tools.r8.graph.DexString;
+
+public class AllClassesMatcher implements R8PartialPredicate {
+
+ public static final String PATTERN = "L**";
+
+ @Override
+ public boolean test(DexString descriptor) {
+ return true;
+ }
+
+ @Override
+ public String serializeToString() {
+ return "**";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/ClassNameMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/ClassNameMatcher.java
new file mode 100644
index 0000000..76f8603
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/ClassNameMatcher.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+public class ClassNameMatcher implements R8PartialPredicate {
+
+ private final String descriptor;
+
+ public ClassNameMatcher(String descriptor) {
+ assert descriptor.charAt(0) == 'L';
+ assert descriptor.charAt(descriptor.length() - 1) == ';';
+ this.descriptor = descriptor;
+ }
+
+ @Override
+ public boolean test(DexString descriptor) {
+ return descriptor.toString().equals(this.descriptor);
+ }
+
+ @Override
+ public String serializeToString() {
+ return DescriptorUtils.descriptorToJavaType(descriptor);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/ClassPrefixMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/ClassPrefixMatcher.java
new file mode 100644
index 0000000..3390bf2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/ClassPrefixMatcher.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static com.android.tools.r8.partial.predicate.R8PartialPredicateUtils.descriptorPrefixPatternToJavaTypePattern;
+
+import com.android.tools.r8.graph.DexString;
+import java.io.UTFDataFormatException;
+
+public class ClassPrefixMatcher implements R8PartialPredicate {
+
+ private final byte[] descriptorPrefix;
+ private final int descriptorPrefixLength;
+
+ public ClassPrefixMatcher(String descriptorPrefix) {
+ assert descriptorPrefix.charAt(0) == 'L';
+ assert descriptorPrefix.charAt(descriptorPrefix.length() - 1) != '/';
+ this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
+ this.descriptorPrefixLength = descriptorPrefix.length();
+ }
+
+ @Override
+ public boolean test(DexString descriptor) {
+ return descriptor.startsWith(descriptorPrefix)
+ && descriptor.lastIndexOf('/') < descriptorPrefixLength - 1;
+ }
+
+ @Override
+ public String serializeToString() {
+ try {
+ return descriptorPrefixPatternToJavaTypePattern(
+ DexString.decodeFromMutf8(descriptorPrefix, descriptorPrefixLength) + "*");
+ } catch (UTFDataFormatException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/PackageAndSubpackagePrefixMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/PackageAndSubpackagePrefixMatcher.java
new file mode 100644
index 0000000..b790ccb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/PackageAndSubpackagePrefixMatcher.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static com.android.tools.r8.partial.predicate.R8PartialPredicateUtils.descriptorPrefixPatternToJavaTypePattern;
+
+import com.android.tools.r8.graph.DexString;
+import java.io.UTFDataFormatException;
+
+public class PackageAndSubpackagePrefixMatcher implements R8PartialPredicate {
+
+ private final byte[] descriptorPrefix;
+ private final int descriptorPrefixLength;
+
+ public PackageAndSubpackagePrefixMatcher(String descriptorPrefix) {
+ assert descriptorPrefix.charAt(0) == 'L';
+ assert descriptorPrefix.charAt(descriptorPrefix.length() - 1) == '/';
+ this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
+ this.descriptorPrefixLength = descriptorPrefix.length();
+ }
+
+ @Override
+ public boolean test(DexString descriptor) {
+ return descriptor.startsWith(descriptorPrefix);
+ }
+
+ @Override
+ public String serializeToString() {
+ try {
+ return descriptorPrefixPatternToJavaTypePattern(
+ DexString.decodeFromMutf8(descriptorPrefix, descriptorPrefixLength) + "**");
+ } catch (UTFDataFormatException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/PackagePrefixMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/PackagePrefixMatcher.java
new file mode 100644
index 0000000..5307c22
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/PackagePrefixMatcher.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static com.android.tools.r8.partial.predicate.R8PartialPredicateUtils.descriptorPrefixPatternToJavaTypePattern;
+
+import com.android.tools.r8.graph.DexString;
+import java.io.UTFDataFormatException;
+
+public class PackagePrefixMatcher implements R8PartialPredicate {
+
+ private final byte[] descriptorPrefix;
+ private final int descriptorPrefixLength;
+
+ public PackagePrefixMatcher(String descriptorPrefix) {
+ assert descriptorPrefix.charAt(0) == 'L';
+ assert descriptorPrefix.charAt(descriptorPrefix.length() - 1) == '/';
+ this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
+ this.descriptorPrefixLength = descriptorPrefix.length();
+ }
+
+ @Override
+ public boolean test(DexString descriptor) {
+ return descriptor.startsWith(descriptorPrefix)
+ && descriptor.lastIndexOf('/') == descriptorPrefixLength - 1;
+ }
+
+ @Override
+ public String serializeToString() {
+ try {
+ return descriptorPrefixPatternToJavaTypePattern(
+ DexString.decodeFromMutf8(descriptorPrefix, descriptorPrefixLength) + "*");
+ } catch (UTFDataFormatException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicate.java b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicate.java
new file mode 100644
index 0000000..b28ac73
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicate.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import com.android.tools.r8.graph.DexString;
+import java.util.function.Predicate;
+
+public interface R8PartialPredicate extends Predicate<DexString> {
+
+ String serializeToString();
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateCollection.java b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateCollection.java
new file mode 100644
index 0000000..2b8339b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateCollection.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import java.util.ArrayList;
+import java.util.List;
+
+public class R8PartialPredicateCollection {
+
+ private final List<R8PartialPredicate> predicates = new ArrayList<>();
+
+ public void add(R8PartialPredicate predicate) {
+ predicates.add(predicate);
+ }
+
+ public boolean isEmpty() {
+ return predicates.isEmpty();
+ }
+
+ public boolean test(DexProgramClass clazz) {
+ DexString descriptor = clazz.getType().getDescriptor();
+ for (R8PartialPredicate predicate : predicates) {
+ if (predicate.test(descriptor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public byte[] getDumpFileContent() {
+ assert !isEmpty();
+ StringBuilder builder = new StringBuilder();
+ builder.append(predicates.iterator().next().serializeToString());
+ for (int i = 1; i < predicates.size(); i++) {
+ builder.append('\n').append(predicates.get(i).serializeToString());
+ }
+ return builder.toString().getBytes(UTF_8);
+ }
+
+ public String serializeToString() {
+ throw new Unimplemented();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateUtils.java b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateUtils.java
new file mode 100644
index 0000000..7b3d165
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateUtils.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static com.android.tools.r8.utils.DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR;
+import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
+
+public class R8PartialPredicateUtils {
+
+ static String descriptorPrefixPatternToJavaTypePattern(String descriptorPrefixPattern) {
+ assert descriptorPrefixPattern.charAt(0) == 'L';
+ assert descriptorPrefixPattern.charAt(descriptorPrefixPattern.length() - 1) != ';';
+ return descriptorPrefixPattern
+ .substring(1)
+ .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/UnnamedPackageMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/UnnamedPackageMatcher.java
new file mode 100644
index 0000000..fd04ccb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/UnnamedPackageMatcher.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import com.android.tools.r8.graph.DexString;
+
+public class UnnamedPackageMatcher implements R8PartialPredicate {
+
+ public static final String PATTERN = "L*";
+
+ @Override
+ public boolean test(DexString descriptor) {
+ return descriptor.indexOf('/') == -1;
+ }
+
+ @Override
+ public String serializeToString() {
+ return "*";
+ }
+}
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 16058e4..df2c15b 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
@@ -8,7 +8,6 @@
import com.android.tools.r8.TextOutputStream;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
@@ -17,6 +16,7 @@
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration;
import com.android.tools.r8.profile.AbstractProfile;
import com.android.tools.r8.profile.AbstractProfileRule;
import com.android.tools.r8.utils.InternalOptions;
@@ -179,15 +179,13 @@
AppInfo appInfo = appView.appInfo();
return transform(
(classRule, builder) -> {
- if (appInfo.hasDefinitionForWithoutExistenceAssert(classRule.getType())) {
+ if (hasDefinitionFor(appInfo, classRule.getType())) {
builder.addClassRule(
ArtProfileClassRule.builder().setType(classRule.getType()).build());
}
},
(methodRule, builder) -> {
- DexClass clazz =
- appInfo.definitionForWithoutExistenceAssert(methodRule.getMethod().getHolderType());
- if (methodRule.getMethod().isDefinedOnClass(clazz)) {
+ if (hasDefinitionFor(appInfo, methodRule.getMethod())) {
builder.addMethodRule(
ArtProfileMethodRule.builder()
.setMethod(methodRule.getMethod())
@@ -199,6 +197,17 @@
});
}
+ private boolean hasDefinitionFor(AppInfo appInfo, DexReference reference) {
+ if (appInfo.hasDefinitionForWithoutExistenceAssert(reference)) {
+ return true;
+ }
+ R8PartialSubCompilationConfiguration subCompilationConfiguration =
+ appInfo.options().partialSubCompilationConfiguration;
+ return subCompilationConfiguration != null
+ && subCompilationConfiguration.isR8()
+ && subCompilationConfiguration.asR8().hasD8DefinitionFor(reference);
+ }
+
public ArtProfile withoutPrunedItems(PrunedItems prunedItems) {
rules.keySet().removeIf(prunedItems::isRemoved);
return this;
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
index 6f4fb22..c371080 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
@@ -10,9 +10,10 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -22,16 +23,21 @@
public static ArtProfileCollection createInitialArtProfileCollection(
AppInfo appInfo, InternalOptions options) {
ArtProfileOptions artProfileOptions = options.getArtProfileOptions();
- Collection<ArtProfileProvider> artProfileProviders = artProfileOptions.getArtProfileProviders();
- List<ArtProfile> artProfiles =
- new ArrayList<>(
- artProfileProviders.size()
- + BooleanUtils.intValue(artProfileOptions.isCompletenessCheckForTestingEnabled()));
- for (ArtProfileProvider artProfileProvider : artProfileProviders) {
- ArtProfile.Builder artProfileBuilder =
- ArtProfile.builderForInitialArtProfile(artProfileProvider, options);
- artProfileProvider.getArtProfile(artProfileBuilder);
- artProfiles.add(artProfileBuilder.build());
+ List<ArtProfile> artProfiles = new ArrayList<>();
+ if (options.partialSubCompilationConfiguration == null
+ || options.partialSubCompilationConfiguration.isD8()) {
+ Collection<ArtProfileProvider> artProfileProviders =
+ artProfileOptions.getArtProfileProviders();
+ for (ArtProfileProvider artProfileProvider : artProfileProviders) {
+ ArtProfile.Builder artProfileBuilder =
+ ArtProfile.builderForInitialArtProfile(artProfileProvider, options);
+ artProfileProvider.getArtProfile(artProfileBuilder);
+ artProfiles.add(artProfileBuilder.build());
+ }
+ } else {
+ assert options.partialSubCompilationConfiguration.isR8();
+ Iterables.addAll(
+ artProfiles, options.partialSubCompilationConfiguration.asR8().getArtProfiles());
}
if (artProfileOptions.isCompletenessCheckForTestingEnabled()) {
artProfiles.add(createCompleteArtProfile(appInfo));
@@ -48,8 +54,7 @@
private static ArtProfile createCompleteArtProfile(AppInfo appInfo) {
ArtProfile.Builder artProfileBuilder = ArtProfile.builder();
for (DexProgramClass clazz : appInfo.classesWithDeterministicOrder()) {
- artProfileBuilder.addClassRule(
- ArtProfileClassRule.builder().setType(clazz.getType()).build());
+ artProfileBuilder.addClassRule(clazz.getType());
clazz.forEachMethod(
method ->
artProfileBuilder.addMethodRule(
@@ -60,6 +65,11 @@
methodRuleInfoBuilder.setIsHot().setIsStartup().setIsPostStartup())
.build()));
}
+ R8PartialSubCompilationConfiguration subCompilationConfiguration =
+ appInfo.options().partialSubCompilationConfiguration;
+ if (subCompilationConfiguration != null && subCompilationConfiguration.isR8()) {
+ subCompilationConfiguration.asR8().amendCompleteArtProfile(artProfileBuilder);
+ }
return artProfileBuilder.build();
}
@@ -80,6 +90,8 @@
public abstract void supplyConsumers(AppView<?> appView);
+ public abstract ArtProfileCollection transformForR8Partial(AppView<AppInfo> appView);
+
public abstract ArtProfileCollection withoutMissingItems(AppView<?> appView);
public abstract ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing);
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
index 45b3072..75d7cfe 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
@@ -37,6 +37,17 @@
this.options = options;
}
+ // Constructor for D8 in R8 partial.
+ public ArtProfileOptions(InternalOptions options, ArtProfileOptions artProfileOptions) {
+ this(options);
+ this.artProfilesForRewriting = artProfileOptions.artProfilesForRewriting;
+ this.enableCompletenessCheckForTesting = artProfileOptions.enableCompletenessCheckForTesting;
+ this.enableNopCheckForTesting = artProfileOptions.enableNopCheckForTesting;
+ this.hasReadArtProfileProviders = artProfileOptions.hasReadArtProfileProviders;
+ this.allowReadingEmptyArtProfileProvidersMultipleTimesForTesting =
+ artProfileOptions.allowReadingEmptyArtProfileProvidersMultipleTimesForTesting;
+ }
+
public Collection<ArtProfileForRewriting> getArtProfilesForRewriting() {
return artProfilesForRewriting;
}
@@ -45,6 +56,8 @@
assert !hasReadArtProfileProviders
|| (allowReadingEmptyArtProfileProvidersMultipleTimesForTesting
&& artProfilesForRewriting.isEmpty());
+ assert options.partialSubCompilationConfiguration == null
+ || options.partialSubCompilationConfiguration.isD8();
hasReadArtProfileProviders = true;
return ListUtils.map(artProfilesForRewriting, ArtProfileForRewriting::getArtProfileProvider);
}
@@ -57,9 +70,7 @@
return enableCompletenessCheckForTesting
&& !options.getLibraryDesugaringOptions().isDesugaredLibraryCompilation()
&& !options.getStartupOptions().isStartupCompletenessCheckForTestingEnabled()
- && !options.getInstrumentationOptions().isInstrumentationEnabled()
- // TODO(b/390355818): Enable completeness testing for R8 partial.
- && options.partialSubCompilationConfiguration == null;
+ && !options.getInstrumentationOptions().isInstrumentationEnabled();
}
public boolean isNopCheckForTestingEnabled() {
diff --git a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
index 42ab16d..5b3f159 100644
--- a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.profile.art;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.GraphLens;
@@ -58,6 +59,11 @@
}
@Override
+ public ArtProfileCollection transformForR8Partial(AppView<AppInfo> appView) {
+ return this;
+ }
+
+ @Override
public ArtProfileCollection withoutMissingItems(AppView<?> appView) {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
index 926bab4..4f98cdf 100644
--- a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.profile.art;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.GraphLens;
@@ -96,6 +97,28 @@
}
}
+ /**
+ * Processes the D8 art profiles before forwarding them to R8, in R8 partial.
+ *
+ * <p>When testing, a synthetic art profile is injected for completeness testing. This first runs
+ * the completeness checker and then removes the synthetic profile.
+ *
+ * <p>In release, this returns the art profiles as-is.
+ */
+ @Override
+ public ArtProfileCollection transformForR8Partial(AppView<AppInfo> appView) {
+ ArtProfileOptions artProfileOptions = appView.options().getArtProfileOptions();
+ assert !artProfileOptions.isNopCheckForTestingEnabled();
+ if (artProfileOptions.isCompletenessCheckForTestingEnabled()) {
+ assert ArtProfileCompletenessChecker.verify(appView);
+ ListUtils.removeLast(artProfiles);
+ if (artProfiles.isEmpty()) {
+ return ArtProfileCollection.empty();
+ }
+ }
+ return this;
+ }
+
@Override
public ArtProfileCollection withoutMissingItems(AppView<?> appView) {
return map(artProfile -> artProfile.withoutMissingItems(appView));
diff --git a/src/main/java/com/android/tools/r8/profile/art/ThrowingArtProfileConsumer.java b/src/main/java/com/android/tools/r8/profile/art/ThrowingArtProfileConsumer.java
new file mode 100644
index 0000000..e2d61e0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ThrowingArtProfileConsumer.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2025, 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.profile.art;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TextOutputStream;
+import com.android.tools.r8.errors.Unreachable;
+
+public class ThrowingArtProfileConsumer implements ArtProfileConsumer {
+
+ @Override
+ public TextOutputStream getHumanReadableArtProfileConsumer() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public ArtProfileRuleConsumer getRuleConsumer() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ throw new Unreachable();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
index 7fa618c..2882170 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
@@ -80,6 +80,12 @@
context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
}
+ public void addMethodAndHolderIfContextIsInProfile(
+ ProgramMethod method, ProgramDefinition context) {
+ applyIfContextIsInProfile(
+ context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+ }
+
void applyIfContextIsInProfile(
ProgramDefinition context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
if (context.isProgramClass()) {
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
index 3af5dc4..8a65064 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
@@ -342,6 +342,12 @@
}
@Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+ parent.acceptAutoCloseableDispatchMethod(method, context);
+ }
+
+ @Override
public void acceptRecordClass(DexProgramClass recordClass) {
parent.acceptRecordClass(recordClass);
}
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
index 944f860..926c9d7 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
@@ -106,11 +107,28 @@
}
@Override
+ public void acceptAutoCloseableForwardingMethod(ProgramMethod method, ProgramDefinition context) {
+ additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+ parent.acceptAutoCloseableForwardingMethod(method, context);
+ }
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+ parent.acceptAutoCloseableDispatchMethod(method, context);
+ }
+
+ @Override
public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
parent.acceptInterfaceInjection(clazz, newInterface);
}
@Override
+ public void acceptAutoCloseableInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
+ parent.acceptAutoCloseableInterfaceInjection(clazz, newInterface);
+ }
+
+ @Override
public void acceptInterfaceMethodDesugaringForwardingMethod(
ProgramMethod method, DexClassAndMethod baseMethod) {
additionsCollection.addMethodIfContextIsInProfile(method, baseMethod);
diff --git a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
index 62ee585..61c845e 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault;
import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SystemPropertyUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Paths;
@@ -65,7 +66,10 @@
private Collection<StartupProfileProvider> startupProfileProviders;
- public StartupOptions() {
+ private final InternalOptions options;
+
+ public StartupOptions(InternalOptions options) {
+ this.options = options;
this.startupProfileProviders =
SystemPropertyUtils.applySystemProperty(
"com.android.tools.r8.startup.profile",
@@ -76,6 +80,21 @@
Collections::emptyList);
}
+ public StartupOptions(InternalOptions options, StartupOptions startupOptions) {
+ this(options);
+ this.enableOutlining = startupOptions.enableOutlining;
+ this.enableMinimalStartupDex = startupOptions.enableMinimalStartupDex;
+ this.enableStartupBoundaryOptimizations = startupOptions.enableStartupBoundaryOptimizations;
+ this.enableStartupCompletenessCheckForTesting =
+ startupOptions.enableStartupCompletenessCheckForTesting;
+ this.enableStartupLayoutOptimization = startupOptions.enableStartupLayoutOptimization;
+ this.enableStartupMixedSectionLayoutOptimizations =
+ startupOptions.enableStartupMixedSectionLayoutOptimizations;
+ this.multiStartupDexDistributionStrategyName =
+ startupOptions.multiStartupDexDistributionStrategyName;
+ this.startupProfileProviders = startupOptions.startupProfileProviders;
+ }
+
public boolean isOutliningEnabled() {
return enableOutlining;
}
@@ -141,6 +160,8 @@
}
public Collection<StartupProfileProvider> getStartupProfileProviders() {
+ assert options.partialSubCompilationConfiguration == null
+ || options.partialSubCompilationConfiguration.isD8();
return startupProfileProviders;
}
diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java
index f675a24..609e300 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java
@@ -6,12 +6,12 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.SetUtils;
@@ -129,20 +129,28 @@
AppInfo appInfo = appView.appInfo();
return transform(
(classRule, builder) -> {
- if (appInfo.hasDefinitionForWithoutExistenceAssert(classRule.getReference())) {
+ if (hasDefinitionFor(appInfo, classRule.getReference())) {
builder.addClassRule(classRule);
}
},
(methodRule, builder) -> {
- DexClass clazz =
- appInfo.definitionForWithoutExistenceAssert(
- methodRule.getReference().getHolderType());
- if (methodRule.getReference().isDefinedOnClass(clazz)) {
+ if (hasDefinitionFor(appInfo, methodRule.getReference())) {
builder.addMethodRule(methodRule);
}
});
}
+ private boolean hasDefinitionFor(AppInfo appInfo, DexReference reference) {
+ if (appInfo.hasDefinitionForWithoutExistenceAssert(reference)) {
+ return true;
+ }
+ R8PartialSubCompilationConfiguration subCompilationConfiguration =
+ appInfo.options().partialSubCompilationConfiguration;
+ return subCompilationConfiguration != null
+ && subCompilationConfiguration.isR8()
+ && subCompilationConfiguration.asR8().hasD8DefinitionFor(reference);
+ }
+
@Override
public StartupProfile withoutPrunedItems(
PrunedItems prunedItems, SyntheticItems syntheticItems, Timing timing) {
diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java
index e6a4cb5..2b08062 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java
@@ -65,18 +65,23 @@
return startupProfile != null ? startupProfile : empty();
}
- public static StartupProfile createInitialStartupProfileForD8(AppView<?> appView) {
+ public static StartupProfile createInitialStartupProfileForD8(DexApplication application) {
return createInitialStartupProfile(
- appView.options(),
- origin -> new MissingStartupProfileItemsDiagnostic.Builder(appView).setOrigin(origin));
+ application.options,
+ origin -> new MissingStartupProfileItemsDiagnostic.Builder(application).setOrigin(origin));
}
public static StartupProfile createInitialStartupProfileForR8(DexApplication application) {
// In R8 we expect a startup profile that matches the input app. Since profiles gathered from
// running on ART will include synthetics, and these synthetics are not in the input app, we do
// not raise warnings if some rules in the profile do not match anything.
- return createInitialStartupProfile(
- application.options, origin -> MissingStartupProfileItemsDiagnostic.Builder.nop());
+ InternalOptions options = application.options;
+ if (options.partialSubCompilationConfiguration != null) {
+ return options.partialSubCompilationConfiguration.asR8().getStartupProfile();
+ } else {
+ return createInitialStartupProfile(
+ options, origin -> MissingStartupProfileItemsDiagnostic.Builder.nop());
+ }
}
public static StartupProfile empty() {
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java b/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
index 5925d3f..fe09d4b 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
@@ -203,5 +203,10 @@
public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
return true;
}
+
+ @Override
+ public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+ return this;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 058e951..6867016 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.shaking.ifrules.MaterializedSubsequentRulesOptimizer;
@@ -23,7 +22,7 @@
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.UncheckedExecutionException;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -41,21 +40,18 @@
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final DexItemFactory factory;
- private final SubtypingInfo subtypingInfo;
private final Enqueuer enqueuer;
private final ConsequentRootSetBuilder rootSetBuilder;
private final TaskCollection<?> tasks;
IfRuleEvaluator(
AppView<? extends AppInfoWithClassHierarchy> appView,
- SubtypingInfo subtypingInfo,
Enqueuer enqueuer,
ConsequentRootSetBuilder rootSetBuilder,
TaskCollection<?> tasks) {
assert tasks.isEmpty();
this.appView = appView;
this.factory = appView.dexItemFactory();
- this.subtypingInfo = subtypingInfo;
this.enqueuer = enqueuer;
this.rootSetBuilder = rootSetBuilder;
this.tasks = tasks;
@@ -101,11 +97,10 @@
if (classKind == ClassKind.PROGRAM) {
return ifRule.relevantCandidatesForRule(
appView,
- subtypingInfo,
+ enqueuer.getSubtypingInfo(),
(Iterable<DexProgramClass>) classesWithNewlyLiveMembers,
isEffectivelyLive);
}
- assert classKind == ClassKind.LIBRARY;
return classesWithNewlyLiveMembers;
}
@@ -361,7 +356,7 @@
.collect(Collectors.toList());
// Member rules are combined as AND logic: if found unsatisfied member rule, this
// combination of live members is not a good fit.
- ProgramMethodSet methodsSatisfyingRule = ProgramMethodSet.create();
+ DexClassAndMethodSet methodsSatisfyingRule = DexClassAndMethodSet.create();
boolean satisfied =
memberKeepRules.stream()
.allMatch(
@@ -372,7 +367,7 @@
DexClassAndMethod methodSatisfyingRule =
rootSetBuilder.getMethodSatisfyingRule(memberRule, methodsInCombination);
if (methodSatisfyingRule != null) {
- methodsSatisfyingRule.add(methodSatisfyingRule.asProgramMethod());
+ methodsSatisfyingRule.add(methodSatisfyingRule);
return true;
}
return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
index 6075570..fa32a02 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
import com.android.tools.r8.graph.analysis.NewlyLiveClassEnqueuerAnalysis;
@@ -33,6 +32,7 @@
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
@@ -145,26 +145,50 @@
return false;
}
- public ConsequentRootSet applyActiveIfRulesToLibraryClasses(Enqueuer enqueuer, Timing timing)
+ public void applyActiveIfRulesToClasspathClasses(
+ ConsequentRootSetBuilder consequentRootSetBuilder, Enqueuer enqueuer, Timing timing)
throws ExecutionException {
- timing.begin("Apply if rules to library classes");
- SubtypingInfo subtypingInfo = enqueuer.getSubtypingInfo();
- ConsequentRootSetBuilder consequentRootSetBuilder =
- ConsequentRootSet.builder(appView, enqueuer, subtypingInfo);
+ try (Timing t = timing.begin("Apply if rules to classpath classes")) {
+ applyActiveIfRulesToNonProgramClass(
+ consequentRootSetBuilder,
+ enqueuer,
+ appView.app().asDirect().classpathClasses(),
+ ClassKind.CLASSPATH,
+ timing);
+ }
+ }
+
+ public void applyActiveIfRulesToLibraryClasses(
+ ConsequentRootSetBuilder consequentRootSetBuilder, Enqueuer enqueuer, Timing timing)
+ throws ExecutionException {
+ if (!appView.testing().applyIfRulesToLibrary) {
+ return;
+ }
+ try (Timing t = timing.begin("Apply if rules to library classes")) {
+ applyActiveIfRulesToNonProgramClass(
+ consequentRootSetBuilder,
+ enqueuer,
+ appView.app().asDirect().libraryClasses(),
+ ClassKind.LIBRARY,
+ timing);
+ }
+ }
+
+ private <T extends DexClass> void applyActiveIfRulesToNonProgramClass(
+ ConsequentRootSetBuilder consequentRootSetBuilder,
+ Enqueuer enqueuer,
+ Collection<T> classes,
+ ClassKind<T> classKind,
+ Timing timing)
+ throws ExecutionException {
IfRuleEvaluator evaluator =
- new IfRuleEvaluator(appView, subtypingInfo, enqueuer, consequentRootSetBuilder, tasks);
+ new IfRuleEvaluator(appView, enqueuer, consequentRootSetBuilder, tasks);
evaluator.processActiveIfRulesWithMembers(
- activeIfRulesWithMembers,
- ClassKind.LIBRARY,
- appView.app().asDirect().libraryClasses(),
- alwaysTrue());
+ activeIfRulesWithMembers, classKind, classes, alwaysTrue());
evaluator.processActiveIfRulesWithoutMembers(
activeIfRulesWithoutMembers,
- newIdentityHashMapFromCollection(
- appView.app().asDirect().libraryClasses(), DexClass::getType, Function.identity()),
+ newIdentityHashMapFromCollection(classes, DexClass::getType, Function.identity()),
timing);
- timing.end();
- return consequentRootSetBuilder.buildConsequentRootSet();
}
@Override
@@ -172,9 +196,12 @@
Enqueuer enqueuer, EnqueuerWorklist worklist, ExecutorService executorService, Timing timing)
throws ExecutionException {
boolean isFirstFixpoint = setSeenFixpoint();
- if (isFirstFixpoint && appView.testing().applyIfRulesToLibrary) {
- ConsequentRootSet consequentRootSet = applyActiveIfRulesToLibraryClasses(enqueuer, timing);
- enqueuer.addConsequentRootSet(consequentRootSet);
+ if (isFirstFixpoint) {
+ ConsequentRootSetBuilder consequentRootSetBuilder =
+ ConsequentRootSet.builder(appView, enqueuer);
+ applyActiveIfRulesToClasspathClasses(consequentRootSetBuilder, enqueuer, timing);
+ applyActiveIfRulesToLibraryClasses(consequentRootSetBuilder, enqueuer, timing);
+ enqueuer.addConsequentRootSet(consequentRootSetBuilder.buildConsequentRootSet());
}
if (!shouldProcessActiveIfRulesWithMembers(isFirstFixpoint)
&& !shouldProcessActiveIfRulesWithoutMembers(isFirstFixpoint)) {
@@ -201,11 +228,10 @@
private ConsequentRootSet processActiveIfRules(
Enqueuer enqueuer, boolean isFirstFixpoint, Timing timing) throws ExecutionException {
- SubtypingInfo subtypingInfo = enqueuer.getSubtypingInfo();
ConsequentRootSetBuilder consequentRootSetBuilder =
- ConsequentRootSet.builder(appView, enqueuer, subtypingInfo);
+ ConsequentRootSet.builder(appView, enqueuer);
IfRuleEvaluator evaluator =
- new IfRuleEvaluator(appView, subtypingInfo, enqueuer, consequentRootSetBuilder, tasks);
+ new IfRuleEvaluator(appView, enqueuer, consequentRootSetBuilder, tasks);
timing.begin("If rules with members");
if (shouldProcessActiveIfRulesWithMembers(isFirstFixpoint)) {
processActiveIfRulesWithMembers(evaluator, isFirstFixpoint);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
index cb97db1..81919d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
@@ -3,23 +3,24 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.Definition;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
public class ProguardIfRulePreconditionMatch {
private final ProguardIfRule ifRule;
private final DexClass classMatch;
- private final ProgramMethodSet methodsMatch;
+ private final DexClassAndMethodSet methodsMatch;
public ProguardIfRulePreconditionMatch(ProguardIfRule ifRule, DexClass classMatch) {
- this(ifRule, classMatch, ProgramMethodSet.empty());
+ this(ifRule, classMatch, DexClassAndMethodSet.empty());
}
public ProguardIfRulePreconditionMatch(
- ProguardIfRule ifRule, DexClass classMatch, ProgramMethodSet methodsMatch) {
+ ProguardIfRule ifRule, DexClass classMatch, DexClassAndMethodSet methodsMatch) {
this.ifRule = ifRule;
this.classMatch = classMatch;
this.methodsMatch = methodsMatch;
@@ -50,13 +51,18 @@
}
private void disallowMethodOptimizationsForReevaluation(ConsequentRootSetBuilder rootSetBuilder) {
- for (ProgramMethod method : methodsMatch) {
- rootSetBuilder
- .getDependentMinimumKeepInfo()
- .getOrCreateUnconditionalMinimumKeepInfoFor(method.getReference())
- .asMethodJoiner()
- .disallowClassInlining()
- .disallowInlining();
+ if (classMatch.isProgramClass()) {
+ for (DexClassAndMethod method : methodsMatch) {
+ assert method.isProgramMethod();
+ rootSetBuilder
+ .getDependentMinimumKeepInfo()
+ .getOrCreateUnconditionalMinimumKeepInfoFor(method.getReference())
+ .asMethodJoiner()
+ .disallowClassInlining()
+ .disallowInlining();
+ }
+ } else {
+ assert methodsMatch.stream().noneMatch(Definition::isProgramMethod);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index c1befbf..65e7088 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -231,8 +231,14 @@
R8PartialUseCollector useCollector =
new R8PartialUseCollector(appView) {
+ // Allow D8/R8 boundary obfuscation. We disable repackaging of the R8 part since
+ // repackaging uses a graph lens, which would need to be applied to the D8 part before
+ // the application writer (though this is perfectly doable).
private final ProguardKeepRuleModifiers modifiers =
- ProguardKeepRuleModifiers.builder().build();
+ ProguardKeepRuleModifiers.builder()
+ .setAllowsObfuscation(true)
+ .setAllowsRepackaging(false)
+ .build();
// TODO(b/390576160): Add a test that this works when using -whyareyoukeeping.
private final ReferencedFromD8InR8PartialFakeProguardRule keepRule =
new ReferencedFromD8InR8PartialFakeProguardRule();
@@ -2394,10 +2400,8 @@
}
static ConsequentRootSetBuilder builder(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- Enqueuer enqueuer,
- SubtypingInfo subtypingInfo) {
- return new ConsequentRootSetBuilder(appView, enqueuer, subtypingInfo);
+ AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+ return new ConsequentRootSetBuilder(appView, enqueuer, enqueuer.getSubtypingInfo());
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 0030cec..1ef8edc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -66,6 +66,10 @@
public final SyntheticKind CONST_DYNAMIC = generator.forNonSharableInstanceClass("$Condy");
// Method synthetics.
+ public final SyntheticKind AUTOCLOSEABLE_DISPATCHER =
+ generator.forSingleMethodWithGlobalMerging("AutoCloseableDispatcher");
+ public final SyntheticKind AUTOCLOSEABLE_FORWARDER =
+ generator.forSingleMethodWithGlobalMerging("AutoCloseableForwarder");
public final SyntheticKind TYPE_SWITCH_HELPER =
generator.forSingleMethodWithGlobalMerging("TypeSwitch");
public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index f3c1487..3a83d41 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -43,6 +43,7 @@
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.partial.R8PartialCompilationConfiguration;
import com.android.tools.r8.profile.art.ArtProfileProvider;
import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
import com.android.tools.r8.profile.startup.StartupProfileProviderUtils;
@@ -106,6 +107,8 @@
private static final String dumpLibraryFileName = "library.jar";
private static final String dumpConfigFileName = "proguard.config";
private static final String dumpInputConfigFileName = "proguard_input.config";
+ public static final String dumpR8IncludeFileName = "r8-include.txt";
+ public static final String dumpR8ExcludeFileName = "r8-exclude.txt";
private static Map<FeatureSplit, String> dumpFeatureSplitFileNames(
FeatureSplitConfiguration featureSplitConfiguration, String postfix) {
@@ -496,8 +499,8 @@
return programResourcesMainDescriptor.get(resource);
}
+ @SuppressWarnings("UnusedVariable")
public void dump(Path output, DumpOptions dumpOptions, InternalOptions options) {
- int nextDexIndex = 0;
OpenOption[] openOptions =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) {
@@ -580,6 +583,10 @@
}
}
}
+ if (dumpOptions.hasPartialCompilationConfiguration()) {
+ dumpPartialCompilationConfiguration(dumpOptions.getPartialCompilationConfiguration(), out);
+ }
+ int nextDexIndex = 0;
nextDexIndex =
dumpProgramResources(
dumpProgramFileName,
@@ -677,6 +684,23 @@
}
}
+ private void dumpPartialCompilationConfiguration(
+ R8PartialCompilationConfiguration partialCompilationConfiguration, ZipOutputStream out)
+ throws IOException {
+ writeToZipStream(
+ out,
+ dumpR8IncludeFileName,
+ partialCompilationConfiguration.getIncludePredicates().getDumpFileContent(),
+ ZipEntry.DEFLATED);
+ if (!partialCompilationConfiguration.getExcludePredicates().isEmpty()) {
+ writeToZipStream(
+ out,
+ dumpR8ExcludeFileName,
+ partialCompilationConfiguration.getExcludePredicates().getDumpFileContent(),
+ ZipEntry.DEFLATED);
+ }
+ }
+
private int dumpProgramResources(
String archiveName,
FeatureSplitConfiguration featureSplitConfiguration,
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index ab542a4..f0f345b 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -13,9 +13,12 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.utils.compiledump.CompilerCommandDumpUtils;
+import com.android.tools.r8.utils.compiledump.R8PartialDumpUtils;
import com.android.tools.r8.utils.compiledump.ResourceShrinkerDumpUtils;
import com.android.tools.r8.utils.compiledump.StartupProfileDumpUtils;
import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -65,6 +68,8 @@
"--pg-map-output",
"--desugared-lib",
"--desugared-lib-pg-conf-output",
+ "--partial-include",
+ "--partial-exclude",
"--threads",
"--startup-profile");
@@ -99,12 +104,14 @@
}
@SuppressWarnings({"StringSplitter", "BadImport"})
- public static void main(String[] args) throws CompilationFailedException {
+ public static void main(String[] args) throws CompilationFailedException, IOException {
boolean isCompatMode = false;
OutputMode outputMode = OutputMode.DexIndexed;
Path outputPath = null;
Path pgMapOutput = null;
Path desugaredLibJson = null;
+ Path partialIncludeFile = null;
+ Path partialExcludeFile = null;
StringConsumer desugaredLibKeepRuleConsumer = null;
CompilationMode compilationMode = CompilationMode.RELEASE;
List<Path> program = new ArrayList<>();
@@ -205,6 +212,16 @@
desugaredLibKeepRuleConsumer = new StringConsumer.FileConsumer(Paths.get(operand));
break;
}
+ case "--partial-include":
+ {
+ partialIncludeFile = Paths.get(operand);
+ break;
+ }
+ case "--partial-exclude":
+ {
+ partialExcludeFile = Paths.get(operand);
+ break;
+ }
case "--threads":
{
threads = Integer.parseInt(operand);
@@ -342,6 +359,15 @@
if (pgMapOutput != null) {
commandBuilder.setProguardMapOutputPath(pgMapOutput);
}
+ if (partialIncludeFile != null) {
+ String includePatterns = String.join(",", Files.readAllLines(partialIncludeFile));
+ String excludePatterns =
+ partialExcludeFile != null
+ ? String.join(",", Files.readAllLines(partialExcludeFile))
+ : null;
+ R8PartialDumpUtils.enableExperimentalPartialShrinking(
+ commandBuilder, includePatterns, excludePatterns);
+ }
R8Command command = commandBuilder.build();
if (threads != -1) {
ExecutorService executor = Executors.newWorkStealingPool(threads);
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 c9b09f8..64de864 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1026,8 +1026,8 @@
private final LibraryDesugaringOptions libraryDesugaringOptions =
new LibraryDesugaringOptions(this);
private final MappingComposeOptions mappingComposeOptions = new MappingComposeOptions();
- private final ArtProfileOptions artProfileOptions = new ArtProfileOptions(this);
- private final StartupOptions startupOptions = new StartupOptions();
+ private ArtProfileOptions artProfileOptions = new ArtProfileOptions(this);
+ private StartupOptions startupOptions = new StartupOptions(this);
private final InstrumentationOptions instrumentationOptions;
public R8PartialCompilationConfiguration partialCompilationConfiguration =
R8PartialCompilationConfiguration.disabledConfiguration();
@@ -1127,10 +1127,18 @@
return artProfileOptions;
}
+ public void setArtProfileOptions(ArtProfileOptions artProfileOptions) {
+ this.artProfileOptions = artProfileOptions;
+ }
+
public StartupOptions getStartupOptions() {
return startupOptions;
}
+ public void setStartupOptions(StartupOptions startupOptions) {
+ this.startupOptions = startupOptions;
+ }
+
public InstrumentationOptions getInstrumentationOptions() {
return instrumentationOptions;
}
@@ -2114,6 +2122,7 @@
System.getProperty("com.android.tools.r8.enableKeepAnnotations") != null;
public boolean reverseClassSortingForDeterminism = false;
+ public boolean enableAutoCloseableDesugaring = false;
public boolean enableNumberUnboxer = false;
public boolean printNumberUnboxed = false;
public boolean roundtripThroughLir = false;
@@ -2429,9 +2438,6 @@
public boolean alwaysGenerateLambdaFactoryMethods = false;
- // Work-in-progress optimization: b/145280859.
- public boolean listIterationRewritingEnabled =
- System.getProperty("com.android.tools.r8.enableListIterationRewriting") != null;
public boolean listIterationRewritingRewriteInterfaces =
"all".equals(System.getProperty("com.android.tools.r8.enableListIterationRewriting"));
// Used by unit tests.
@@ -2690,6 +2696,17 @@
&& !canUseDefaultAndStaticInterfaceMethods();
}
+ public boolean canHaveMissingImplementsAutoCloseableInterface() {
+ return getMinApiLevel().isLessThanOrEqualTo(AndroidApiLevel.V);
+ }
+
+ public boolean shouldDesugarAutoCloseable() {
+ return desugarState.isOn()
+ && getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)
+ && canHaveMissingImplementsAutoCloseableInterface()
+ && testing.enableAutoCloseableDesugaring;
+ }
+
public boolean isSwitchRewritingEnabled() {
return enableSwitchRewriting && !debug;
}
diff --git a/src/main/java/com/android/tools/r8/utils/OriginalSourceFiles.java b/src/main/java/com/android/tools/r8/utils/OriginalSourceFiles.java
index 7eca4e7..e5a332e 100644
--- a/src/main/java/com/android/tools/r8/utils/OriginalSourceFiles.java
+++ b/src/main/java/com/android/tools/r8/utils/OriginalSourceFiles.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
import java.util.Map;
/** Abstraction to allow removal of the source file content prior to collecting DEX items. */
@@ -39,11 +38,11 @@
}
/** Saved mapping of original source files prior to mutating the file on classes. */
- public static OriginalSourceFiles fromMap(Map<DexType, DexString> map) {
+ public static OriginalSourceFiles fromMap(Map<DexProgramClass, DexString> map) {
return new OriginalSourceFiles() {
@Override
public DexString getOriginalSourceFile(DexProgramClass clazz) {
- return map.get(clazz.getType());
+ return map.get(clazz);
}
};
}
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/R8PartialDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/R8PartialDumpUtils.java
new file mode 100644
index 0000000..ba620c3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/compiledump/R8PartialDumpUtils.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2025, 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.compiledump;
+
+import com.android.tools.r8.R8Command;
+
+public class R8PartialDumpUtils {
+
+ public static void enableExperimentalPartialShrinking(
+ R8Command.Builder builder, String includePatterns, String excludePatterns) {
+ builder.enableExperimentalPartialShrinking(includePatterns, excludePatterns);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
index fc1029f..bcfdb10 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.CfLineToMethodMapper;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OriginalSourceFiles;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.positions.MappedPositionToClassNameMapperBuilder.MappedPositionToClassNamingBuilder;
@@ -99,7 +100,6 @@
|| newMapVersion.isUnknown();
}
- @SuppressWarnings("ReferenceEquality")
public static ClassNameMapper run(
AppView<?> appView,
AndroidApp inputApp,
@@ -119,60 +119,16 @@
// Collect which files contain which classes that need to have their line numbers optimized.
for (DexProgramClass clazz : appView.appInfo().classes()) {
- IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
- groupMethodsByRenamedName(appView, clazz);
-
- MappedPositionToClassNamingBuilder classNamingBuilder = builder.addClassNaming(clazz);
-
- // Process methods ordered by renamed name.
- List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
- renamedMethodNames.sort(DexString::compareTo);
- for (DexString methodName : renamedMethodNames) {
- List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
- if (methods.size() > 1) {
- // If there are multiple methods with the same name (overloaded) then sort them for
- // deterministic behaviour: the algorithm will assign new line numbers in this order.
- // Methods with different names can share the same line numbers, that's why they don't
- // need to be sorted.
- // If we are compiling to DEX we will try to not generate overloaded names. This saves
- // space by allowing more debug-information to be canonicalized. If we have overloaded
- // methods, we either did not rename them, we renamed them according to a supplied map or
- // they may be bridges for interface methods with covariant return types.
- sortMethods(methods);
- assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
- }
-
- PositionRemapper positionRemapper =
- PositionRemapper.getPositionRemapper(appView, cfLineToMethodMapper);
-
- for (ProgramMethod method : methods) {
- DexEncodedMethod definition = method.getDefinition();
- if (methodName == method.getName()
- && !mustHaveResidualDebugInfo(appView.options(), definition)
- && !definition.isD8R8Synthesized()
- && methods.size() <= 1) {
- continue;
- }
- positionRemapper.setCurrentMethod(definition);
- List<MappedPosition> mappedPositions;
- int pcEncodingCutoff =
- methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
- boolean canUseDexPc = pcEncodingCutoff > 0;
- if (definition.getCode() != null
- && (definition.getCode().isCfCode() || definition.getCode().isDexCode())
- && !appView.isCfByteCodePassThrough(method)) {
- mappedPositions =
- positionToMappedRangeMapper.getMappedPositions(
- method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff);
- } else {
- mappedPositions = new ArrayList<>();
- }
-
- classNamingBuilder.addMappedPositions(
- method, mappedPositions, positionRemapper, canUseDexPc);
- } // for each method of the group
- } // for each method group, grouped by name
- } // for each class
+ if (shouldRun(clazz, appView)) {
+ runForClass(
+ clazz,
+ appView,
+ representation,
+ builder,
+ cfLineToMethodMapper,
+ positionToMappedRangeMapper);
+ }
+ }
// Update all the debug-info objects.
positionToMappedRangeMapper.updateDebugInfoInCodeObjects();
@@ -180,6 +136,77 @@
return builder.build();
}
+ private static boolean shouldRun(DexProgramClass clazz, AppView<?> appView) {
+ InternalOptions options = appView.options();
+ if (options.partialSubCompilationConfiguration == null) {
+ return true;
+ } else {
+ return !options.partialSubCompilationConfiguration.asR8().hasD8DefinitionFor(clazz.getType());
+ }
+ }
+
+ private static void runForClass(
+ DexProgramClass clazz,
+ AppView<?> appView,
+ DebugRepresentationPredicate representation,
+ MappedPositionToClassNameMapperBuilder builder,
+ CfLineToMethodMapper cfLineToMethodMapper,
+ PositionToMappedRangeMapper positionToMappedRangeMapper) {
+ IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
+ groupMethodsByRenamedName(appView, clazz);
+
+ MappedPositionToClassNamingBuilder classNamingBuilder = builder.addClassNaming(clazz);
+
+ // Process methods ordered by renamed name.
+ List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
+ renamedMethodNames.sort(DexString::compareTo);
+ for (DexString methodName : renamedMethodNames) {
+ List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
+ if (methods.size() > 1) {
+ // If there are multiple methods with the same name (overloaded) then sort them for
+ // deterministic behaviour: the algorithm will assign new line numbers in this order.
+ // Methods with different names can share the same line numbers, that's why they don't
+ // need to be sorted.
+ // If we are compiling to DEX we will try to not generate overloaded names. This saves
+ // space by allowing more debug-information to be canonicalized. If we have overloaded
+ // methods, we either did not rename them, we renamed them according to a supplied map or
+ // they may be bridges for interface methods with covariant return types.
+ sortMethods(methods);
+ assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
+ }
+
+ PositionRemapper positionRemapper =
+ PositionRemapper.getPositionRemapper(appView, cfLineToMethodMapper);
+
+ for (ProgramMethod method : methods) {
+ DexEncodedMethod definition = method.getDefinition();
+ if (method.getName().isIdenticalTo(methodName)
+ && !mustHaveResidualDebugInfo(appView.options(), definition)
+ && !definition.isD8R8Synthesized()
+ && methods.size() <= 1) {
+ continue;
+ }
+ positionRemapper.setCurrentMethod(definition);
+ List<MappedPosition> mappedPositions;
+ int pcEncodingCutoff =
+ methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
+ boolean canUseDexPc = pcEncodingCutoff > 0;
+ if (definition.getCode() != null
+ && (definition.getCode().isCfCode() || definition.getCode().isDexCode())
+ && !appView.isCfByteCodePassThrough(method)) {
+ mappedPositions =
+ positionToMappedRangeMapper.getMappedPositions(
+ method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff);
+ } else {
+ mappedPositions = new ArrayList<>();
+ }
+
+ classNamingBuilder.addMappedPositions(
+ method, mappedPositions, positionRemapper, canUseDexPc);
+ } // for each method of the group
+ } // for each method group, grouped by name
+ }
+
@SuppressWarnings("ComplexBooleanConstant")
private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
AppView<?> appView, List<ProgramMethod> methods) {
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java b/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java
new file mode 100644
index 0000000..a6c9881
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryTypeRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper;
+import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.nio.file.Path;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class AutoCloseableDesugaringClassesPresentAtKitKat extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ InternalOptions options = new InternalOptions();
+ Path androidJarK = ToolHelper.getAndroidJar(AndroidApiLevel.K);
+ AndroidApp app = AndroidApp.builder().addProgramFile(androidJarK).build();
+ DirectMappedDexApplication libHolder =
+ new ApplicationReader(app, options, Timing.empty()).read().toDirect();
+ AppInfo initialAppInfo =
+ AppInfo.createInitialAppInfo(libHolder, GlobalSyntheticsStrategy.forNonSynthesizing());
+ AppView<AppInfo> appView =
+ AppView.createForD8(initialAppInfo, DesugaredLibraryTypeRewriter.empty(), Timing.empty());
+
+ AutoCloseableRetargeterHelper data =
+ new AutoCloseableRetargeterHelper(AndroidApiLevel.B, appView.dexItemFactory());
+ Set<DexType> missing = Sets.newIdentityHashSet();
+ for (DexType dexType : data.superTargetsToRewrite()) {
+ if (appView.definitionFor(dexType) == null) {
+ missing.add(dexType);
+ }
+ }
+ assertEquals(1, missing.size());
+ // ForkJoinPool is missing at Android Api level 19 but that's ok since it implements
+ // ExecutorService.close in a more optimized way. We rely on ExecutorService for the
+ // emulated dispatch.
+ assertEquals(
+ options.dexItemFactory().javaUtilConcurrentForkJoinPoolType, missing.iterator().next());
+ }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTest.java
new file mode 100644
index 0000000..5fca53f
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTest.java
@@ -0,0 +1,271 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.ContentProviderClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.DrmManagerClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaMetadataRetriever;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.TypedArray;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+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 AutoCloseableRetargeterAndroidSubtypeTest extends AbstractBackportTest {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public AutoCloseableRetargeterAndroidSubtypeTest(TestParameters parameters) throws IOException {
+ super(
+ parameters,
+ getTestRunner(),
+ ImmutableList.of(
+ getTestRunner(),
+ getTransformedSubclass(
+ ContentProviderClientOverride.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString),
+ getTransformedSubclass(
+ ContentProviderClientSub.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString),
+ getTransformedSubclass(
+ DrmManagerClientOverride.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+ getTransformedSubclass(
+ DrmManagerClientSub.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+ getTransformedSubclass(
+ MediaMetadataRetrieverOverride.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+ getTransformedSubclass(
+ MediaMetadataRetrieverSub.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+ getTransformedSubclass(
+ TypedArrayOverride.class,
+ DexItemFactory.androidContentResTypedArrayDescriptorString),
+ getTransformedSubclass(
+ TypedArraySub.class, DexItemFactory.androidContentResTypedArrayDescriptorString)));
+
+ // The constructor is used by the test and release has been available since API 5 and is the
+ // method close is rewritten to.
+ ignoreInvokes("<init>");
+ ignoreInvokes("release");
+
+ registerTarget(AndroidApiLevel.B, 4);
+ }
+
+ @Override
+ protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+ d8TestBuilder.addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true);
+ }
+
+ @Override
+ protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+ super.configureProgram(builder);
+ if (builder.isJvmTestBuilder()) {
+ builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+ } else {
+ builder
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Override
+ protected void configure(D8TestCompileResult builder) throws Exception {
+ if (parameters.isDexRuntime()) {
+ builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+ } else {
+ builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addStrippedOuter(getClass())
+ .apply(this::configureProgram)
+ .run(parameters.getRuntime(), getTestClassName())
+ // Fails when not desugared.
+ .assertFailureWithErrorThatMatches(containsString("close should not be called"));
+ }
+
+ private static byte[] getTransformedSubclass(Class<?> sub, String superDesc) throws IOException {
+ return transformer(sub)
+ .setSuper(superDesc)
+ .transformMethodInsnInMethod(
+ "<init>",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ assert name.equals("<init>");
+ visitor.visitMethodInsn(
+ opcode,
+ DescriptorUtils.descriptorToInternalName(superDesc),
+ name,
+ descriptor,
+ isInterface);
+ })
+ .transformMethodInsnInMethod(
+ "close",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("close")) {
+ visitor.visitMethodInsn(
+ opcode,
+ DescriptorUtils.descriptorToInternalName(superDesc),
+ name,
+ descriptor,
+ isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .clearNest()
+ .transform();
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(ContentProviderClient.class),
+ DexItemFactory.androidContentContentProviderClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(DrmManagerClient.class),
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaMetadataRetriever.class),
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(TypedArray.class),
+ DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .clearNest()
+ .transform();
+ }
+
+ public static class ContentProviderClientOverride extends ContentProviderClient {
+
+ public void close() {
+ super.close();
+ System.out.println("close content provider");
+ }
+ }
+
+ public static class ContentProviderClientSub extends ContentProviderClient {}
+
+ public static class DrmManagerClientOverride extends DrmManagerClient {
+
+ public void close() {
+ super.close();
+ System.out.println("close drm manager");
+ }
+ }
+
+ public static class DrmManagerClientSub extends DrmManagerClient {}
+
+ public static class MediaMetadataRetrieverOverride extends MediaMetadataRetriever {
+
+ public void close() {
+ super.close();
+ System.out.println("close media metadata");
+ }
+ }
+
+ public static class MediaMetadataRetrieverSub extends MediaMetadataRetriever {}
+
+ public static class TypedArrayOverride extends TypedArray {
+
+ public void close() {
+ super.close();
+ System.out.println("close typed array");
+ }
+ }
+
+ public static class TypedArraySub extends TypedArray {}
+
+ public static class TestRunner extends MiniAssert {
+
+ public static void main(String[] args) {
+ contentProviderClient();
+ drmManagerClient();
+ mediaMetadataRetriever();
+ typedArray();
+ }
+
+ private static void contentProviderClient() {
+ ContentProviderClientSub cpcSub = new ContentProviderClientSub();
+ MiniAssert.assertFalse(cpcSub.wasClosed);
+ cpcSub.close();
+ MiniAssert.assertTrue(cpcSub.wasClosed);
+
+ ContentProviderClientOverride cpcOverride = new ContentProviderClientOverride();
+ MiniAssert.assertFalse(cpcOverride.wasClosed);
+ cpcOverride.close();
+ MiniAssert.assertTrue(cpcOverride.wasClosed);
+ }
+
+ public static void drmManagerClient() {
+ DrmManagerClientSub drmMC = new DrmManagerClientSub();
+ MiniAssert.assertFalse(drmMC.wasClosed);
+ drmMC.close();
+ MiniAssert.assertTrue(drmMC.wasClosed);
+
+ DrmManagerClientOverride drmMCOverride = new DrmManagerClientOverride();
+ MiniAssert.assertFalse(drmMCOverride.wasClosed);
+ drmMCOverride.close();
+ MiniAssert.assertTrue(drmMCOverride.wasClosed);
+ }
+
+ public static void mediaMetadataRetriever() {
+ MediaMetadataRetrieverSub mmrSub = new MediaMetadataRetrieverSub();
+ MiniAssert.assertFalse(mmrSub.wasClosed);
+ mmrSub.close();
+ MiniAssert.assertTrue(mmrSub.wasClosed);
+
+ MediaMetadataRetrieverOverride mmrOverride = new MediaMetadataRetrieverOverride();
+ MiniAssert.assertFalse(mmrOverride.wasClosed);
+ mmrOverride.close();
+ MiniAssert.assertTrue(mmrOverride.wasClosed);
+ }
+
+ public static void typedArray() {
+ TypedArraySub taSub = new TypedArraySub();
+ MiniAssert.assertFalse(taSub.wasClosed);
+ taSub.close();
+ MiniAssert.assertTrue(taSub.wasClosed);
+
+ TypedArrayOverride taOverride = new TypedArrayOverride();
+ MiniAssert.assertFalse(taOverride.wasClosed);
+ taOverride.close();
+ MiniAssert.assertTrue(taOverride.wasClosed);
+ }
+
+ // Forwards to MiniAssert to avoid having to make it public.
+ public static void doFail(String message) {
+ MiniAssert.fail(message);
+ }
+ }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java
new file mode 100644
index 0000000..efad24f
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java
@@ -0,0 +1,330 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.ContentProviderClientApiLevel24;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.DrmManagerClientApiLevel24;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaDrmApiLevel28;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaMetadataRetrieverApiLevel29;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.TypedArrayAndroidApiLevel31;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+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 AutoCloseableRetargeterAndroidSubtypeTwrTest extends AbstractBackportTest {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public AutoCloseableRetargeterAndroidSubtypeTwrTest(TestParameters parameters)
+ throws IOException {
+ super(
+ parameters,
+ getTestRunner(),
+ ImmutableList.of(
+ getTestRunner(),
+ getTransformedSubclass(
+ ContentProviderClientOverride.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString),
+ getTransformedSubclass(
+ ContentProviderClientSub.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString),
+ getTransformedSubclass(
+ DrmManagerClientOverride.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+ getTransformedSubclass(
+ DrmManagerClientSub.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+ getTransformedSubclass(
+ MediaMetadataRetrieverOverride.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+ getTransformedSubclass(
+ MediaMetadataRetrieverSub.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+ getTransformedSubclass(
+ TypedArrayOverride.class,
+ DexItemFactory.androidContentResTypedArrayDescriptorString),
+ getTransformedSubclass(
+ TypedArraySub.class, DexItemFactory.androidContentResTypedArrayDescriptorString)));
+
+ // The constructor is used by the test and release has been available since API 5 and is the
+ // method close is rewritten to.
+ ignoreInvokes("<init>");
+ ignoreInvokes("release");
+
+ registerTarget(AndroidApiLevel.B, 5);
+ }
+
+ @Override
+ protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+ d8TestBuilder.addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true);
+ }
+
+ @Override
+ protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+ super.configureProgram(builder);
+ if (builder.isJvmTestBuilder()) {
+ builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+ } else {
+ builder
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Override
+ protected void configure(D8TestCompileResult builder) throws Exception {
+ if (parameters.isDexRuntime()) {
+ builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+ } else {
+ builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addStrippedOuter(getClass())
+ .apply(this::configureProgram)
+ .run(parameters.getRuntime(), getTestClassName())
+ // Fails when not desugared.
+ .assertFailureWithErrorThatMatches(containsString("close should not be called"));
+ }
+
+ private static byte[] getTransformedSubclass(Class<?> sub, String superDesc) throws IOException {
+ return transformer(sub)
+ .setSuper(superDesc)
+ .transformMethodInsnInMethod(
+ "<init>",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ assert name.equals("<init>");
+ visitor.visitMethodInsn(
+ opcode,
+ DescriptorUtils.descriptorToInternalName(superDesc),
+ name,
+ descriptor,
+ isInterface);
+ })
+ .transformMethodInsnInMethod(
+ "close",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("close")) {
+ visitor.visitMethodInsn(
+ opcode,
+ DescriptorUtils.descriptorToInternalName(superDesc),
+ name,
+ descriptor,
+ isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .clearNest()
+ .transform();
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(ContentProviderClientApiLevel24.class),
+ DexItemFactory.androidContentContentProviderClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(DrmManagerClientApiLevel24.class),
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaMetadataRetrieverApiLevel29.class),
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(TypedArrayAndroidApiLevel31.class),
+ DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaDrmApiLevel28.class),
+ DexItemFactory.androidMediaMediaDrmDescriptorString)
+ .clearNest()
+ .transform();
+ }
+
+ public static class ContentProviderClientOverride extends ContentProviderClientApiLevel24 {
+
+ public void close() {
+ super.close();
+ System.out.println("close content provider");
+ }
+ }
+
+ public static class ContentProviderClientSub extends ContentProviderClientApiLevel24 {}
+
+ public static class DrmManagerClientOverride extends DrmManagerClientApiLevel24 {
+
+ public void close() {
+ super.close();
+ System.out.println("close drm manager");
+ }
+ }
+
+ public static class DrmManagerClientSub extends DrmManagerClientApiLevel24 {}
+
+ public static class MediaMetadataRetrieverOverride extends MediaMetadataRetrieverApiLevel29 {
+
+ public void close() {
+ super.close();
+ System.out.println("close media metadata");
+ }
+ }
+
+ public static class MediaMetadataRetrieverSub extends MediaMetadataRetrieverApiLevel29 {}
+
+ public static class TypedArrayOverride extends TypedArrayAndroidApiLevel31 {
+
+ public void close() {
+ super.close();
+ System.out.println("close typed array");
+ }
+ }
+
+ public static class TypedArraySub extends TypedArrayAndroidApiLevel31 {}
+
+ public static class TestRunner extends MiniAssert {
+
+ public static void main(String[] args) {
+ raw();
+ contentProviderClient();
+ drmManagerClient();
+ mediaMetadataRetriever();
+ typedArray();
+ }
+
+ private static void raw() {
+ ContentProviderClientApiLevel24[] box1 = new ContentProviderClientApiLevel24[1];
+ try (ContentProviderClientApiLevel24 val = new ContentProviderClientApiLevel24()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ DrmManagerClientApiLevel24[] box2 = new DrmManagerClientApiLevel24[1];
+ try (DrmManagerClientApiLevel24 val = new DrmManagerClientApiLevel24()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+
+ MediaDrmApiLevel28[] box3 = new MediaDrmApiLevel28[1];
+ try (MediaDrmApiLevel28 val = new MediaDrmApiLevel28()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box3[0] = val;
+ }
+ MiniAssert.assertTrue(box3[0].wasClosed);
+
+ MediaMetadataRetrieverApiLevel29[] box4 = new MediaMetadataRetrieverApiLevel29[1];
+ try (MediaMetadataRetrieverApiLevel29 val = new MediaMetadataRetrieverApiLevel29()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box4[0] = val;
+ }
+ MiniAssert.assertTrue(box4[0].wasClosed);
+
+ TypedArrayAndroidApiLevel31[] box5 = new TypedArrayAndroidApiLevel31[1];
+ try (TypedArrayAndroidApiLevel31 val = new TypedArrayAndroidApiLevel31()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box5[0] = val;
+ }
+ MiniAssert.assertTrue(box5[0].wasClosed);
+ }
+
+ private static void contentProviderClient() {
+ ContentProviderClientSub[] box1 = new ContentProviderClientSub[1];
+ try (ContentProviderClientSub val = new ContentProviderClientSub()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ ContentProviderClientOverride[] box2 = new ContentProviderClientOverride[1];
+ try (ContentProviderClientOverride val = new ContentProviderClientOverride()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+ }
+
+ public static void drmManagerClient() {
+ DrmManagerClientSub[] box1 = new DrmManagerClientSub[1];
+ try (DrmManagerClientSub val = new DrmManagerClientSub()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ DrmManagerClientOverride[] box2 = new DrmManagerClientOverride[1];
+ try (DrmManagerClientOverride val = new DrmManagerClientOverride()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+ }
+
+ public static void mediaMetadataRetriever() {
+ MediaMetadataRetrieverSub[] box1 = new MediaMetadataRetrieverSub[1];
+ try (MediaMetadataRetrieverSub val = new MediaMetadataRetrieverSub()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ MediaMetadataRetrieverOverride[] box2 = new MediaMetadataRetrieverOverride[1];
+ try (MediaMetadataRetrieverOverride val = new MediaMetadataRetrieverOverride()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+ }
+
+ public static void typedArray() {
+ TypedArraySub[] box1 = new TypedArraySub[1];
+ try (TypedArraySub val = new TypedArraySub()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ TypedArrayOverride[] box2 = new TypedArrayOverride[1];
+ try (TypedArrayOverride val = new TypedArrayOverride()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+ }
+
+ // Forwards to MiniAssert to avoid having to make it public.
+ public static void doFail(String message) {
+ MiniAssert.fail(message);
+ }
+ }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java
new file mode 100644
index 0000000..e68e264
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java
@@ -0,0 +1,160 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.ContentProviderClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.DrmManagerClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaDrm;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaMetadataRetriever;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.TypedArray;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+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 AutoCloseableRetargeterAndroidTest extends AbstractBackportTest {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public AutoCloseableRetargeterAndroidTest(TestParameters parameters) throws IOException {
+ super(parameters, getTestRunner(), ImmutableList.of(getTestRunner()));
+
+ // The constructor is used by the test and release has been available since API 5 and is the
+ // method close is rewritten to.
+ ignoreInvokes("<init>");
+ ignoreInvokes("release");
+
+ registerTarget(AndroidApiLevel.B, 5);
+ }
+
+ @Override
+ protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+ d8TestBuilder.addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true);
+ }
+
+ @Override
+ protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+ super.configureProgram(builder);
+ if (builder.isJvmTestBuilder()) {
+ builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+ } else {
+ builder
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Override
+ protected void configure(D8TestCompileResult builder) throws Exception {
+ if (parameters.isDexRuntime()) {
+ builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+ } else {
+ builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addStrippedOuter(getClass())
+ .apply(this::configureProgram)
+ .run(parameters.getRuntime(), getTestClassName())
+ // Fails when not desugared.
+ .assertFailureWithErrorThatMatches(containsString("close should not be called"));
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(ContentProviderClient.class),
+ DexItemFactory.androidContentContentProviderClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(DrmManagerClient.class),
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaDrm.class), DexItemFactory.androidMediaMediaDrmDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaMetadataRetriever.class),
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(TypedArray.class),
+ DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .clearNest()
+ .transform();
+ }
+
+ public static class TestRunner extends MiniAssert {
+
+ public static void main(String[] args) {
+ contentProviderClient();
+ drmManagerClient();
+ mediaDrm();
+ mediaMetadataRetriever();
+ typedArray();
+ }
+
+ private static void contentProviderClient() {
+ ContentProviderClient contentProviderClient = new ContentProviderClient();
+ MiniAssert.assertFalse(contentProviderClient.wasClosed);
+ // Loop as regression test for b/276874854.
+ for (int i = 0; i < 2; i++) {
+ contentProviderClient.close();
+ MiniAssert.assertTrue(contentProviderClient.wasClosed);
+ }
+ }
+
+ public static void drmManagerClient() {
+ DrmManagerClient drmManagerClient = new DrmManagerClient();
+ MiniAssert.assertFalse(drmManagerClient.wasClosed);
+ drmManagerClient.close();
+ MiniAssert.assertTrue(drmManagerClient.wasClosed);
+ }
+
+ public static void mediaDrm() {
+ MediaDrm mediaDrm = new MediaDrm();
+ MiniAssert.assertFalse(mediaDrm.wasClosed);
+ mediaDrm.close();
+ MiniAssert.assertTrue(mediaDrm.wasClosed);
+ }
+
+ public static void mediaMetadataRetriever() {
+ MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
+ MiniAssert.assertFalse(mediaMetadataRetriever.wasClosed);
+ mediaMetadataRetriever.close();
+ MiniAssert.assertTrue(mediaMetadataRetriever.wasClosed);
+ }
+
+ public static void typedArray() {
+ TypedArray typedArray = new TypedArray();
+ MiniAssert.assertFalse(typedArray.wasClosed);
+ typedArray.close();
+ MiniAssert.assertTrue(typedArray.wasClosed);
+ }
+ }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceSubtypeTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceSubtypeTest.java
new file mode 100644
index 0000000..d9c567c
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceSubtypeTest.java
@@ -0,0 +1,349 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.Assert;
+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 AutoCloseableRetargeterExecutorServiceSubtypeTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntime(CfVm.JDK21)
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+ .build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines("close", "close", "close", "close", "close", "close", "close", "SUCCESS");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+ .setMinApi(parameters)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+ .compile()
+ .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+ .inspect(this::assertCloseMethodsAndTags)
+ .run(
+ parameters.getRuntime(),
+ Main.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void assertCloseMethodsAndTags(CodeInspector inspector) {
+ for (Class<? extends ExecutorService> clazz :
+ ImmutableList.of(
+ PrintForkJoinPool.class,
+ OverrideForkJoinPool.class,
+ Executor1.class,
+ Executor2.class)) {
+ ClassSubject subj = inspector.clazz(clazz);
+ Assert.assertTrue(subj.isPresent());
+ Assert.assertTrue(subj.allMethods().stream().anyMatch(m -> m.getFinalName().equals("close")));
+ Assert.assertTrue(
+ subj.getDexProgramClass()
+ .getInterfaces()
+ .contains(inspector.getFactory().autoCloseableType));
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(Main.class)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addInliningAnnotations()
+ .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+ .setMinApi(parameters)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+ .compile()
+ .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+ .run(
+ parameters.getRuntime(),
+ Main.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ public static class PrintForkJoinPool extends ForkJoinPool {
+
+ public void close() {
+ super.close();
+ System.out.println("close");
+ }
+ }
+
+ public static class OverrideForkJoinPool extends ForkJoinPool {}
+
+ public static class OverrideForkJoinPool2 extends OverrideForkJoinPool {}
+
+ public static class Executor1 implements ExecutorService {
+
+ boolean done = false;
+
+ @Override
+ public void shutdown() {
+ done = true;
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ done = true;
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return done;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return done;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return true;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return null;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+
+ @Override
+ public void execute(Runnable command) {}
+ }
+
+ public static class Executor2 implements ExecutorService {
+
+ boolean done = false;
+
+ @Override
+ public void shutdown() {
+ done = true;
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ done = true;
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return done;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return done;
+ }
+
+ public void close() {
+ ExecutorService.super.close();
+ System.out.println("close");
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return true;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return null;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+
+ @Override
+ public void execute(Runnable command) {}
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ PrintForkJoinPool forkJoinPool = new PrintForkJoinPool();
+ forkJoinPool.close();
+ forkJoinPool = new PrintForkJoinPool();
+ close(forkJoinPool);
+
+ ForkJoinPool forkJoinPool2 = new PrintForkJoinPool();
+ forkJoinPool2.close();
+
+ ExecutorService executorService = new PrintForkJoinPool();
+ executorService.close();
+ executorService = new PrintForkJoinPool();
+ closeExecutorService(executorService);
+
+ OverrideForkJoinPool overrideForkJoinPool = new OverrideForkJoinPool();
+ overrideForkJoinPool.close();
+ overrideForkJoinPool = new OverrideForkJoinPool();
+ close(overrideForkJoinPool);
+
+ ForkJoinPool overrideForkJoinPool1 = new OverrideForkJoinPool();
+ overrideForkJoinPool1.close();
+
+ executorService = new OverrideForkJoinPool();
+ executorService.close();
+ executorService = new OverrideForkJoinPool();
+ closeExecutorService(executorService);
+
+ OverrideForkJoinPool2 overrideForkJoinPool2 = new OverrideForkJoinPool2();
+ overrideForkJoinPool2.close();
+ overrideForkJoinPool2 = new OverrideForkJoinPool2();
+ close(overrideForkJoinPool2);
+
+ ForkJoinPool overrideForkJoinPool22 = new OverrideForkJoinPool2();
+ overrideForkJoinPool22.close();
+
+ executorService = new OverrideForkJoinPool2();
+ executorService.close();
+ executorService = new OverrideForkJoinPool2();
+ closeExecutorService(executorService);
+
+ Executor1 executor1 = new Executor1();
+ executor1.close();
+ Executor2 executor2 = new Executor2();
+ executor2.close();
+ ExecutorService executor11 = new Executor1();
+ close(executor11);
+ ExecutorService executor22 = new Executor2();
+ close(executor22);
+
+ System.out.println("SUCCESS");
+ }
+
+ @NeverInline
+ public static void closeExecutorService(ExecutorService ac) throws Exception {
+ ac.close();
+ }
+
+ @NeverInline
+ public static void close(AutoCloseable ac) throws Exception {
+ ac.close();
+ }
+ }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java
new file mode 100644
index 0000000..ae043fd
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+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 AutoCloseableRetargeterExecutorServiceTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .build();
+ }
+
+ private static String EXPECTED_OUTPUT = StringUtils.lines("false", "true", "SUCCESS");
+ private static String EXPECTED_OUTPUT_24 =
+ StringUtils.lines("false", "true", "false", "false", "SUCCESS");
+
+ private String getExpectedOutput() {
+ return parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)
+ ? EXPECTED_OUTPUT
+ : EXPECTED_OUTPUT_24;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+ .setMinApi(parameters)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+ .compile()
+ .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+ .run(
+ parameters.getRuntime(),
+ Main.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(Main.class)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addInliningAnnotations()
+ .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+ .setMinApi(parameters)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+ .compile()
+ .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+ .run(
+ parameters.getRuntime(),
+ Main.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ int api = Integer.parseInt(args[0]);
+
+ ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);
+ System.out.println(pool.isTerminated());
+ pool.close();
+ System.out.println(pool.isTerminated());
+
+ if (api >= 21) {
+ ForkJoinPool forkJoinPool = new ForkJoinPool();
+ forkJoinPool.close();
+ forkJoinPool = new ForkJoinPool();
+ close(forkJoinPool);
+
+ ExecutorService executorService = new ForkJoinPool();
+ executorService.close();
+ executorService = new ForkJoinPool();
+ closeExecutorService(executorService);
+ }
+
+ if (api >= 24) {
+ ForkJoinPool commonPool = ForkJoinPool.commonPool();
+ System.out.println(commonPool.isTerminated());
+ commonPool.close();
+ // Common pool should never terminate.
+ System.out.println(commonPool.isTerminated());
+ }
+
+ System.out.println("SUCCESS");
+ }
+
+ @NeverInline
+ public static void closeExecutorService(ExecutorService ac) throws Exception {
+ ac.close();
+ }
+
+ @NeverInline
+ public static void close(AutoCloseable ac) throws Exception {
+ ac.close();
+ }
+ }
+}
diff --git a/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java b/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
index c55a8e4..a57d54b 100644
--- a/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
+++ b/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
@@ -4,8 +4,14 @@
package twr;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.desugar.backports.AbstractBackportTest;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -33,6 +39,32 @@
ignoreInvokes("isTerminated");
}
+ @Override
+ protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+ super.configureProgram(builder);
+ if (builder.isJvmTestBuilder()) {
+ builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+ } else {
+ builder
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Override
+ protected void configure(D8TestCompileResult builder) throws Exception {
+ if (parameters.isDexRuntime()) {
+ builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+ } else {
+ builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Override
+ public void testD8Cf() throws Exception {
+ super.testD8Cf();
+ }
+
public static class Main {
public static void main(String[] args) {
diff --git a/src/test/examplesJava21/twr/LookUpCloseResourceTest.java b/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
index 51028fe..64ebfd2 100644
--- a/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
+++ b/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
@@ -78,7 +78,7 @@
for (DexProgramClass clazz : appInfoForMax.classes()) {
OptionalBool contains =
appInfoForMax.implementedInterfaces(clazz.getType()).contains(factory.autoCloseableType);
- if (contains.isTrue() && clazz.getType() != factory.autoCloseableType) {
+ if (contains.isPossiblyTrue() && clazz.getType() != factory.autoCloseableType) {
if (clazz.lookupVirtualMethod(close.withHolder(clazz.getType(), factory)) != null) {
autoCloseableSubclassesWithOverride.add(clazz.getType());
} else {
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index b217907..b3c22ce 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -298,7 +298,7 @@
.allowUnusedProguardConfigurationRules()
// TODO(b/222228826): Disallow unrecognized diagnostics and open interfaces.
.allowDiagnosticMessages()
- .addR8PartialOptionsModification(
+ .addR8PartialR8OptionsModification(
options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces());
}
@@ -409,7 +409,7 @@
FileUtils.readTextFile(dump.getProguardConfigFile()),
"-shrinkunusedprotofields",
""))
- .addR8PartialOptionsModification(
+ .addR8PartialR8OptionsModification(
options -> {
options.apiModelingOptions().androidApiExtensionPackages =
dumpProperties.getAndroidApiExtensionPackages();
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java
index 931a36c..6d86a37 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java
@@ -158,7 +158,7 @@
.allowUnnecessaryDontWarnWildcards()
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
- .addR8PartialOptionsModification(
+ .addR8PartialR8OptionsModification(
options -> options.getOpenClosedInterfacesOptions().disallowOpenInterfaces());
}
diff --git a/src/test/java/com/android/tools/r8/dump/CompilerDumpTest.java b/src/test/java/com/android/tools/r8/dump/CompilerDumpTest.java
index 509f85f..9f4ff3c 100644
--- a/src/test/java/com/android/tools/r8/dump/CompilerDumpTest.java
+++ b/src/test/java/com/android/tools/r8/dump/CompilerDumpTest.java
@@ -3,15 +3,23 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dump;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
+import com.android.tools.r8.R8PartialTestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.internal.CompilationTestBase;
import com.android.tools.r8.utils.DumpInputFlags;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Lists;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -19,7 +27,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class CompilerDumpTest extends TestBase {
+public class CompilerDumpTest extends CompilationTestBase {
@Parameter() public TestParameters parameters;
@@ -78,10 +86,73 @@
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
+ @Test
+ public void testR8Partial() throws Exception {
+ parameters.assumeR8PartialTestParameters();
+ // Create an R8 partial dump.
+ Path dumpDirectory = temp.newFolder().toPath();
+ DumpInputFlags dumpInputFlags = DumpInputFlags.dumpToDirectory(dumpDirectory);
+ R8PartialTestCompileResult compileResult =
+ testForR8Partial(parameters.getBackend())
+ .addR8IncludedClasses(IncludedMain.class)
+ .addR8ExcludedClasses(ExcludedMain.class)
+ .addKeepMainRule(IncludedMain.class)
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters)
+ .addR8PartialOptionsModification(options -> options.setDumpInputFlags(dumpInputFlags))
+ .addR8PartialD8OptionsModification(options -> options.setDumpInputFlags(dumpInputFlags))
+ .addR8PartialR8OptionsModification(options -> options.setDumpInputFlags(dumpInputFlags))
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertInfosMatch(
+ diagnosticMessage(startsWith("Dumped compilation inputs to:")))
+ .assertNoWarnings()
+ .assertNoErrors());
+
+ // Verify that only one archive was dumped.
+ List<Path> dumpArchives = Files.list(dumpDirectory).collect(Collectors.toList());
+ assertEquals(1, dumpArchives.size());
+
+ Path dumpArchive = dumpArchives.iterator().next();
+
+ // Inspect the dump.
+ CompilerDump dump = CompilerDump.fromArchive(dumpArchive, temp.newFolder().toPath());
+ assertEquals(
+ Lists.newArrayList(IncludedMain.class.getTypeName()), dump.getR8PartialIncludePatterns());
+ assertEquals(
+ Lists.newArrayList(ExcludedMain.class.getTypeName()),
+ dump.getR8PartialExcludePatternsOrDefault(null));
+
+ // Compile the dump.
+ testForR8Partial(parameters.getBackend())
+ .applyCompilerDump(dump)
+ .compile()
+ .apply(
+ recompileResult ->
+ assertIdenticalApplications(compileResult.getApp(), recompileResult.getApp()))
+ .run(parameters.getRuntime(), IncludedMain.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
static class TestClass {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
+
+ static class IncludedMain {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+
+ static class ExcludedMain {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java
index 3d5a297..c0382b0 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java
@@ -1,11 +1,21 @@
package com.android.tools.r8.ir.desugar.backports;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
public class ExecutorServiceMethods {
+ // Stub out android.os.Build$VERSION as it does not exist when building R8.
+ private static class AndroidOsBuildVersionStub {
+ public static int SDK_INT;
+ }
public static void closeExecutorService(ExecutorService executorService) {
+ if (AndroidOsBuildVersionStub.SDK_INT > 23) {
+ if (executorService == ForkJoinPool.commonPool()) {
+ return;
+ }
+ }
boolean terminated = executorService.isTerminated();
if (!terminated) {
executorService.shutdown();
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 96dd2e3..40d6b6d 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -169,7 +169,13 @@
private static CfInstruction rewriteToAndroidOsBuildVersion(
DexItemFactory itemFactory, CfInstruction instruction) {
// Rewrite references to UnsafeStub to sun.misc.Unsafe.
- if (instruction.isStaticFieldGet()) {
+ if (instruction.isStaticFieldGet()
+ && instruction
+ .asFieldInstruction()
+ .getField()
+ .getHolderType()
+ .toString()
+ .contains("Stub")) {
CfStaticFieldRead fieldGet = instruction.asStaticFieldGet();
return new CfStaticFieldRead(
itemFactory.createField(
@@ -182,7 +188,10 @@
.asFrame()
.mapReferenceTypes(
type -> {
- throw new RuntimeException("Unexpected CfFrame instruction.");
+ if (type.toString().contains("Stub")) {
+ throw new RuntimeException("Unexpected CfFrame instruction.");
+ }
+ return type;
});
}
return instruction;
@@ -206,6 +215,12 @@
.map(instruction -> rewriteToUnsafe(factory, instruction))
.collect(Collectors.toList()));
}
+ if (holderName.equals("ExecutorServiceMethods") && methodName.equals("closeExecutorService")) {
+ code.setInstructions(
+ code.getInstructions().stream()
+ .map(instruction -> rewriteToAndroidOsBuildVersion(factory, instruction))
+ .collect(Collectors.toList()));
+ }
if (holderName.equals("AndroidOsBuildVersionMethods") && methodName.equals("getSdkIntFull")) {
code.setInstructions(
code.getInstructions().stream()
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationArtProfileTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationArtProfileTest.java
new file mode 100644
index 0000000..9af4f97
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationArtProfileTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2025, 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.partial;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.partial.PartialCompilationWithDefaultInterfaceMethodTest.Main;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 PartialCompilationArtProfileTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+ .build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8Partial(parameters.getBackend())
+ .addR8IncludedClasses(IncludedInterface.class)
+ .addR8ExcludedClasses(Main.class, ExcludedInterface.class)
+ .addArtProfileForRewriting(
+ ExternalArtProfile.builder()
+ .addMethodRule(
+ Reference.methodFromMethod(IncludedInterface.class.getDeclaredMethod("foo")))
+ .addMethodRule(
+ Reference.methodFromMethod(ExcludedInterface.class.getDeclaredMethod("bar")))
+ .build())
+ .setMinApi(parameters)
+ .compile()
+ .inspectResidualArtProfile(
+ (artProfileInspector, codeInspector) -> {
+ ClassSubject includedInterfaceClass, excludedInterfaceClass;
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ includedInterfaceClass = codeInspector.clazz(IncludedInterface.class);
+ excludedInterfaceClass = codeInspector.clazz(ExcludedInterface.class);
+ } else {
+ includedInterfaceClass =
+ codeInspector.clazz(
+ SyntheticItemsTestUtils.syntheticCompanionClass(IncludedInterface.class));
+ excludedInterfaceClass =
+ codeInspector.clazz(
+ SyntheticItemsTestUtils.syntheticCompanionClass(ExcludedInterface.class));
+ }
+ artProfileInspector
+ .assertContainsClassRules(includedInterfaceClass, excludedInterfaceClass)
+ .assertContainsMethodRules(
+ includedInterfaceClass.uniqueMethodWithOriginalName("foo"),
+ excludedInterfaceClass.uniqueMethodWithOriginalName("bar"))
+ .assertContainsNoOtherRules();
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("IncludedInterface.foo()", "ExcludedInterface.bar()");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ IncludedInterface.foo();
+ ExcludedInterface.bar();
+ }
+ }
+
+ interface IncludedInterface {
+
+ static void foo() {
+ System.out.println("IncludedInterface.foo()");
+ }
+ }
+
+ interface ExcludedInterface {
+
+ static void bar() {
+ System.out.println("ExcludedInterface.bar()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicPreviewPatternsTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicPreviewPatternsTest.java
index de63504..5daa8d1 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicPreviewPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicPreviewPatternsTest.java
@@ -138,10 +138,7 @@
.addProgramClasses(ALL_CLASSES)
.setR8PartialConfiguration(
builder ->
- builder
- .addJavaTypeIncludePattern(PKG1 + ".**")
- .addJavaTypeExcludePattern(PKG1 + ".A1")
- .addJavaTypeExcludePattern(PKG1 + ".A2"))
+ builder.addJavaTypeIncludePattern(PKG1 + ".**").excludeClasses(A1.class, A2.class))
.compile()
.inspect(
inspector ->
@@ -163,7 +160,7 @@
builder
.addJavaTypeIncludePattern(PKG1 + ".**")
.addJavaTypeIncludePattern(PKG2 + ".**")
- .addJavaTypeExcludePattern(PKG2 + ".C1"))
+ .excludeClasses(C1.class))
.compile()
.inspect(inspector -> assertTrue(inspector.hasExactlyProgramClasses(C1.class, Main.class)))
.run(parameters.getRuntime(), Main.class, ALL_TYPE_NAMES)
@@ -186,7 +183,7 @@
.addJavaTypeIncludePattern(PKG1 + ".*")
.addJavaTypeIncludePattern(SUBPKG + ".*")
.addJavaTypeIncludePattern(PKG2 + ".*")
- .addJavaTypeExcludePattern(PKG1 + ".A1"))
+ .excludeClasses(A1.class))
.compile()
.inspect(inspector -> assertTrue(inspector.hasExactlyProgramClasses(A1.class, Main.class)))
.run(parameters.getRuntime(), Main.class, ALL_TYPE_NAMES)
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationD8LineNumberTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationD8LineNumberTest.java
new file mode 100644
index 0000000..57a67db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationD8LineNumberTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2025, 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.partial;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.R8PartialTestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 PartialCompilationD8LineNumberTest extends TestBase {
+
+ private static StackTrace expectedD8StackTrace;
+ private static StackTrace expectedR8StackTrace;
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @BeforeClass
+ public static void setup() throws Exception {
+ // Get the expected stack traces by running on the JVM.
+ expectedD8StackTrace =
+ testForJvm(getStaticTemp())
+ .addTestClasspath()
+ .run(CfRuntime.getSystemRuntime(), ExcludedClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .map(StackTrace::extractFromJvm);
+ expectedR8StackTrace =
+ testForJvm(getStaticTemp())
+ .addTestClasspath()
+ .run(CfRuntime.getSystemRuntime(), IncludedClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .map(StackTrace::extractFromJvm);
+ }
+
+ @Test
+ public void test() throws Throwable {
+ parameters.assumeR8PartialTestParameters();
+ R8PartialTestCompileResult compileResult =
+ testForR8Partial(parameters.getBackend())
+ .addR8IncludedClasses(IncludedClass.class)
+ .addR8ExcludedClasses(ExcludedClass.class)
+ .addKeepMainRule(IncludedClass.class)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject includedClass = inspector.clazz(IncludedClass.class);
+ assertThat(includedClass, isPresent());
+ assertEquals(
+ "SourceFile", includedClass.getDexProgramClass().getSourceFile().toString());
+
+ ClassSubject excludedClass = inspector.clazz(ExcludedClass.class);
+ assertThat(excludedClass, isPresent());
+ assertEquals(
+ "PartialCompilationD8LineNumberTest.java",
+ excludedClass.getDexProgramClass().getSourceFile().toString());
+ })
+ .inspectProguardMap(
+ map -> {
+ assertThat(map, containsString(IncludedClass.class.getTypeName()));
+ assertThat(map, not(containsString(ExcludedClass.class.getTypeName())));
+ });
+
+ compileResult
+ .run(parameters.getRuntime(), ExcludedClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .inspectOriginalStackTrace(
+ stackTrace -> assertThat(stackTrace, isSame(expectedD8StackTrace)));
+
+ compileResult
+ .run(parameters.getRuntime(), IncludedClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .inspectOriginalStackTrace(
+ stackTrace -> assertThat(stackTrace, not(isSame(expectedD8StackTrace))))
+ .inspectStackTrace(stackTrace -> assertThat(stackTrace, isSame(expectedR8StackTrace)));
+ }
+
+ static class IncludedClass {
+
+ public static void main(String[] args) {
+ foo();
+ }
+
+ private static void foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ static class ExcludedClass {
+
+ public static void main(String[] args) {
+ foo();
+ }
+
+ private static void foo() {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
index c7dc4dc..860837a 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
@@ -37,7 +37,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.function.Predicate;
+import java.util.function.Consumer;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -146,28 +146,33 @@
Path tempDir = temp.newFolder().toPath();
// Different sets of namespaces to shrink.
- ImmutableMap<String, Predicate<String>> splits =
+ ImmutableMap<String, Consumer<R8PartialCompilationConfiguration.Builder>> splits =
ImmutableMap.of(
- "androidx", name -> name.startsWith("androidx."),
+ "androidx", configuration -> configuration.addJavaTypeIncludePattern("androidx.**"),
"androidx_kotlin_and_kotlinx",
- name ->
- name.startsWith("androidx.")
- || name.startsWith("kotlin.")
- || name.startsWith("kotlinx."),
+ configuration ->
+ configuration
+ .addJavaTypeIncludePattern("androidx.**")
+ .addJavaTypeIncludePattern("kotlin.**")
+ .addJavaTypeIncludePattern("kotlinx.**"),
"more_libraries",
- name ->
- name.startsWith("androidx.")
- || name.startsWith("kotlin.")
- || name.startsWith("kotlinx.")
- || name.startsWith("android.support.")
- || name.startsWith("io.ktor.")
- || name.startsWith("com.google.android.gms.")
- || name.startsWith("com.google.firebase."),
- "all_but_app namespace", name -> !name.startsWith(appNamespace + "."));
+ configuration ->
+ configuration
+ .addJavaTypeIncludePattern("androidx.**")
+ .addJavaTypeIncludePattern("kotlin.**")
+ .addJavaTypeIncludePattern("kotlinx.**")
+ .addJavaTypeIncludePattern("android.support.**")
+ .addJavaTypeIncludePattern("io.ktor.**")
+ .addJavaTypeIncludePattern("com.google.android.gms.**")
+ .addJavaTypeIncludePattern("com.google.firebase.**"),
+ "all_but_app_namespace",
+ configuration ->
+ configuration.includeAll().addJavaTypeExcludePattern(appNamespace + ".**"));
// Compile with each set of namespaces to shrink and collect DEX size.
Map<String, Pair<Long, Long>> dexSize = new LinkedHashMap<>();
- for (Entry<String, Predicate<String>> entry : splits.entrySet()) {
+ for (Entry<String, Consumer<R8PartialCompilationConfiguration.Builder>> entry :
+ splits.entrySet()) {
long size = runR8PartialAndL8(tempDir, dump, entry.getKey(), entry.getValue());
dexSize.put(entry.getKey(), new Pair<>(size, 0L));
}
@@ -205,11 +210,15 @@
}
private long runR8PartialAndL8(
- Path tempDir, CompilerDump dump, String name, Predicate<String> isR8) throws Exception {
+ Path tempDir,
+ CompilerDump dump,
+ String name,
+ Consumer<R8PartialCompilationConfiguration.Builder> partialConfiguration)
+ throws Exception {
Path tmp = tempDir.resolve(name);
Files.createDirectory(tmp);
Path output = tmp.resolve("tivix8.zip");
- runR8Partial(tempDir, dump, output, isR8);
+ runR8Partial(tempDir, dump, output, partialConfiguration);
Path l8Output = tmp.resolve("tivix8l8.zip");
runL8(tmp, dump, output, l8Output);
Box<Long> size = new Box<>(0L);
@@ -226,14 +235,16 @@
return size.get();
}
- private void runR8Partial(Path tempDir, CompilerDump dump, Path output, Predicate<String> isR8)
+ private void runR8Partial(
+ Path tempDir,
+ CompilerDump dump,
+ Path output,
+ Consumer<R8PartialCompilationConfiguration.Builder> partialConfiguration)
throws IOException, CompilationFailedException {
testForR8Partial(parameters.getBackend())
- .setR8PartialConfigurationJavaTypePredicate(isR8)
+ .setR8PartialConfiguration(partialConfiguration)
.addOptionsModification(
options -> {
- options.partialCompilationConfiguration.setTempDir(tempDir);
-
// For compiling nowonandroid.
options.testing.allowUnnecessaryDontWarnWildcards = true;
options.testing.allowUnusedDontWarnRules = true;
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationIfD8ClassPresentTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationIfD8ClassPresentTest.java
new file mode 100644
index 0000000..aae826f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationIfD8ClassPresentTest.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2025, 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.partial;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartialCompilationIfD8ClassPresentTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ parameters.assumeR8PartialTestParameters();
+ testForR8Partial(parameters.getBackend())
+ .addR8IncludedClasses(Main.class)
+ .addR8ExcludedClasses(ExcludedClass.class)
+ .addKeepRules(
+ "-if class **Excluded** { public static void foo(); }",
+ "-keep class " + Main.class.getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ ExcludedClass.foo();
+ }
+ }
+
+ static class ExcludedClass {
+
+ public static void foo() {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationKeepAllowObfuscationBoundaryTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationKeepAllowObfuscationBoundaryTest.java
new file mode 100644
index 0000000..30bafec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationKeepAllowObfuscationBoundaryTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2025, 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.partial;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.MethodSubject;
+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 PartialCompilationKeepAllowObfuscationBoundaryTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ parameters.assumeR8PartialTestParameters();
+ testForR8Partial(parameters.getBackend())
+ .addR8IncludedClasses(IncludedClass.class)
+ .addR8ExcludedClasses(Main.class)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject includedClassSubject = inspector.clazz(IncludedClass.class);
+ assertThat(includedClassSubject, isPresentAndRenamed());
+
+ MethodSubject fooMethodSubject =
+ includedClassSubject.uniqueMethodWithOriginalName("foo");
+ assertThat(fooMethodSubject, isPresentAndRenamed());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ IncludedClass.foo();
+ }
+ }
+
+ static class IncludedClass {
+
+ static void foo() {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationStartupProfileTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationStartupProfileTest.java
new file mode 100644
index 0000000..aa101c2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationStartupProfileTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2025, 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.partial;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.StartupClassesNonStartupFractionDiagnostic;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+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 PartialCompilationStartupProfileTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+ .build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8Partial(parameters.getBackend())
+ .addR8IncludedClasses(IncludedInterface.class)
+ .addR8ExcludedClasses(Main.class, ExcludedInterface.class)
+ .allowDiagnosticInfoMessages()
+ .apply(
+ testBuilder -> StartupTestingUtils.addStartupProfile(testBuilder, getStartupProfile()))
+ .setMinApi(parameters)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertInfosMatch(
+ diagnosticType(StartupClassesNonStartupFractionDiagnostic.class))
+ .assertNoWarnings()
+ .assertNoErrors())
+ .inspectMultiDex(
+ primaryDexInspector -> {
+ assertEquals(2, primaryDexInspector.allClasses().size());
+ ClassSubject includedInterfaceClass, excludedInterfaceClass;
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ includedInterfaceClass = primaryDexInspector.clazz(IncludedInterface.class);
+ excludedInterfaceClass = primaryDexInspector.clazz(ExcludedInterface.class);
+ } else {
+ includedInterfaceClass =
+ primaryDexInspector.clazz(
+ SyntheticItemsTestUtils.syntheticCompanionClass(IncludedInterface.class));
+ excludedInterfaceClass =
+ primaryDexInspector.clazz(
+ SyntheticItemsTestUtils.syntheticCompanionClass(ExcludedInterface.class));
+ }
+ assertThat(includedInterfaceClass, isPresent());
+ assertThat(excludedInterfaceClass, isPresent());
+ },
+ secondaryDexInspector -> {
+ assertEquals(
+ parameters.canUseDefaultAndStaticInterfaceMethods() ? 1 : 2,
+ secondaryDexInspector.allClasses().size());
+ assertThat(secondaryDexInspector.clazz(Main.class), isPresent());
+ assertThat(
+ secondaryDexInspector.clazz(ExcludedInterface.class),
+ isAbsentIf(parameters.canUseDefaultAndStaticInterfaceMethods()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("IncludedInterface.foo()", "ExcludedInterface.bar()");
+ }
+
+ private static List<ExternalStartupMethod> getStartupProfile() throws NoSuchMethodException {
+ return ImmutableList.of(
+ ExternalStartupMethod.builder()
+ .setMethodReference(
+ Reference.methodFromMethod(IncludedInterface.class.getDeclaredMethod("foo")))
+ .build(),
+ ExternalStartupMethod.builder()
+ .setMethodReference(
+ Reference.methodFromMethod(ExcludedInterface.class.getDeclaredMethod("bar")))
+ .build());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ IncludedInterface.foo();
+ ExcludedInterface.bar();
+ }
+ }
+
+ interface IncludedInterface {
+
+ static void foo() {
+ System.out.println("IncludedInterface.foo()");
+ }
+ }
+
+ interface ExcludedInterface {
+
+ static void bar() {
+ System.out.println("ExcludedInterface.bar()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
index 4159691..aacdc98 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.partial;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.DEFAULT_METHOD_PREFIX;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -61,7 +62,8 @@
assertThat(jCompanionClassSubject, isPresent());
MethodSubject jCompanionMethodSubject =
- jCompanionClassSubject.uniqueMethodWithOriginalName("m");
+ jCompanionClassSubject.uniqueMethodWithOriginalName(
+ DEFAULT_METHOD_PREFIX + "m");
assertThat(jCompanionMethodSubject, isPresent());
ClassSubject aClassSubject = inspector.clazz(A.class);
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index 2670bee..5eead4f 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -162,7 +162,7 @@
public static void addStartupProfile(
TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder,
- Collection<ExternalStartupItem> startupItems) {
+ Collection<? extends ExternalStartupItem> startupItems) {
StartupProfileProvider startupProfileProvider =
new StartupProfileProvider() {
@Override
@@ -188,9 +188,12 @@
};
if (testBuilder.isD8TestBuilder()) {
testBuilder.asD8TestBuilder().addStartupProfileProviders(startupProfileProvider);
- } else {
+ } else if (testBuilder.isR8TestBuilder()) {
assertTrue(testBuilder.isR8TestBuilder());
testBuilder.asR8TestBuilder().addStartupProfileProviders(startupProfileProvider);
+ } else {
+ assertTrue(testBuilder.isR8PartialTestBuilder());
+ testBuilder.asR8PartialTestBuilder().addStartupProfileProviders(startupProfileProvider);
}
}
diff --git a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
index da2771f..25d4fab 100644
--- a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
@@ -46,6 +46,11 @@
}
@Override
+ public boolean isJvmTestBuilder() {
+ return true;
+ }
+
+ @Override
JvmTestBuilder self() {
return this;
}
diff --git a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
index 7ac85b1..f32f03c 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
@@ -6,17 +6,18 @@
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.dump.CompilerDump;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.partial.R8PartialCompilationConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
-import java.util.function.Predicate;
import java.util.function.Supplier;
public class R8PartialTestBuilder
@@ -66,15 +67,6 @@
return this;
}
- public R8PartialTestBuilder setR8PartialConfigurationJavaTypePredicate(
- Predicate<String> include) {
- assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
- : "Overwriting configuration...?";
- r8PartialConfiguration =
- R8PartialCompilationConfiguration.builder().includeJavaType(include).build();
- return self();
- }
-
public R8PartialTestBuilder setR8PartialConfiguration(
Consumer<R8PartialCompilationConfiguration.Builder> consumer) {
assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
@@ -163,14 +155,20 @@
+ "Did you mean addD8PartialOptionsModification or addR8PartialOptionsModification?");
}
- public R8PartialTestBuilder addD8PartialOptionsModification(Consumer<InternalOptions> consumer) {
+ public R8PartialTestBuilder addR8PartialOptionsModification(Consumer<InternalOptions> consumer) {
+ return super.addOptionsModification(consumer);
+ }
+
+ public R8PartialTestBuilder addR8PartialD8OptionsModification(
+ Consumer<InternalOptions> consumer) {
return super.addOptionsModification(
options ->
options.partialCompilationConfiguration.d8DexOptionsConsumer =
options.partialCompilationConfiguration.d8DexOptionsConsumer.andThen(consumer));
}
- public R8PartialTestBuilder addR8PartialOptionsModification(Consumer<InternalOptions> consumer) {
+ public R8PartialTestBuilder addR8PartialR8OptionsModification(
+ Consumer<InternalOptions> consumer) {
return super.addOptionsModification(
options ->
options.partialCompilationConfiguration.r8OptionsConsumer =
@@ -178,25 +176,39 @@
}
public R8PartialTestBuilder addGlobalOptionsModification(Consumer<InternalOptions> consumer) {
- return addD8PartialOptionsModification(consumer)
- .addR8PartialOptionsModification(consumer);
+ return addR8PartialD8OptionsModification(consumer).addR8PartialR8OptionsModification(consumer);
}
@Override
public R8PartialTestBuilder allowUnnecessaryDontWarnWildcards() {
- return addR8PartialOptionsModification(
+ return addR8PartialR8OptionsModification(
options -> options.getTestingOptions().allowUnnecessaryDontWarnWildcards = true);
}
@Override
public R8PartialTestBuilder allowUnusedDontWarnPatterns() {
- return addR8PartialOptionsModification(
+ return addR8PartialR8OptionsModification(
options -> options.getTestingOptions().allowUnusedDontWarnRules = true);
}
@Override
+ public R8PartialTestBuilder applyCompilerDump(CompilerDump dump) throws IOException {
+ List<String> includePatterns = dump.getR8PartialIncludePatterns();
+ if (includePatterns != null) {
+ List<String> excludePatterns =
+ dump.getR8PartialExcludePatternsOrDefault(Collections.emptyList());
+ setR8PartialConfiguration(
+ configuration -> {
+ includePatterns.forEach(configuration::addJavaTypeIncludePattern);
+ excludePatterns.forEach(configuration::addJavaTypeExcludePattern);
+ });
+ }
+ return super.applyCompilerDump(dump);
+ }
+
+ @Override
public R8PartialTestBuilder enableExperimentalKeepAnnotations() {
- return addR8PartialOptionsModification(
+ return addR8PartialR8OptionsModification(
o -> o.getTestingOptions().enableEmbeddedKeepAnnotations = true)
.addKeepAnnoLibToClasspath();
}
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
index ea40b20..294f719 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
@@ -96,6 +96,10 @@
return self;
}
+ public boolean isJvmTestBuilder() {
+ return false;
+ }
+
@Deprecated
public RR run(String mainClass)
throws CompilationFailedException, ExecutionException, IOException {
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
index b017214..b5c886d 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -71,7 +71,6 @@
options -> {
options.testing.allowUnusedDontWarnRules = false;
options.testing.allowUnnecessaryDontWarnWildcards = false;
- options.testing.listIterationRewritingEnabled = true;
options.horizontalClassMergerOptions().enable();
options.horizontalClassMergerOptions().setEnableInterfaceMerging();
options.inlinerOptions().enableConstructorInliningWithFinalFields = true;
diff --git a/src/test/testbase/java/com/android/tools/r8/TestParameters.java b/src/test/testbase/java/com/android/tools/r8/TestParameters.java
index ab3077c..ef797e2 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestParameters.java
@@ -287,6 +287,15 @@
return (isDexRuntime() || representativeApiLevelForRuntime) && !isNoneRuntime();
}
+ public TestParameters assumeR8PartialTestParameters() {
+ assumeTrue(isR8PartialTestParameters());
+ return this;
+ }
+
+ public boolean isR8PartialTestParameters() {
+ return isDexRuntime() && apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L);
+ }
+
public TestParameters assumeRuntimeTestParameters() {
assertFalse(
"No need to use assumeRuntimeTestParameters() when not using api levels for CF",
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java b/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java
new file mode 100644
index 0000000..ec7aa63
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java
@@ -0,0 +1,240 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class AutoCloseableAndroidLibraryFileData extends TestBase {
+
+ public static Path compileAutoCloseableAndroidLibraryClasses(
+ TestBase testBase, TestParameters parameters) throws Exception {
+ return testBase
+ .testForD8()
+ .addProgramClassFileData(getAutoCloseableAndroidClassData(parameters))
+ .setMinApi(parameters)
+ .compile()
+ .writeToZip();
+ }
+
+ public static ImmutableList<byte[]> getAutoCloseableAndroidClassData(TestParameters parameters)
+ throws Exception {
+ return ImmutableList.of(
+ getMediaDrm(parameters),
+ getContentProviderClient(parameters),
+ getDrmManagerClient(parameters),
+ getMediaMetadataRetriever(parameters),
+ getTypedArray(parameters),
+ getTransformedBuildVERSIONClass());
+ }
+
+ private static byte[] getContentProviderClient(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.N,
+ ContentProviderClientApiLevel24.class,
+ ContentProviderClient.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString);
+ }
+
+ private static byte[] getDrmManagerClient(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.N,
+ DrmManagerClientApiLevel24.class,
+ DrmManagerClient.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString);
+ }
+
+ private static byte[] getMediaDrm(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.P,
+ MediaDrmApiLevel28.class,
+ MediaDrm.class,
+ DexItemFactory.androidMediaMediaDrmDescriptorString);
+ }
+
+ private static byte[] getMediaMetadataRetriever(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.Q,
+ MediaMetadataRetrieverApiLevel29.class,
+ MediaMetadataRetriever.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString);
+ }
+
+ private static byte[] getTypedArray(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.S,
+ TypedArrayAndroidApiLevel31.class,
+ TypedArray.class,
+ DexItemFactory.androidContentResTypedArrayDescriptorString);
+ }
+
+ private static byte[] getTransformedClass(
+ TestParameters parameters,
+ AndroidApiLevel minApiLevel,
+ Class<?> lowApiClass,
+ Class<?> highApiClass,
+ String descriptorString)
+ throws IOException {
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(minApiLevel)) {
+ return transformer(lowApiClass).setClassDescriptor(descriptorString).clearNest().transform();
+ } else {
+ return transformer(highApiClass).setClassDescriptor(descriptorString).clearNest().transform();
+ }
+ }
+
+ private static byte[] getTransformedBuildVERSIONClass() throws IOException, NoSuchFieldException {
+ return transformer(VERSION.class)
+ .setClassDescriptor("Landroid/os/Build$VERSION;")
+ .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+ .transform();
+ }
+
+ // Minimal android.os.Build$VERSION for runtime classpath.
+ public static class /*android.os.Build$*/ VERSION {
+
+ public static /*final*/ int SDK_INT = -1;
+ }
+
+ public static class ContentProviderClient {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public boolean release() {
+ wasClosed = true;
+ return wasClosed;
+ }
+ }
+
+ public static class ContentProviderClientApiLevel24 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public boolean release() {
+ throw new AssertionError("release should not be called");
+ }
+ }
+
+ public static class DrmManagerClient {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public void release() {
+ wasClosed = true;
+ }
+ }
+
+ public static class DrmManagerClientApiLevel24 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void release() {
+ throw new AssertionError("release should not be called");
+ }
+ }
+
+ public static class MediaDrm {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public void release() {
+ wasClosed = true;
+ }
+ }
+
+ public static class MediaDrmApiLevel28 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void release() {
+ throw new AssertionError("release should not be called");
+ }
+ }
+
+ public static class MediaMetadataRetriever {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public void release() {
+ wasClosed = true;
+ }
+ }
+
+ public static class MediaMetadataRetrieverApiLevel29 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void release() {
+ throw new AssertionError("release should not be called");
+ }
+ }
+
+ public static class TypedArray {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public void recycle() {
+ wasClosed = true;
+ }
+ }
+
+ public static class TypedArrayAndroidApiLevel31 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void recycle() {
+ throw new AssertionError("recycle should not be called");
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 8eebd4a..cdbf02f 100644
--- a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -189,7 +189,7 @@
ignoredInvokes.add(methodName);
}
- protected void configureProgram(TestBuilder<?, ?> builder) throws IOException {
+ protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
builder.addProgramClasses(MiniAssert.class, IgnoreInvokes.class);
if (testClass != null) {
testClass.addAsProgramClass(builder);
@@ -198,6 +198,10 @@
}
}
+ protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+ // Intentionally empty.
+ }
+
@Test
public void testJvm() throws Exception {
parameters.assumeJvmTestParameters();
@@ -233,6 +237,7 @@
testForD8()
.setMinApi(parameters)
.apply(this::configureProgram)
+ .apply(this::configureD8Options)
.setIncludeClassesChecksum(true)
.compileWithExpectedDiagnostics(this::checkDiagnostics)
.apply(this::configure)
@@ -256,8 +261,10 @@
testForD8(Backend.CF)
.setMinApi(parameters)
.apply(this::configureProgram)
+ .apply(this::configureD8Options)
.setIncludeClassesChecksum(true)
.compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .apply(this::configure)
.run(parameters.getRuntime(), testClassName)
.assertSuccess()
.inspect(this::assertDesugaring);
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 2d63c57..8ed1bcc 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -262,6 +262,12 @@
def config_file(self):
return self.if_exists('proguard.config')
+ def r8_include_file(self):
+ return self.if_exists('r8-include.txt')
+
+ def r8_exclude_file(self):
+ return self.if_exists('r8-exclude.txt')
+
def version_file(self):
return self.if_exists('r8-version')
@@ -327,8 +333,10 @@
" No value for 'force-proguard-compatibility'.")
if build_properties.get(
'force-proguard-compatibility').lower() == 'false':
- compiler = compiler + 'full'
- if compiler == 'TraceReferences':
+ compiler = 'r8full'
+ elif compiler == 'r8partial':
+ compiler = 'r8full'
+ elif compiler == 'TraceReferences':
compiler = build_properties.get('tool').lower()
if compiler not in compilers:
error("Unable to determine a compiler to use. Specified %s,"
@@ -652,11 +660,20 @@
cmd.append('com.android.tools.r8.tracereferences.TraceReferences')
cmd.extend(
determine_trace_references_commands(build_properties, out))
- if compiler.startswith('r8'):
+ if is_r8_compiler('r8'):
prepare_r8_wrapper(jar, temp, jdkhome)
cmd.append('com.android.tools.r8.utils.CompileDumpCompatR8')
- if compiler == 'r8':
- cmd.append('--compat')
+ if compiler == 'r8':
+ cmd.append('--compat')
+ elif compiler == 'r8full':
+ r8_partial_include_file = dump.r8_include_file()
+ if r8_partial_include_file:
+ cmd.append('--partial-include')
+ cmd.append(r8_partial_include_file)
+ r8_partial_exclude_file = dump.r8_exclude_file()
+ if r8_partial_exclude_file:
+ cmd.append('--partial-exclude')
+ cmd.append(r8_partial_exclude_file)
if compiler != 'tracereferences':
assert mode == 'debug' or mode == 'release'
cmd.append('--' + mode)