Merge commit 'f946160c75ad612b5f34af1dd5129e4799967d19' into dev-release Change-Id: I421ef94f52515c9b4a2fd3f0ca702815e73c0946
diff --git a/src/blastradius/proto/blastradius.proto b/src/blastradius/proto/blastradius.proto index ed3d8cd..e1b14da 100644 --- a/src/blastradius/proto/blastradius.proto +++ b/src/blastradius/proto/blastradius.proto
@@ -42,6 +42,17 @@ repeated uint32 method_blast_radius = 4; } +// The ids of GlobalKeepRuleBlastRadius and KeepRuleBlastRadius are +// non-overlapping by design, so that a single uint32 id can be used to +// unambiguously reference a GlobalKeepRuleBlastRadius or KeepRuleBlastRadius. +message GlobalKeepRuleBlastRadius { + uint32 id = 1; + // Textual representation of the keep rule. + string source = 2; + // Where this rule comes from. + TextFileOrigin origin = 3; +} + // Information about the constraints of a single keep annotation or rule, // i.e., what is prohibited by it. // @@ -207,7 +218,8 @@ // Keep specifications. repeated KeepConstraints keep_constraints_table = 12; repeated KeepRuleBlastRadius keep_rule_blast_radius_table = 13; + repeated GlobalKeepRuleBlastRadius global_keep_rule_blast_radius_table = 14; // Build info. - BuildInfo build_info = 14; + BuildInfo build_info = 15; }
diff --git a/src/blastradius/web/blastradius.proto b/src/blastradius/web/blastradius.proto index ed3d8cd..e1b14da 100644 --- a/src/blastradius/web/blastradius.proto +++ b/src/blastradius/web/blastradius.proto
@@ -42,6 +42,17 @@ repeated uint32 method_blast_radius = 4; } +// The ids of GlobalKeepRuleBlastRadius and KeepRuleBlastRadius are +// non-overlapping by design, so that a single uint32 id can be used to +// unambiguously reference a GlobalKeepRuleBlastRadius or KeepRuleBlastRadius. +message GlobalKeepRuleBlastRadius { + uint32 id = 1; + // Textual representation of the keep rule. + string source = 2; + // Where this rule comes from. + TextFileOrigin origin = 3; +} + // Information about the constraints of a single keep annotation or rule, // i.e., what is prohibited by it. // @@ -207,7 +218,8 @@ // Keep specifications. repeated KeepConstraints keep_constraints_table = 12; repeated KeepRuleBlastRadius keep_rule_blast_radius_table = 13; + repeated GlobalKeepRuleBlastRadius global_keep_rule_blast_radius_table = 14; // Build info. - BuildInfo build_info = 14; + BuildInfo build_info = 15; }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 3d0cf86..116af49 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -287,15 +287,14 @@ timing.end(); try { AppView<AppInfoWithClassHierarchy> appView; - List<KeepDeclaration> keepDeclarations; { timing.begin("Read app"); ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing); DirectMappedDexApplication application = applicationReader.readDirect(executorService); - keepDeclarations = - options.partialSubCompilationConfiguration != null - ? options.partialSubCompilationConfiguration.asR8().getAndClearKeepDeclarations() - : application.getKeepDeclarations(); + if (options.partialSubCompilationConfiguration != null) { + application.setKeepDeclarations( + options.partialSubCompilationConfiguration.asR8().getAndClearKeepDeclarations()); + } timing.end(); options .getLibraryDesugaringOptions() @@ -316,6 +315,7 @@ timing.begin("Register references and more setup"); assert ArtProfileCompletenessChecker.verify(appView); + List<KeepDeclaration> keepDeclarations = appView.app().asDirect().getKeepDeclarations(); readKeepSpecifications(appView, keepDeclarations); // Check for potentially having pass-through of Cf-code for kotlin libraries.
diff --git a/src/main/java/com/android/tools/r8/blastradius/RootSetBlastRadiusSerializer.java b/src/main/java/com/android/tools/r8/blastradius/RootSetBlastRadiusSerializer.java index e826cae..06d0c2a 100644 --- a/src/main/java/com/android/tools/r8/blastradius/RootSetBlastRadiusSerializer.java +++ b/src/main/java/com/android/tools/r8/blastradius/RootSetBlastRadiusSerializer.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.blastradius.proto.BuildInfo; import com.android.tools.r8.blastradius.proto.FieldReference; import com.android.tools.r8.blastradius.proto.FileOrigin; +import com.android.tools.r8.blastradius.proto.GlobalKeepRuleBlastRadius; import com.android.tools.r8.blastradius.proto.KeepConstraint; import com.android.tools.r8.blastradius.proto.KeepConstraints; import com.android.tools.r8.blastradius.proto.KeepRuleBlastRadius; @@ -30,9 +31,12 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.DexTypeList; import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; import com.android.tools.r8.position.TextRange; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.EnqueuerResult; +import com.android.tools.r8.shaking.GlobalConfigurationRule; +import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.ProguardKeepRuleBase; import com.android.tools.r8.shaking.ProguardKeepRuleModifiers; import com.android.tools.r8.utils.ArrayUtils; @@ -101,6 +105,7 @@ .setSource(blastRadiusForRule.getSource()); container.addKeepRuleBlastRadiusTable(ruleProto); } + serializeGlobalKeepRuleBlastRadii(ruleIds.size()); keptClassInfos.values().forEach(container::addKeptClassInfoTable); keptFieldInfos.values().forEach(container::addKeptFieldInfoTable); keptMethodInfos.values().forEach(container::addKeptMethodInfoTable); @@ -226,6 +231,23 @@ }); } + private void serializeGlobalKeepRuleBlastRadii(int nextRuleId) { + ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration(); + if (proguardConfiguration == null) { + return; + } + // The iteration over the global rules should have deterministic iteration order since the + // global rules lists are populated in-order by the ProguardConfigurationParser. + for (GlobalConfigurationRule rule : proguardConfiguration.getGlobalRules()) { + container.addGlobalKeepRuleBlastRadiusTable( + GlobalKeepRuleBlastRadius.newBuilder() + .setId(nextRuleId++) + .setOrigin(serializeTextFileOrigin(rule)) + .setSource(rule.getSource()) + .build()); + } + } + private MethodReference serializeMethodReference(DexMethod method) { return methodReferences.computeIfAbsent( method, @@ -261,10 +283,18 @@ }); } - private TextFileOrigin serializeTextFileOrigin(ProguardKeepRuleBase keepRule) { + private TextFileOrigin serializeTextFileOrigin(GlobalConfigurationRule rule) { + return serializeTextFileOrigin(rule.getOrigin(), rule.getPosition()); + } + + private TextFileOrigin serializeTextFileOrigin(ProguardKeepRuleBase rule) { + return serializeTextFileOrigin(rule.getOrigin(), rule.getPosition()); + } + + private TextFileOrigin serializeTextFileOrigin(Origin origin, Position position) { int line, column; - if (keepRule.getPosition() instanceof TextRange) { - TextRange textRange = (TextRange) keepRule.getPosition(); + if (position instanceof TextRange) { + TextRange textRange = (TextRange) position; line = textRange.getStart().getLine(); column = textRange.getStart().getColumn(); } else { @@ -272,7 +302,7 @@ column = -1; } return TextFileOrigin.newBuilder() - .setFileOriginId(serializeOrigin(keepRule.getOrigin()).getId()) + .setFileOriginId(serializeOrigin(origin).getId()) .setLineNumber(line) .setColumnNumber(column) .build(); @@ -325,6 +355,8 @@ @SuppressWarnings("UnusedVariable") private boolean validate(BlastRadiusContainer container) { // TODO(b/441055269): Check that ids of ClassFileInJarOrigin and FileOrigin are non-overlapping. + // TODO(b/441055269): Check that ids of GlobalKeepRuleBlastRadius and KeepRuleBlastRadius are + // non-overlapping. // TODO(b/441055269): Check that the reference constants pools do not contain duplicates. return true; }
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 e2ff939..ad2b61f 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -8,16 +8,12 @@ import com.android.tools.r8.ByteBufferProvider; import com.android.tools.r8.ByteDataView; import com.android.tools.r8.D8.ConvertedCfFiles; -import com.android.tools.r8.DataDirectoryResource; -import com.android.tools.r8.DataEntryResource; import com.android.tools.r8.DataResourceConsumer; import com.android.tools.r8.DataResourceProvider; -import com.android.tools.r8.DataResourceProvider.Visitor; import com.android.tools.r8.DexFilePerClassFileConsumer; import com.android.tools.r8.DexIndexedConsumer; import com.android.tools.r8.FeatureSplit; import com.android.tools.r8.ProgramConsumer; -import com.android.tools.r8.ResourceException; import com.android.tools.r8.SourceFileEnvironment; import com.android.tools.r8.debuginfo.DebugRepresentation; import com.android.tools.r8.debuginfo.DebugRepresentation.DebugRepresentationPredicate; @@ -27,9 +23,7 @@ import com.android.tools.r8.dex.distribution.FillFilesDistributor; import com.android.tools.r8.dex.distribution.MonoDexDistributor; import com.android.tools.r8.dex.jumbostrings.JumboStringRewriter; -import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer; -import com.android.tools.r8.graph.AppServices; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexAnnotationSet; @@ -69,7 +63,6 @@ import com.android.tools.r8.utils.OriginalSourceFiles; import com.android.tools.r8.utils.PredicateUtils; import com.android.tools.r8.utils.Reporter; -import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.SupplierUtils; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.timing.Timing; @@ -591,12 +584,8 @@ if (dataResourceConsumer != null) { ImmutableList<DataResourceProvider> dataResourceProviders = appView.app().dataResourceProviders; - adaptAndPassDataResources( - options, - dataResourceConsumer, - dataResourceProviders, - resourceAdapter, - kotlinModuleSynthesizer); + DataResourceWriter.adaptAndPassDataResources( + options, dataResourceConsumer, dataResourceProviders, resourceAdapter); appView.appServices().write(appView, FeatureSplit.BASE, dataResourceConsumer); // Rewrite/synthesize kotlin_module files @@ -608,8 +597,8 @@ if (options.hasFeatureSplitConfiguration()) { for (DataResourceProvidersAndConsumer entry : options.getFeatureSplitConfiguration().getDataResourceProvidersAndConsumers()) { - adaptAndPassDataResources( - options, entry.consumer, entry.providers, resourceAdapter, kotlinModuleSynthesizer); + DataResourceWriter.adaptAndPassDataResources( + options, entry.consumer, entry.providers, resourceAdapter); appView.appServices().write(appView, entry.featureSplit, entry.consumer); } } @@ -629,61 +618,6 @@ } } - private static void adaptAndPassDataResources( - InternalOptions options, - DataResourceConsumer dataResourceConsumer, - Collection<DataResourceProvider> dataResourceProviders, - ResourceAdapter resourceAdapter, - KotlinModuleSynthesizer kotlinModuleSynthesizer) { - Set<String> generatedResourceNames = new HashSet<>(); - - for (DataResourceProvider dataResourceProvider : dataResourceProviders) { - try { - dataResourceProvider.accept( - new Visitor() { - @Override - public void visit(DataDirectoryResource directory) { - DataDirectoryResource adapted = resourceAdapter.adaptIfNeeded(directory); - if (adapted != null) { - dataResourceConsumer.accept(adapted, options.reporter); - options.reporter.failIfPendingErrors(); - } - } - - @Override - public void visit(DataEntryResource file) { - if ("META-INF/MANIFEST.MF".equals(file.getName())) { - // Many android library input .jar files contain a MANIFEST.MF. It does not make - // sense to propagate them since they are manifests of the input libraries. - if (options.isGeneratingDex() - || options.getTestingOptions().forcePruneMetaInfManifestMf) { - return; - } - } - if (file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)) { - // META-INF/services resources are handled separately. - return; - } - if (kotlinModuleSynthesizer.isKotlinModuleFile(file)) { - // .kotlin_module files are synthesized. - return; - } - DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file); - if (generatedResourceNames.add(adapted.getName())) { - dataResourceConsumer.accept(adapted, options.reporter); - } else { - options.reporter.warning( - new StringDiagnostic("Resource '" + file.getName() + "' already exists.")); - } - options.reporter.failIfPendingErrors(); - } - }); - } catch (ResourceException e) { - throw new CompilationError(e.getMessage(), e); - } - } - } - private void insertAttributeAnnotations() { // Convert inner-class attributes to DEX annotations for (DexProgramClass clazz : appView.appInfo().classes()) {
diff --git a/src/main/java/com/android/tools/r8/dex/DataResourceWriter.java b/src/main/java/com/android/tools/r8/dex/DataResourceWriter.java new file mode 100644 index 0000000..f0f79b1 --- /dev/null +++ b/src/main/java/com/android/tools/r8/dex/DataResourceWriter.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2026, 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.dex; + +import com.android.tools.r8.DataDirectoryResource; +import com.android.tools.r8.DataEntryResource; +import com.android.tools.r8.DataResourceConsumer; +import com.android.tools.r8.DataResourceProvider; +import com.android.tools.r8.DataResourceProvider.Visitor; +import com.android.tools.r8.ResourceException; +import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.graph.AppServices; +import com.android.tools.r8.naming.KotlinModuleSynthesizer; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.StringDiagnostic; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class DataResourceWriter { + + public static void adaptAndPassDataResources( + InternalOptions options, + DataResourceConsumer dataResourceConsumer, + Collection<DataResourceProvider> dataResourceProviders, + ResourceAdapter resourceAdapter) { + Set<String> generatedResourceNames = new HashSet<>(); + + for (DataResourceProvider dataResourceProvider : dataResourceProviders) { + try { + dataResourceProvider.accept( + new Visitor() { + @Override + public void visit(DataDirectoryResource directory) { + if (shouldDropDataDirectoryResource(directory, options)) { + return; + } + DataDirectoryResource adapted = resourceAdapter.adaptIfNeeded(directory); + if (adapted != null) { + dataResourceConsumer.accept(adapted, options.reporter); + options.reporter.failIfPendingErrors(); + } + } + + @Override + public void visit(DataEntryResource file) { + if (shouldDropDataEntryResource(file, options)) { + return; + } + DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file); + if (generatedResourceNames.add(adapted.getName())) { + dataResourceConsumer.accept(adapted, options.reporter); + } else { + options.reporter.warning( + new StringDiagnostic("Resource '" + file.getName() + "' already exists.")); + } + options.reporter.failIfPendingErrors(); + } + }); + } catch (ResourceException e) { + throw new CompilationError(e.getMessage(), e); + } + } + } + + public static boolean shouldDropDataDirectoryResource( + DataDirectoryResource directory, InternalOptions options) { + if (options.getProguardConfiguration() == null) { + assert options.testing.enableD8MetaInfServicesPassThrough; + return true; + } + return !options.getProguardConfiguration().getKeepDirectories().matches(directory.getName()); + } + + public static boolean shouldDropDataEntryResource( + DataEntryResource file, InternalOptions options) { + if ("META-INF/MANIFEST.MF".equals(file.getName())) { + // Many android library input .jar files contain a MANIFEST.MF. It does not make + // sense to propagate them since they are manifests of the input libraries. + if (options.isGeneratingDex() || options.getTestingOptions().forcePruneMetaInfManifestMf) { + return true; + } + } + if (file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)) { + // META-INF/services resources are handled separately. + return true; + } + if (KotlinModuleSynthesizer.isKotlinModuleFile(file)) { + // .kotlin_module files are synthesized. + return true; + } + return false; + } +}
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java index e9b35f4..ddb9418 100644 --- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java +++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.dex; +import static com.android.tools.r8.graph.lens.GraphLens.getIdentityLens; + import com.android.tools.r8.DataDirectoryResource; import com.android.tools.r8.DataEntryResource; import com.android.tools.r8.ResourceException; @@ -46,12 +48,15 @@ this.options = appView.options(); } + public String adaptFileNameIfNeeded(DataEntryResource file) { + return shouldAdapt(file, options, ProguardConfiguration::getAdaptResourceFilenames) + ? adaptFileName(file) + : file.getName(); + } + public DataEntryResource adaptIfNeeded(DataEntryResource file) { // Adapt name, if needed. - String name = - shouldAdapt(file, options, ProguardConfiguration::getAdaptResourceFilenames) - ? adaptFileName(file) - : file.getName(); + String name = adaptFileNameIfNeeded(file); assert name != null; // Adapt contents, if needed. byte[] contents = @@ -74,14 +79,6 @@ } public DataDirectoryResource adaptIfNeeded(DataDirectoryResource directory) { - // First check if this directory should even be in the output. - if (options.getProguardConfiguration() == null) { - assert options.testing.enableD8MetaInfServicesPassThrough; - return null; - } - if (!options.getProguardConfiguration().getKeepDirectories().matches(directory.getName())) { - return null; - } return DataDirectoryResource.fromName(adaptDirectoryName(directory), directory.getOrigin()); } @@ -111,12 +108,21 @@ return file.getName(); } - private String adaptDirectoryName(DataDirectoryResource file) { - DirectoryNameAdapter adapter = new DirectoryNameAdapter(file.getName()); + public String adaptDirectoryName(DataDirectoryResource directory) { + DirectoryNameAdapter adapter = new DirectoryNameAdapter(directory.getName()); if (adapter.run()) { return adapter.getResult(); } - return file.getName(); + return directory.getName(); + } + + protected String adaptPackage(String javaPackage) { + String packageName = appView.graphLens().lookupPackageName(javaPackage); + return namingLens.lookupPackageName(packageName); + } + + private DexString adaptType(DexType type) { + return namingLens.lookupDescriptor(graphLens.lookupType(type, getIdentityLens())); } // According to the Proguard documentation, the resource files should be parsed and written using @@ -271,7 +277,7 @@ DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(javaType)); DexType dexType = descriptor != null ? dexItemFactory.lookupType(descriptor) : null; if (dexType != null) { - DexString renamedDescriptor = namingLens.lookupDescriptor(graphLens.lookupType(dexType)); + DexString renamedDescriptor = adaptType(dexType); if (!descriptor.equals(renamedDescriptor)) { String renamedJavaType = DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString()); @@ -296,8 +302,7 @@ if (getClassNameSeparator() != '/') { javaPackage = javaPackage.replace(getClassNameSeparator(), '/'); } - String packageName = appView.graphLens().lookupPackageName(javaPackage); - String minifiedJavaPackage = namingLens.lookupPackageName(packageName); + String minifiedJavaPackage = adaptPackage(javaPackage); if (!javaPackage.equals(minifiedJavaPackage)) { outputRangeFromInput(outputFrom, from); outputJavaType(
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 5002ee7..ff0aca1 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -356,6 +356,7 @@ public final DexString newUpdaterName = createString("newUpdater"); public final DexString compareAndSetName = createString("compareAndSet"); + public final DexString getName = createString("get"); public final DexString constructorMethodName = createString(Constants.INSTANCE_INITIALIZER_NAME); public final DexString classConstructorMethodName = @@ -2589,6 +2590,7 @@ public final DexMethod longUpdater; public final DexMethod referenceUpdater; public final DexMethod referenceCompareAndSet; + public final DexMethod referenceGet; private final Set<DexMethod> updaters; private AtomicFieldUpdaterMethods() { @@ -2610,13 +2612,19 @@ newUpdaterName, referenceFieldUpdaterDescriptor, new DexString[] {classDescriptor, classDescriptor, stringDescriptor}); + updaters = ImmutableSet.of(intUpdater, longUpdater, referenceUpdater); referenceCompareAndSet = createMethod( referenceFieldUpdaterDescriptor, compareAndSetName, booleanDescriptor, new DexString[] {objectDescriptor, objectDescriptor, objectDescriptor}); - updaters = ImmutableSet.of(intUpdater, longUpdater, referenceUpdater); + referenceGet = + createMethod( + referenceFieldUpdaterDescriptor, + getName, + objectDescriptor, + new DexString[] {objectDescriptor}); } public boolean isFieldUpdater(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java index 30c2029..a8ded38 100644 --- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -90,6 +90,10 @@ return keepDeclarations; } + public void setKeepDeclarations(List<KeepDeclaration> keepDeclarations) { + this.keepDeclarations = keepDeclarations; + } + public Collection<DexLibraryClass> libraryClasses() { return libraryClasses.values(); } @@ -239,6 +243,7 @@ super(application); classpathClasses = application.classpathClasses; libraryClasses = application.libraryClasses; + keepDeclarations = application.keepDeclarations; } private Builder(InternalOptions options, Timing timing) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java index 272f1e9..483ab9c 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.graph; import com.android.tools.r8.utils.DescriptorUtils; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Iterator; import java.util.Set; @@ -65,7 +64,11 @@ } public Set<DexProgramClass> classesInPackage() { - return ImmutableSet.copyOf(classes); + return classes; + } + + public int size() { + return classes.size(); } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java index 889d772..7a5daf8 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
@@ -44,6 +44,10 @@ return packages.get(clazz.getType().getPackageDescriptor()); } + public ProgramPackage getPackage(String packageDescriptor) { + return packages.get(packageDescriptor); + } + public boolean isEmpty() { return packages.isEmpty(); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/AtomicFieldUpdaterOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/AtomicFieldUpdaterOptimizer.java index ba0671e..1cbc19f 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/AtomicFieldUpdaterOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/AtomicFieldUpdaterOptimizer.java
@@ -6,7 +6,6 @@ import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; 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.DexType; import com.android.tools.r8.ir.code.IRCode; @@ -21,6 +20,7 @@ import com.android.tools.r8.ir.optimize.AtomicFieldUpdaterInstrumentor.AtomicFieldUpdaterInstrumentorInfo; import com.google.common.collect.ImmutableList; import java.util.ArrayList; +import java.util.Map; /** * This pass uses the information and instrumentation of {@code AtomicFieldUpdaterInstrumentor} to @@ -82,7 +82,6 @@ @Override protected CodeRewriterResult rewriteCode(IRCode code) { AtomicFieldUpdaterInstrumentorInfo info = appView.getAtomicFieldUpdaterInstrumentorInfo(); - DexItemFactory factory = appView.dexItemFactory(); var atomicUpdaterFields = info.getInstrumentations().get(code.context().getHolderType()); assert atomicUpdaterFields != null; @@ -104,90 +103,197 @@ } var invoke = next.asInvokeVirtual(); assert invoke != null; + DexMethod invokedMethod = invoke.getInvokedMethod(); + if (!invokedMethod.holder.isIdenticalTo( + dexItemFactory.javaUtilConcurrentAtomicAtomicReferenceFieldUpdater)) { + continue; + } // Check for updater.compareAndSet(holder, expect, update) call. - if (!invoke - .getInvokedMethod() - .isIdenticalTo(factory.atomicFieldUpdaterMethods.referenceCompareAndSet)) { - continue; - } - - // Resolve updater. - var updaterValue = invoke.getReceiver(); - var updaterMightBeNull = updaterValue.getType().isNullable(); - DexField updaterField; - var updaterAbstractValue = - updaterValue.getAbstractValue(appView, code.context()).removeNullOrAbstractValue(); - if (updaterAbstractValue.isSingleFieldValue()) { - updaterField = updaterAbstractValue.asSingleFieldValue().getField(); + if (invokedMethod.isIdenticalTo( + dexItemFactory.atomicFieldUpdaterMethods.referenceCompareAndSet)) { + if (visitCompareAndSet( + code, + it, + next.getPosition(), + invoke, + atomicUpdaterFields, + info.getUnsafeInstanceField(), + next.outValue())) { + changed = true; + } + } else if (invokedMethod.isIdenticalTo( + dexItemFactory.atomicFieldUpdaterMethods.referenceGet)) { + if (visitGet( + code, + it, + next.getPosition(), + invoke, + atomicUpdaterFields, + info.getUnsafeInstanceField(), + next.outValue())) { + changed = true; + } } else { reportFailure( - next.getPosition(), - "HERE.compareAndSet(..) is statically unclear or unhelpful: " + updaterAbstractValue); - continue; + next.getPosition(), "not implemented: " + invokedMethod.name.toSourceString()); } - var updaterInfo = atomicUpdaterFields.get(updaterField); - if (updaterInfo == null) { - reportFailure( - next.getPosition(), - "HERE.compareAndSet(..) refers to an un-instrumented updater field"); - continue; - } - - // Resolve holder. - var holderValue = invoke.getFirstNonReceiverArgument(); - var expectedHolder = updaterInfo.holder; - if (!holderValue - .getType() - .lessThanOrEqual(expectedHolder.toNonNullTypeElement(appView), appView)) { - if (appView.testing().enableAtomicFieldUpdaterLogs) { - if (holderValue - .getType() - .lessThanOrEqual(expectedHolder.toTypeElement(appView), appView)) { - reportFailure(next.getPosition(), "_.compareAndSet(HERE, _, _) is nullable"); - } else { - reportFailure(next.getPosition(), "_.compareAndSet(HERE, _, _) is of unexpected type"); - } - } - continue; - } - - // Resolve expect. - var expectValue = invoke.getSecondNonReceiverArgument(); - - // Resolve update. - var updateValue = invoke.getThirdNonReceiverArgument(); - var expectedType = updaterInfo.reflectedFieldType; - if (!updateValue.getType().lessThanOrEqual(expectedType.toTypeElement(appView), appView)) { - reportFailure(next.getPosition(), "_.compareAndSet(_, _, HERE) is of unexpected type"); - continue; - } - - reportSuccess(next.getPosition(), updaterMightBeNull); - rewriteToOptimizedCall( - code, - it, - next.getPosition(), - updaterMightBeNull, - info.getUnsafeInstanceField(), - updaterValue, - holderValue, - updaterInfo.offsetField, - expectValue, - updateValue, - next.outValue()); - changed = true; } return CodeRewriterResult.hasChanged(changed); } + private boolean visitCompareAndSet( + IRCode code, + IRCodeInstructionListIterator it, + Position position, + InvokeVirtual invoke, + Map<DexField, AtomicFieldUpdaterInfo> atomicUpdaterFields, + DexField unsafeInstanceField, + Value outValue) { + // Resolve updater. + var updaterValue = invoke.getReceiver(); + var resolvedUpdater = + resolveUpdater(code, position, atomicUpdaterFields, updaterValue, "compareAndSet"); + if (resolvedUpdater == null) { + return false; + } + + // Resolve holder. + var holderValue = invoke.getFirstNonReceiverArgument(); + var expectedHolder = resolvedUpdater.updaterFieldInfo.holder; + if (!isHolderValid(position, holderValue, expectedHolder, "compareAndSet")) { + return false; + } + + // Resolve expect. + var expectValue = invoke.getSecondNonReceiverArgument(); + + // Resolve update. + var updateValue = invoke.getThirdNonReceiverArgument(); + var expectedType = resolvedUpdater.updaterFieldInfo.reflectedFieldType; + if (!updateValue.getType().lessThanOrEqual(expectedType.toTypeElement(appView), appView)) { + reportFailure(position, "_.compareAndSet(_, _, HERE) is of unexpected type"); + return false; + } + + reportSuccess(position, resolvedUpdater.isNullable); + rewriteCompareAndSet( + code, + it, + position, + resolvedUpdater.isNullable, + unsafeInstanceField, + updaterValue, + holderValue, + resolvedUpdater.updaterFieldInfo.offsetField, + expectValue, + updateValue, + outValue); + return true; + } + + private boolean visitGet( + IRCode code, + IRCodeInstructionListIterator it, + Position position, + InvokeVirtual invoke, + Map<DexField, AtomicFieldUpdaterInfo> atomicUpdaterFields, + DexField unsafeInstanceField, + Value outValue) { + // Resolve updater. + var updaterValue = invoke.getReceiver(); + var resolvedUpdater = resolveUpdater(code, position, atomicUpdaterFields, updaterValue, "get"); + if (resolvedUpdater == null) { + return false; + } + + // Resolve holder. + var holderValue = invoke.getFirstNonReceiverArgument(); + var expectedHolder = resolvedUpdater.updaterFieldInfo.holder; + if (!isHolderValid(position, holderValue, expectedHolder, "get")) { + return false; + } + + reportSuccess(position, resolvedUpdater.isNullable); + rewriteGet( + code, + it, + position, + resolvedUpdater.isNullable, + unsafeInstanceField, + updaterValue, + holderValue, + resolvedUpdater.updaterFieldInfo.offsetField, + outValue); + return true; + } + + private ResolvedUpdater resolveUpdater( + IRCode code, + Position position, + Map<DexField, AtomicFieldUpdaterInfo> atomicUpdaterFields, + Value updaterValue, + String methodNameForLogging) { + var updaterMightBeNull = updaterValue.getType().isNullable(); + DexField updaterField; + var updaterAbstractValue = + updaterValue.getAbstractValue(appView, code.context()).removeNullOrAbstractValue(); + if (updaterAbstractValue.isSingleFieldValue()) { + updaterField = updaterAbstractValue.asSingleFieldValue().getField(); + } else { + reportFailure( + position, + "HERE." + + methodNameForLogging + + "(..) is statically unclear or unhelpful: " + + updaterAbstractValue); + return null; + } + var updaterInfo = atomicUpdaterFields.get(updaterField); + if (updaterInfo == null) { + reportFailure( + position, + "HERE." + methodNameForLogging + "(..) refers to an un-instrumented updater field"); + return null; + } + return new ResolvedUpdater(updaterMightBeNull, updaterInfo); + } + + private static class ResolvedUpdater { + + final boolean isNullable; + final AtomicFieldUpdaterInfo updaterFieldInfo; + + private ResolvedUpdater(boolean isNullable, AtomicFieldUpdaterInfo updaterFieldInfo) { + this.isNullable = isNullable; + this.updaterFieldInfo = updaterFieldInfo; + } + } + + private boolean isHolderValid( + Position position, Value holderValue, DexType expectedHolder, String methodNameForLogging) { + if (holderValue + .getType() + .lessThanOrEqual(expectedHolder.toNonNullTypeElement(appView), appView)) { + return true; + } + if (appView.testing().enableAtomicFieldUpdaterLogs) { + if (holderValue.getType().lessThanOrEqual(expectedHolder.toTypeElement(appView), appView)) { + reportFailure(position, "_." + methodNameForLogging + "(HERE, ..) is nullable"); + } else { + reportFailure(position, "_." + methodNameForLogging + "(HERE, ..) is of unexpected type"); + } + } + return false; + } + /** * Rewrites a call to {@code updater.compareAndSet(holder, expect, update)} (assumed to be the * last instruction returned by {@code it.next}) into a call {@code * SyntheticUnsafeClass.unsafe.compareAndSwapObject(holder, this.offsetField, expect, update)} and * potentially a null-check on updater. */ - private void rewriteToOptimizedCall( + private void rewriteCompareAndSet( IRCode code, IRCodeInstructionListIterator it, Position position, @@ -199,51 +305,31 @@ Value expectValue, Value updateValue, Value outValue) { - var factory = appView.dexItemFactory(); var instructions = new ArrayList<Instruction>(3); - // Null-check for updater. if (updaterMightBeNull) { - var nullCheck = - new InvokeVirtual( - factory.objectMembers.getClass, - code.createValue(factory.classType.toTypeElement(appView)), - ImmutableList.of(updaterValue)); - nullCheck.setPosition(position); - instructions.add(nullCheck); + instructions.add(createNullCheck(code, position, updaterValue)); } - // Get unsafe instance. - assert unsafeInstanceField.type.isIdenticalTo(factory.unsafeType); - Instruction unsafeInstance = - new StaticGet( - code.createValue(factory.unsafeType.toTypeElement(appView)), unsafeInstanceField); - unsafeInstance.setPosition(position); + Instruction unsafeInstance = createUnsafeGet(code, position, unsafeInstanceField); instructions.add(unsafeInstance); - // Get offset field. - assert offsetField.type.isIdenticalTo(factory.longType); - Instruction offset = - new StaticGet(code.createValue(factory.longType.toTypeElement(appView)), offsetField); - offset.setPosition(position); + Instruction offset = createOffsetGet(code, position, offsetField); instructions.add(offset); // Add instructions BEFORE the compareAndSet instruction. - it.previous(); - // TODO(b/453628974): Test with a local exception handler. - it.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(instructions, appView.options()); - it.next(); + insertInstructionsBeforeCurrentInstruction(it, instructions); // Call underlying unsafe method. DexMethod unsafeCompareAndSetMethod = - factory.createMethod( - factory.unsafeType, - factory.createProto( - factory.booleanType, - factory.objectType, - factory.longType, - factory.objectType, - factory.objectType), + dexItemFactory.createMethod( + dexItemFactory.unsafeType, + dexItemFactory.createProto( + dexItemFactory.booleanType, + dexItemFactory.objectType, + dexItemFactory.longType, + dexItemFactory.objectType, + dexItemFactory.objectType), "compareAndSwapObject"); Instruction unsafeCompareAndSet = new InvokeVirtual( @@ -260,6 +346,94 @@ // TODO(b/453628974): Does profiling need to be updated? } + /** + * Rewrites a call to {@code updater.get(holder)} (assumed to be the last instruction returned by + * {@code it.next}) into a call {@code SyntheticUnsafeClass.unsafe.getReferenceVolatile(holder)} + * and potentially a null-check on updater. + */ + private void rewriteGet( + IRCode code, + IRCodeInstructionListIterator it, + Position position, + boolean updaterMightBeNull, + DexField unsafeInstanceField, + Value updaterValue, + Value holderValue, + DexField offsetField, + Value outValue) { + var instructions = new ArrayList<Instruction>(3); + + // Null-check for updater. + if (updaterMightBeNull) { + instructions.add(createNullCheck(code, position, updaterValue)); + } + + // Get unsafe instance. + Instruction unsafeInstance = createUnsafeGet(code, position, unsafeInstanceField); + instructions.add(unsafeInstance); + + // Get offset field. + Instruction offset = createOffsetGet(code, position, offsetField); + instructions.add(offset); + + // Add instructions BEFORE the get instruction. + insertInstructionsBeforeCurrentInstruction(it, instructions); + + // Call underlying unsafe method. + DexMethod unsafeGetMethod = + dexItemFactory.createMethod( + dexItemFactory.unsafeType, + dexItemFactory.createProto( + dexItemFactory.objectType, dexItemFactory.objectType, dexItemFactory.longType), + "getObjectVolatile"); + Instruction unsafeGet = + new InvokeVirtual( + unsafeGetMethod, + outValue, + ImmutableList.of(unsafeInstance.outValue(), holderValue, offset.outValue())); + unsafeGet.setPosition(position); + it.replaceCurrentInstruction(unsafeGet); + // TODO(b/453628974): Does profiling need to be updated? + } + + private Instruction createOffsetGet(IRCode code, Position position, DexField offsetField) { + assert offsetField.type.isIdenticalTo(dexItemFactory.longType); + Instruction offset = + new StaticGet( + code.createValue(dexItemFactory.longType.toTypeElement(appView)), offsetField); + offset.setPosition(position); + return offset; + } + + private InvokeVirtual createNullCheck(IRCode code, Position position, Value updaterValue) { + var nullCheck = + new InvokeVirtual( + dexItemFactory.objectMembers.getClass, + code.createValue(dexItemFactory.classType.toTypeElement(appView)), + ImmutableList.of(updaterValue)); + nullCheck.setPosition(position); + return nullCheck; + } + + private Instruction createUnsafeGet( + IRCode code, Position position, DexField unsafeInstanceField) { + assert unsafeInstanceField.type.isIdenticalTo(dexItemFactory.unsafeType); + Instruction unsafeInstance = + new StaticGet( + code.createValue(dexItemFactory.unsafeType.toTypeElement(appView)), + unsafeInstanceField); + unsafeInstance.setPosition(position); + return unsafeInstance; + } + + private void insertInstructionsBeforeCurrentInstruction( + IRCodeInstructionListIterator it, ArrayList<Instruction> instructions) { + it.previous(); + // TODO(b/453628974): Test with a local exception handler. + it.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(instructions, appView.options()); + it.next(); + } + private void reportFailure(Position position, String reason) { if (!appView.testing().enableAtomicFieldUpdaterLogs) { return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java b/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java index cfd20f4..db012d9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java
@@ -330,30 +330,37 @@ return null; } var fieldNameIns = fieldNameValue.definition; - // TODO(b/453628974): Add test with an invalid field name (this is then a const string - // instruction). - if (!fieldNameIns.isDexItemBasedConstString()) { - logs.reportFailure(updaterField, "newUpdater(_, _, HERE) is not a dex-item-based string"); + ProgramField reflectedField; + if (fieldNameIns.isDexItemBasedConstString()) { + var fieldNameReference = fieldNameIns.asDexItemBasedConstString().getItem(); + if (!fieldNameReference.isDexField()) { + logs.reportFailure( + updaterField, "newUpdater(_, _, HERE) is a dex reference to a non-field"); + return null; + } + var reflectedFieldReference = fieldNameReference.asDexField(); + if (!reflectedFieldReference.getHolderType().isIdenticalTo(clazz.getType())) { + logs.reportFailure( + updaterField, "newUpdater(_, _, HERE) is a dex reference to a field of another class."); + return null; + } + if (!reflectedFieldReference.type.isIdenticalTo(fieldType)) { + logs.reportFailure( + updaterField, "newUpdater(_, TYPE, FIELD) FIELD's type and TYPE disagree"); + return null; + } + reflectedField = clazz.lookupProgramField(reflectedFieldReference); + } else if (fieldNameIns.isConstString()) { + var fieldNameString = fieldNameIns.asConstString().getValue(); + reflectedField = + clazz.lookupProgramField( + itemFactory.createField(clazz.getType(), fieldType, fieldNameString)); + } else { + logs.reportFailure(updaterField, "newUpdater(_, _, HERE) is not a string constant"); return null; } - var fieldNameReference = fieldNameIns.asDexItemBasedConstString().getItem(); - if (!fieldNameReference.isDexField()) { - logs.reportFailure(updaterField, "newUpdater(_, _, HERE) is a dex reference to a non-field"); - return null; - } - var reflectedFieldReference = fieldNameReference.asDexField(); - if (!reflectedFieldReference.getHolderType().isIdenticalTo(clazz.getType())) { - logs.reportFailure( - updaterField, "newUpdater(_, _, HERE) is a dex reference to a field of another class."); - return null; - } - if (!reflectedFieldReference.type.isIdenticalTo(fieldType)) { - logs.reportFailure(updaterField, "newUpdater(_, TYPE, FIELD) FIELD's type and TYPE disagree"); - return null; - } - var reflectedField = clazz.lookupProgramField(reflectedFieldReference); if (reflectedField == null) { - logs.reportFailure(updaterField, "newUpdater(..) does not validly refer to a field"); + logs.reportFailure(updaterField, "newUpdater(..) does not refer to a field"); return null; } if (!reflectedField.getAccessFlags().isVolatile()) {
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java index 96bd80a..e5daf62 100644 --- a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java +++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.metadata.impl.R8DexFileMetadataImpl; import com.android.tools.r8.metadata.impl.R8FeatureSplitMetadataImpl; import com.android.tools.r8.metadata.impl.R8FeatureSplitsMetadataImpl; +import com.android.tools.r8.metadata.impl.R8KeepAnnotationsMetadataImpl; import com.android.tools.r8.metadata.impl.R8KeepAttributesMetadataImpl; import com.android.tools.r8.metadata.impl.R8LibraryDesugaringMetadataImpl; import com.android.tools.r8.metadata.impl.R8OptionsMetadataImpl; @@ -46,6 +47,8 @@ .registerTypeAdapter( R8FeatureSplitsMetadata.class, deserializeTo(R8FeatureSplitsMetadataImpl.class)) .registerTypeAdapter( + R8KeepAnnotationsMetadata.class, deserializeTo(R8KeepAnnotationsMetadataImpl.class)) + .registerTypeAdapter( R8KeepAttributesMetadata.class, deserializeTo(R8KeepAttributesMetadataImpl.class)) .registerTypeAdapter( R8LibraryDesugaringMetadata.class,
diff --git a/src/main/java/com/android/tools/r8/metadata/R8KeepAnnotationsMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8KeepAnnotationsMetadata.java new file mode 100644 index 0000000..b04d291 --- /dev/null +++ b/src/main/java/com/android/tools/r8/metadata/R8KeepAnnotationsMetadata.java
@@ -0,0 +1,12 @@ +// Copyright (c) 2026, 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.metadata; + +import com.android.tools.r8.keepanno.annotations.KeepForApi; + +@KeepForApi +public interface R8KeepAnnotationsMetadata { + + int getNumberOfKeepAnnotations(); +}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8OptionsMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8OptionsMetadata.java index f50b85b..810f37a 100644 --- a/src/main/java/com/android/tools/r8/metadata/R8OptionsMetadata.java +++ b/src/main/java/com/android/tools/r8/metadata/R8OptionsMetadata.java
@@ -14,6 +14,12 @@ R8ApiModelingMetadata getApiModelingMetadata(); /** + * @return null if the underlying metadata is generated with a version of R8 that does not emit + * {@link R8KeepAnnotationsMetadata}. This was added in R8 9.1.26-dev. + */ + R8KeepAnnotationsMetadata getKeepAnnotationsMetadata(); + + /** * @return null if no ProGuard configuration is provided. */ R8KeepAttributesMetadata getKeepAttributesMetadata();
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java b/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java index ed22eb6..70dadfd 100644 --- a/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java +++ b/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java
@@ -45,7 +45,7 @@ virtualFilesForFeatureSplit.getOrDefault(FeatureSplit.BASE, Collections.emptyList()); InternalOptions options = appView.options(); return R8BuildMetadataImpl.builder() - .setOptions(new R8OptionsMetadataImpl(options)) + .setOptions(new R8OptionsMetadataImpl(appView, options)) .setBaselineProfileRewritingOptions(R8BaselineProfileRewritingMetadataImpl.create(options)) .applyIf( options.isGeneratingDex(), builder -> builder.setDexFilesMetadata(baseVirtualFiles))
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/R8KeepAnnotationsMetadataImpl.java b/src/main/java/com/android/tools/r8/metadata/impl/R8KeepAnnotationsMetadataImpl.java new file mode 100644 index 0000000..8ce7913 --- /dev/null +++ b/src/main/java/com/android/tools/r8/metadata/impl/R8KeepAnnotationsMetadataImpl.java
@@ -0,0 +1,39 @@ +// Copyright (c) 2026, 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.metadata.impl; + +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.keepanno.annotations.AnnotationPattern; +import com.android.tools.r8.keepanno.annotations.FieldAccessFlags; +import com.android.tools.r8.keepanno.annotations.KeepConstraint; +import com.android.tools.r8.keepanno.annotations.KeepItemKind; +import com.android.tools.r8.keepanno.annotations.UsedByReflection; +import com.android.tools.r8.metadata.R8KeepAnnotationsMetadata; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +@UsedByReflection( + description = "Keep and preserve @SerializedName for correct (de)serialization", + constraints = {KeepConstraint.LOOKUP}, + constrainAnnotations = @AnnotationPattern(constant = SerializedName.class), + kind = KeepItemKind.CLASS_AND_FIELDS, + fieldAccess = {FieldAccessFlags.PRIVATE}, + fieldAnnotatedByClassConstant = SerializedName.class) +public class R8KeepAnnotationsMetadataImpl implements R8KeepAnnotationsMetadata { + + @Expose + @SerializedName("numberOfKeepAnnotations") + private final int numberOfKeepAnnotations; + + public R8KeepAnnotationsMetadataImpl(AppView<? extends AppInfoWithClassHierarchy> appView) { + this.numberOfKeepAnnotations = + appView.app().isDirect() ? appView.app().asDirect().getKeepDeclarations().size() : 0; + } + + @Override + public int getNumberOfKeepAnnotations() { + return numberOfKeepAnnotations; + } +}
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/R8OptionsMetadataImpl.java b/src/main/java/com/android/tools/r8/metadata/impl/R8OptionsMetadataImpl.java index 385adfe..da3150b 100644 --- a/src/main/java/com/android/tools/r8/metadata/impl/R8OptionsMetadataImpl.java +++ b/src/main/java/com/android/tools/r8/metadata/impl/R8OptionsMetadataImpl.java
@@ -3,12 +3,15 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.metadata.impl; +import com.android.tools.r8.graph.AppInfoWithClassHierarchy; +import com.android.tools.r8.graph.AppView; import com.android.tools.r8.keepanno.annotations.AnnotationPattern; import com.android.tools.r8.keepanno.annotations.FieldAccessFlags; import com.android.tools.r8.keepanno.annotations.KeepConstraint; import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.UsedByReflection; import com.android.tools.r8.metadata.R8ApiModelingMetadata; +import com.android.tools.r8.metadata.R8KeepAnnotationsMetadata; import com.android.tools.r8.metadata.R8KeepAttributesMetadata; import com.android.tools.r8.metadata.R8LibraryDesugaringMetadata; import com.android.tools.r8.metadata.R8OptionsMetadata; @@ -41,6 +44,10 @@ private final boolean hasPackageObfuscationDictionary; @Expose + @SerializedName("keepAnnotations") + private final R8KeepAnnotationsMetadata keepAnnotationsMetadata; + + @Expose @SerializedName("keepAttributes") private final R8KeepAttributesMetadata keepAttributesMetadata; @@ -76,7 +83,8 @@ @SerializedName("isShrinkingEnabled") private final boolean isShrinkingEnabled; - public R8OptionsMetadataImpl(InternalOptions options) { + public R8OptionsMetadataImpl( + AppView<? extends AppInfoWithClassHierarchy> appView, InternalOptions options) { super( R8ApiModelingMetadataImpl.create(options), R8LibraryDesugaringMetadataImpl.create(options), @@ -89,6 +97,7 @@ hasConfiguration && !configuration.getClassObfuscationDictionary().isEmpty(); this.hasPackageObfuscationDictionary = hasConfiguration && !configuration.getPackageObfuscationDictionary().isEmpty(); + this.keepAnnotationsMetadata = new R8KeepAnnotationsMetadataImpl(appView); this.keepAttributesMetadata = hasConfiguration ? new R8KeepAttributesMetadataImpl(configuration.getKeepAttributes()) @@ -105,6 +114,11 @@ } @Override + public R8KeepAnnotationsMetadata getKeepAnnotationsMetadata() { + return keepAnnotationsMetadata; + } + + @Override public R8KeepAttributesMetadata getKeepAttributesMetadata() { return keepAttributesMetadata; }
diff --git a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java index f38fdaf..d8ec043 100644 --- a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java +++ b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
@@ -43,7 +43,7 @@ this.appView = appView; } - public boolean isKotlinModuleFile(DataEntryResource file) { + public static boolean isKotlinModuleFile(DataEntryResource file) { return FileUtils.isKotlinModuleFile(file.getName()); }
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java index 2b099a6..617f475 100644 --- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java +++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -69,8 +69,7 @@ public Repackaging(AppView<AppInfoWithLiveness> appView) { this.appView = appView; this.packageObfuscationMode = appView.options().getPackageObfuscationMode(); - this.repackagingConfiguration = - appView.options().testing.repackagingConfigurationFactory.apply(appView); + this.repackagingConfiguration = new DefaultRepackagingConfiguration(appView); } public void run(ExecutorService executorService, Timing timing) throws ExecutionException { @@ -132,17 +131,22 @@ private RepackagingLens repackageClasses( DirectMappedDexApplication.Builder appBuilder, ExecutorService executorService) throws ExecutionException { - if (packageObfuscationMode.isNone()) { - return null; - } BiMap<DexType, DexType> mappings = HashBiMap.create(); Map<String, String> packageMappings = new HashMap<>(); Set<String> seenPackageDescriptors = new HashSet<>(); SortedProgramPackageCollection packages = SortedProgramPackageCollection.createWithAllProgramClasses(appView); - processPackagesInDesiredLocation(packages, mappings, packageMappings, seenPackageDescriptors); + RepackagingResourceCollisionResolver resourceCollisionDetector = + RepackagingResourceCollisionResolver.create(appView, packages, packageObfuscationMode); + processPackagesInDesiredLocation( + packages, mappings, packageMappings, resourceCollisionDetector, seenPackageDescriptors); processRemainingPackages( - packages, mappings, packageMappings, seenPackageDescriptors, executorService); + packages, + mappings, + packageMappings, + resourceCollisionDetector, + seenPackageDescriptors, + executorService); mappings.entrySet().removeIf(entry -> entry.getKey().isIdenticalTo(entry.getValue())); if (mappings.isEmpty()) { return null; @@ -203,13 +207,15 @@ SortedProgramPackageCollection packages, BiMap<DexType, DexType> mappings, Map<String, String> packageMappings, + RepackagingResourceCollisionResolver resourceCollisionDetector, Set<String> seenPackageDescriptors) { // For each package that is already in the desired location, record all the classes from the // package in the mapping for collision detection. Iterator<ProgramPackage> iterator = packages.iterator(); while (iterator.hasNext()) { ProgramPackage pkg = iterator.next(); - if (repackagingConfiguration.isPackageInTargetLocation(pkg)) { + if (repackagingConfiguration.isPackageInTargetLocation(pkg, packageObfuscationMode)) { + assert !resourceCollisionDetector.isBlocked(pkg); for (DexProgramClass alreadyRepackagedClass : pkg) { if (!appView.appInfo().isRepackagingAllowed(alreadyRepackagedClass, appView)) { mappings.put(alreadyRepackagedClass.getType(), alreadyRepackagedClass.getType()); @@ -219,6 +225,7 @@ processClass(alreadyRepackagedClass, pkg, pkg.getPackageDescriptor(), mappings); } packageMappings.put(pkg.getPackageDescriptor(), pkg.getPackageDescriptor()); + resourceCollisionDetector.acceptRepackagedPackage(pkg); seenPackageDescriptors.add(pkg.getPackageDescriptor()); iterator.remove(); } @@ -229,6 +236,7 @@ SortedProgramPackageCollection packages, BiMap<DexType, DexType> mappings, Map<String, String> packageMappings, + RepackagingResourceCollisionResolver resourceCollisionDetector, Set<String> seenPackageDescriptors, ExecutorService executorService) throws ExecutionException { @@ -242,7 +250,7 @@ computeClassesToRepackage(pkg, packages, packagesWithClassesToRepackage, executorService); packagesWithClassesToRepackage.put(pkg, classesToRepackage); // Reserve the package name to ensure that we are not renaming to a package we cannot move. - if (classesToRepackage.size() != pkg.classesInPackage().size()) { + if (classesToRepackage.size() != pkg.size()) { seenPackageDescriptors.add(pkg.getPackageDescriptor()); } } @@ -252,19 +260,31 @@ continue; } // Already processed packages should have been removed. - assert !repackagingConfiguration.isPackageInTargetLocation(pkg); - String newPackageDescriptor = - repackagingConfiguration.getNewPackageDescriptor(pkg, seenPackageDescriptors); + assert !repackagingConfiguration.isPackageInTargetLocation(pkg, packageObfuscationMode); + String newPackageDescriptor; + if (resourceCollisionDetector.isBlocked(pkg)) { + assert packageObfuscationMode.isRepackageClasses(); + newPackageDescriptor = + repackagingConfiguration.getNewPackageDescriptor( + pkg, PackageObfuscationMode.FLATTEN, seenPackageDescriptors); + } else { + newPackageDescriptor = + repackagingConfiguration.getNewPackageDescriptor( + pkg, packageObfuscationMode, seenPackageDescriptors); + } for (DexProgramClass classToRepackage : classesToRepackage) { processClass(classToRepackage, pkg, newPackageDescriptor, mappings); } seenPackageDescriptors.add(newPackageDescriptor); + if (classesToRepackage.size() == pkg.size()) { + resourceCollisionDetector.acceptRepackagedPackage(pkg); + } // Package remapping is used for adapting resources. If we cannot repackage all classes in // a package then we put in the original descriptor to ensure that resources are not // rewritten. packageMappings.put( pkg.getPackageDescriptor(), - classesToRepackage.size() == pkg.classesInPackage().size() + classesToRepackage.size() == pkg.size() ? newPackageDescriptor : pkg.getPackageDescriptor()); } @@ -330,9 +350,13 @@ public interface RepackagingConfiguration { - String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors); + String getNewPackageDescriptor( + ProgramPackage pkg, + PackageObfuscationMode packageObfuscationMode, + Set<String> seenPackageDescriptors); - boolean isPackageInTargetLocation(ProgramPackage pkg); + boolean isPackageInTargetLocation( + ProgramPackage pkg, PackageObfuscationMode packageObfuscationMode); DexType getRepackagedType( DexProgramClass clazz, @@ -346,7 +370,6 @@ private final AppView<AppInfoWithLiveness> appView; private final DexItemFactory dexItemFactory; private final InternalOptions options; - private final PackageObfuscationMode packageObfuscationMode; private final ProguardConfiguration proguardConfiguration; private final MinificationPackageNamingStrategy packageMinificationStrategy; @@ -354,13 +377,15 @@ this.appView = appView; this.dexItemFactory = appView.dexItemFactory(); this.options = appView.options(); - this.packageObfuscationMode = options.getPackageObfuscationMode(); this.proguardConfiguration = options.getProguardConfiguration(); this.packageMinificationStrategy = new MinificationPackageNamingStrategy(appView); } @Override - public String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors) { + public String getNewPackageDescriptor( + ProgramPackage pkg, + PackageObfuscationMode packageObfuscationMode, + Set<String> seenPackageDescriptors) { String newPackageDescriptor = DescriptorUtils.getBinaryNameFromJavaType(proguardConfiguration.getPackagePrefix()); if (!appView.options().isMinifying()) { @@ -392,7 +417,8 @@ } @Override - public boolean isPackageInTargetLocation(ProgramPackage pkg) { + public boolean isPackageInTargetLocation( + ProgramPackage pkg, PackageObfuscationMode packageObfuscationMode) { String newPackageDescriptor = DescriptorUtils.getBinaryNameFromJavaType(proguardConfiguration.getPackagePrefix()); if (packageObfuscationMode.isRepackageClasses()) {
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java index b934d4e..0901048 100644 --- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -265,7 +265,8 @@ } else if (classesToRepackageInResolvedPackage.contains(resolvedProgramHolder)) { // Disallow repackaging of the current class. pinnedNodes.add(getNode(method.getDefinition())); - } else if (repackagingConfiguration.isPackageInTargetLocation(resolvedPackage)) { + } else if (repackagingConfiguration.isPackageInTargetLocation( + resolvedPackage, packageObfuscationMode)) { assert false : "Expected resolvedPackage to be null, but was: " + resolvedPackage; // Disallow repackaging of the current class. pinnedNodes.add(getNode(method.getDefinition()));
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingResourceCollisionResolver.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingResourceCollisionResolver.java new file mode 100644 index 0000000..9a9869f --- /dev/null +++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingResourceCollisionResolver.java
@@ -0,0 +1,203 @@ +// Copyright (c) 2026, 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.repackaging; + +import static com.android.tools.r8.dex.DataResourceWriter.shouldDropDataDirectoryResource; +import static com.android.tools.r8.dex.DataResourceWriter.shouldDropDataEntryResource; +import static com.android.tools.r8.utils.MapUtils.ignoreKey; + +import com.android.tools.r8.DataDirectoryResource; +import com.android.tools.r8.DataEntryResource; +import com.android.tools.r8.DataResourceProvider; +import com.android.tools.r8.DataResourceProvider.Visitor; +import com.android.tools.r8.ResourceException; +import com.android.tools.r8.dex.ResourceAdapter; +import com.android.tools.r8.features.FeatureSplitConfiguration; +import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.ProgramPackage; +import com.android.tools.r8.graph.SortedProgramPackageCollection; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ExceptionDiagnostic; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode; +import com.android.tools.r8.utils.MapUtils; +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +public class RepackagingResourceCollisionResolver { + + // Map from each package to the set of packages it collides with. + private final Map<ProgramPackage, Set<ProgramPackage>> collisions = new HashMap<>(); + + private final Set<ProgramPackage> blocked = Sets.newIdentityHashSet(); + + private RepackagingResourceCollisionResolver() {} + + private RepackagingResourceCollisionResolver(Collection<RepackagingCollision> collisions) { + for (RepackagingCollision collision : collisions) { + if (collision.size() > 1) { + for (ProgramPackage pkg : collision.packages) { + this.collisions + .computeIfAbsent(pkg, ignoreKey(Sets::newIdentityHashSet)) + .addAll(collision.packages); + } + } + } + } + + public static RepackagingResourceCollisionResolver create( + AppView<AppInfoWithLiveness> appView, + SortedProgramPackageCollection packages, + PackageObfuscationMode packageObfuscationMode) { + // If there is no resource consumer then collisions don't matter. + if (appView.options().dataResourceConsumer == null) { + return empty(); + } + + // If we are not repackaging then there are no collisions. + if (!packageObfuscationMode.isRepackageClasses()) { + return empty(); + } + + // Visit all data resources meanwhile repackaging all packages to the same target package and + // collecting the collisions. + InterceptingResourceAdapter adapter = new InterceptingResourceAdapter(appView, packages); + Map<String, RepackagingCollision> collisions = new HashMap<>(); + InternalOptions options = appView.options(); + forEachDataResourceProvider( + appView, + provider -> { + try { + provider.accept( + new Visitor() { + @Override + public void visit(DataDirectoryResource directory) { + if (shouldDropDataDirectoryResource(directory, options)) { + return; + } + collisions + .computeIfAbsent( + adapter.adaptDirectoryName(directory), + ignoreKey(RepackagingCollision::new)) + .addInterceptedPackagesFrom(adapter); + } + + @Override + public void visit(DataEntryResource file) { + if (shouldDropDataEntryResource(file, options)) { + return; + } + collisions + .computeIfAbsent( + adapter.adaptFileNameIfNeeded(file), + ignoreKey(RepackagingCollision::new)) + .addInterceptedPackagesFrom(adapter); + } + }); + } catch (ResourceException e) { + appView.reporter().error(new ExceptionDiagnostic(e)); + } + }); + return new RepackagingResourceCollisionResolver(collisions.values()); + } + + private static void forEachDataResourceProvider( + AppView<AppInfoWithLiveness> appView, Consumer<DataResourceProvider> consumer) { + appView.app().dataResourceProviders.forEach(consumer); + if (appView.options().hasFeatureSplitConfiguration()) { + FeatureSplitConfiguration featureSplitConfiguration = + appView.options().getFeatureSplitConfiguration(); + for (DataResourceProvidersAndConsumer entry : + featureSplitConfiguration.getDataResourceProvidersAndConsumers()) { + entry.providers.forEach(consumer); + } + } + } + + private static RepackagingResourceCollisionResolver empty() { + return new RepackagingResourceCollisionResolver(); + } + + // Called when a package is repackaged to the target package (e.g., the default package ""). + // When this happens we prohibit repackaging of all packages that collide with the current one, + // by adding them to the `blocked` set. These packages will be subject to -flattenpackagehierarchy + // instead of -repackageclasses. + void acceptRepackagedPackage(ProgramPackage pkg) { + Set<ProgramPackage> pkgCollisions = + MapUtils.removeOrDefault(collisions, pkg, Collections.emptySet()); + for (ProgramPackage pkgBlocked : pkgCollisions) { + if (pkgBlocked != pkg) { + blocked.add(pkgBlocked); + collisions.remove(pkgBlocked); + } + } + } + + boolean isBlocked(ProgramPackage pkg) { + return blocked.contains(pkg); + } + + // A resource adapter implementation that stores the set of packages that are being queried during + // calls to adaptDirectoryName() or adaptFileNameIfNeeded(). + // + // If two resources are mapped to the same file name, then we conservatively treat the packages + // that were queried during the renaming as colliding. + // + // This only intercepts calls to adaptPackage() (and not also adaptType()), since repackaging does + // not cause any collisions among types. + private static class InterceptingResourceAdapter extends ResourceAdapter { + + private final SortedProgramPackageCollection packages; + + private final Set<ProgramPackage> interceptedPackages = Sets.newIdentityHashSet(); + + InterceptingResourceAdapter( + AppView<AppInfoWithLiveness> appView, SortedProgramPackageCollection packages) { + super(appView); + this.packages = packages; + } + + @Override + public String adaptDirectoryName(DataDirectoryResource directory) { + assert interceptedPackages.isEmpty(); + return super.adaptDirectoryName(directory); + } + + @Override + public String adaptFileNameIfNeeded(DataEntryResource file) { + assert interceptedPackages.isEmpty(); + return super.adaptFileNameIfNeeded(file); + } + + @Override + protected String adaptPackage(String packageDescriptor) { + ProgramPackage pkg = packages.getPackage(packageDescriptor); + if (pkg != null) { + interceptedPackages.add(pkg); + return ""; + } + return packageDescriptor; + } + } + + private static class RepackagingCollision { + + private final Set<ProgramPackage> packages = Sets.newIdentityHashSet(); + + void addInterceptedPackagesFrom(InterceptingResourceAdapter adapter) { + packages.addAll(adapter.interceptedPackages); + adapter.interceptedPackages.clear(); + } + + int size() { + return packages.size(); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/DontObfuscateRule.java b/src/main/java/com/android/tools/r8/shaking/DontObfuscateRule.java new file mode 100644 index 0000000..43af396 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/DontObfuscateRule.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; + +public class DontObfuscateRule extends GlobalConfigurationRule { + + public DontObfuscateRule(Origin origin, Position position) { + super(origin, position); + } + + @Override + public String getSource() { + return "-dontobfuscate"; + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/DontOptimizeRule.java b/src/main/java/com/android/tools/r8/shaking/DontOptimizeRule.java new file mode 100644 index 0000000..ccff3b0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/DontOptimizeRule.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; + +public class DontOptimizeRule extends GlobalConfigurationRule { + + public DontOptimizeRule(Origin origin, Position position) { + super(origin, position); + } + + @Override + public String getSource() { + return "-dontoptimize"; + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/DontRepackageRule.java b/src/main/java/com/android/tools/r8/shaking/DontRepackageRule.java new file mode 100644 index 0000000..bcf34bd --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/DontRepackageRule.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; + +public class DontRepackageRule extends GlobalConfigurationRule { + + public DontRepackageRule(Origin origin, Position position) { + super(origin, position); + } + + @Override + public String getSource() { + return "-dontrepackage"; + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/DontShrinkRule.java b/src/main/java/com/android/tools/r8/shaking/DontShrinkRule.java new file mode 100644 index 0000000..b87647c --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/DontShrinkRule.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; + +public class DontShrinkRule extends GlobalConfigurationRule { + + public DontShrinkRule(Origin origin, Position position) { + super(origin, position); + } + + @Override + public String getSource() { + return "-dontshrink"; + } +}
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/GlobalConfigurationRule.java new file mode 100644 index 0000000..9cb7051 --- /dev/null +++ b/src/main/java/com/android/tools/r8/shaking/GlobalConfigurationRule.java
@@ -0,0 +1,28 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.shaking; + +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; + +public abstract class GlobalConfigurationRule { + + private Origin origin; + private Position position; + + public GlobalConfigurationRule(Origin origin, Position position) { + this.origin = origin; + this.position = position; + } + + public Origin getOrigin() { + return origin; + } + + public Position getPosition() { + return position; + } + + public abstract String getSource(); +}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java index 03ac5f9..4397112 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -60,9 +60,10 @@ private final Reporter reporter; private boolean allowAccessModification; private boolean ignoreWarnings; - private boolean optimizing = true; - private boolean obfuscating = true; - private boolean shrinking = true; + private final List<DontObfuscateRule> dontObfuscateRules = new ArrayList<>(); + private final List<DontOptimizeRule> dontOptimizeRules = new ArrayList<>(); + private final List<DontRepackageRule> dontRepackageRules = new ArrayList<>(); + private final List<DontShrinkRule> dontShrinkRules = new ArrayList<>(); private boolean printBlastRadius; private Path printBlastRadiusFile; private boolean printConfiguration; @@ -186,49 +187,53 @@ @Override public void disableOptimization(ProguardConfigurationSourceParser parser, Position position) { - this.optimizing = false; + dontOptimizeRules.add(new DontOptimizeRule(parser.getOrigin(), position)); } @Override public void disableObfuscation(ProguardConfigurationSourceParser parser, Position position) { - this.obfuscating = false; + dontObfuscateRules.add(new DontObfuscateRule(parser.getOrigin(), position)); } @Override public void disableRepackaging(ProguardConfigurationSourceParser parser, Position position) { + dontRepackageRules.add(new DontRepackageRule(parser.getOrigin(), position)); packageObfuscationMode = PackageObfuscationMode.NONE; } @Override public void disableShrinking(ProguardConfigurationSourceParser parser, Position position) { - this.shrinking = false; - } - - public Builder disableOptimization() { - this.optimizing = false; - return this; + dontShrinkRules.add(new DontShrinkRule(parser.getOrigin(), position)); } public Builder disableObfuscation() { - this.obfuscating = false; + // TODO(b/479221963): Improve origin for this case. + dontObfuscateRules.add(new DontObfuscateRule(Origin.unknown(), Position.UNKNOWN)); + return this; + } + + public Builder disableOptimization() { + // TODO(b/479221963): Improve origin for this case. + dontOptimizeRules.add(new DontOptimizeRule(Origin.unknown(), Position.UNKNOWN)); + return this; + } + + public Builder disableShrinking() { + // TODO(b/479221963): Improve origin for this case. + dontShrinkRules.add(new DontShrinkRule(Origin.unknown(), Position.UNKNOWN)); return this; } boolean isObfuscating() { - return obfuscating; + return dontObfuscateRules.isEmpty(); } public boolean isOptimizing() { - return optimizing; + return dontOptimizeRules.isEmpty(); } public boolean isShrinking() { - return shrinking; - } - - public Builder disableShrinking() { - shrinking = false; - return this; + return dontShrinkRules.isEmpty(); } @Override @@ -521,9 +526,10 @@ packagePrefix, allowAccessModification, ignoreWarnings, - optimizing, - obfuscating, - shrinking, + dontObfuscateRules, + dontOptimizeRules, + dontRepackageRules, + dontShrinkRules, printBlastRadius, printBlastRadiusFile, printConfiguration, @@ -571,9 +577,10 @@ private final String packagePrefix; private final boolean allowAccessModification; private final boolean ignoreWarnings; - private final boolean optimizing; - private final boolean obfuscating; - private final boolean shrinking; + private final List<DontObfuscateRule> dontObfuscateRules; + private final List<DontOptimizeRule> dontOptimizeRules; + private final List<DontRepackageRule> dontRepackageRules; + private final List<DontShrinkRule> dontShrinkRules; private final boolean printBlastRadius; private final Path printBlastRadiusFile; private final boolean printConfiguration; @@ -614,9 +621,10 @@ String packagePrefix, boolean allowAccessModification, boolean ignoreWarnings, - boolean optimizing, - boolean obfuscating, - boolean shrinking, + List<DontObfuscateRule> dontObfuscateRules, + List<DontOptimizeRule> dontOptimizeRules, + List<DontRepackageRule> dontRepackageRules, + List<DontShrinkRule> dontShrinkRules, boolean printBlastRadius, Path printBlastRadiusFile, boolean printConfiguration, @@ -653,9 +661,10 @@ this.packagePrefix = packagePrefix; this.allowAccessModification = allowAccessModification; this.ignoreWarnings = ignoreWarnings; - this.optimizing = optimizing; - this.obfuscating = obfuscating; - this.shrinking = shrinking; + this.dontObfuscateRules = dontObfuscateRules; + this.dontOptimizeRules = dontOptimizeRules; + this.dontRepackageRules = dontRepackageRules; + this.dontShrinkRules = dontShrinkRules; this.printBlastRadius = printBlastRadius; this.printBlastRadiusFile = printBlastRadiusFile; this.printConfiguration = printConfiguration; @@ -747,15 +756,15 @@ } public boolean isOptimizing() { - return optimizing; + return dontOptimizeRules.isEmpty(); } public boolean isObfuscating() { - return obfuscating; + return dontObfuscateRules.isEmpty(); } public boolean isShrinking() { - return shrinking; + return dontShrinkRules.isEmpty(); } public boolean isPrintBlastRadius() { @@ -830,6 +839,11 @@ return rules; } + public Iterable<GlobalConfigurationRule> getGlobalRules() { + return Iterables.concat( + dontObfuscateRules, dontOptimizeRules, dontRepackageRules, dontShrinkRules); + } + public List<String> getObfuscationDictionary() { return obfuscationDictionary; }
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 64d635c..b7f8efc 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -97,8 +97,6 @@ import com.android.tools.r8.profile.startup.instrumentation.InstrumentationOptions; import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.Reference; -import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration; -import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration; import com.android.tools.r8.repackaging.RepackagingLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.shaking.Enqueuer; @@ -917,10 +915,6 @@ if (isGeneratingClassFiles() && !getTestingOptions().enableRepackagingByDefaultForCf) { return getPackageObfuscationModeForNone(); } - // TODO(b/480068080): Also enable by default when -adaptresourcefilenames is enabled. - if (proguardConfiguration.getAdaptResourceFilenames().isEnabled()) { - return getPackageObfuscationModeForNone(); - } return PackageObfuscationMode.REPACKAGE; } assert packageObfuscationMode.isFlattenPackageHierarchy() @@ -2375,9 +2369,6 @@ public Consumer<String> processingContextsConsumer = null; - public Function<AppView<AppInfoWithLiveness>, RepackagingConfiguration> - repackagingConfigurationFactory = DefaultRepackagingConfiguration::new; - public BiConsumer<AppView<?>, HorizontallyMergedClasses> horizontallyMergedClassesConsumer = ConsumerUtils.emptyBiConsumer(); public Function<List<Policy>, List<Policy>> horizontalClassMergingPolicyRewriter =
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java index ba1ab79..73d1be8 100644 --- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java +++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java
@@ -120,8 +120,7 @@ // Keep the sealed class to ensure the PermittedSubclasses attribute stays live. .addKeepPermittedSubclasses(Super.class, Sub1.class, Sub2.class) .addKeepMainRule(TestClass.class) - // TODO(b/480068080): Only add -dontrepackage. - .addKeepRules(repackage ? "-repackageclasses" : "-dontrepackage") + .applyIf(!repackage, b -> b.addKeepRules("-dontrepackage")) .compile() .inspectIf(!parameters.isRandomPartialCompilation(), this::inspect) .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java index cc662f1..0505273 100644 --- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java +++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
@@ -78,8 +78,6 @@ testForR8(parameters.getBackend()) .addProgramFiles(getProgramFiles()) .addKeepMainRule(getMainClassName()) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .addHorizontallyMergedClassesInspector(inspector -> inspect(inspector, lambdasInInput)) .allowAccessModification(allowAccessModification) .noClassInlining()
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/InvokeSuperToEmptyDefaultInterfaceMethodInLibraryTest.java b/src/test/java/com/android/tools/r8/memberrebinding/InvokeSuperToEmptyDefaultInterfaceMethodInLibraryTest.java index 6f93c96..05c43ec 100644 --- a/src/test/java/com/android/tools/r8/memberrebinding/InvokeSuperToEmptyDefaultInterfaceMethodInLibraryTest.java +++ b/src/test/java/com/android/tools/r8/memberrebinding/InvokeSuperToEmptyDefaultInterfaceMethodInLibraryTest.java
@@ -44,8 +44,6 @@ .addLibraryClasses(Build.class, I.class) .addDefaultRuntimeLibrary(parameters) .addKeepMainRule(Main.class) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .allowDiagnosticWarningMessages(shouldReportDiagnostic()) .enableInliningAnnotations() .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java index 459bf82..e38bb1d 100644 --- a/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java +++ b/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.LibraryDesugaringTestConfiguration; import com.android.tools.r8.NeverInline; import com.android.tools.r8.R8TestBuilder; +import com.android.tools.r8.R8TestBuilder.KeepAnnotationLibrary; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -24,6 +25,9 @@ import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource; import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder; import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; +import com.android.tools.r8.keepanno.annotations.KeepEdge; +import com.android.tools.r8.keepanno.annotations.KeepItemKind; +import com.android.tools.r8.keepanno.annotations.KeepTarget; import com.android.tools.r8.profile.art.model.ExternalArtProfile; import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.Reference; @@ -65,7 +69,6 @@ public void testR8() throws Exception { R8BuildMetadata buildMetadata = testForR8(parameters) - .addKeepMainRule(Main.class) .apply(this::configure) .addOptionsModification(this::configureVersion) .applyIf( @@ -135,6 +138,7 @@ LibraryDesugaringSpecification.JDK11.getSpecification())) .enableIsolatedSplits(true) .enableOptimizedShrinking()) + .enableExperimentalKeepAnnotations(KeepAnnotationLibrary.LEGACY) .collectBuildMetadata(); } @@ -186,6 +190,13 @@ } // Options metadata. assertNotNull(buildMetadata.getOptionsMetadata()); + assertNotNull(buildMetadata.getOptionsMetadata().getKeepAnnotationsMetadata()); + assertEquals( + 1, + buildMetadata + .getOptionsMetadata() + .getKeepAnnotationsMetadata() + .getNumberOfKeepAnnotations()); assertNotNull(buildMetadata.getOptionsMetadata().getKeepAttributesMetadata()); assertEquals( parameters.isCfRuntime() ? null : Integer.toString(parameters.getApiLevel().getLevel()), @@ -247,6 +258,12 @@ assertEquals(versionString, buildMetadata.getVersion()); } + @KeepEdge( + consequences = + @KeepTarget( + kind = KeepItemKind.CLASS_AND_METHODS, + classConstant = Main.class, + methodName = "main")) static class Main { public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java b/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java index 6eb8bca..4faaf9b 100644 --- a/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java +++ b/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java
@@ -52,8 +52,6 @@ .addKeepMainRule(Main.class) .addKeepClassAndMembersRules(Anno.class) .addKeepClassRulesWithAllowObfuscation(A.class) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .addKeepRuntimeVisibleAnnotations() .addDontWarn(descriptorToJavaType(newDescriptor)) .compile()
diff --git a/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java b/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterCompareAndSetTest.java similarity index 84% rename from src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java rename to src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterCompareAndSetTest.java index 7c2f94dc..369b946 100644 --- a/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java +++ b/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterCompareAndSetTest.java
@@ -11,8 +11,9 @@ import com.android.tools.r8.Diagnostic; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestShrinkerBuilder; import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeMatchers; import com.android.tools.r8.utils.codeinspector.MethodSubject; @@ -26,18 +27,23 @@ import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class AtomicFieldUpdaterInstrumentorTest extends TestBase { +public class AtomicFieldUpdaterCompareAndSetTest extends TestBase { @Parameter(0) public TestParameters parameters; - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return TestParameters.builder() - .withDexRuntimesStartingFromIncluding( - Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4. - .withAllApiLevels() - .build(); + @Parameter(1) + public boolean dontObfuscate; + + @Parameters(name = "{0}, dontObfuscate:{1}") + public static List<Object[]> data() { + return buildParameters( + TestParameters.builder() + .withDexRuntimesStartingFromIncluding( + Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4. + .withAllApiLevels() + .build(), + BooleanUtils.values()); } @Test @@ -54,6 +60,7 @@ .addProgramClasses(testClass) .allowDiagnosticInfoMessages() .addKeepMainRule(testClass) + .applyIf(dontObfuscate, TestShrinkerBuilder::addDontObfuscate) .compile() .inspectDiagnosticMessages( diagnostics -> { @@ -88,7 +95,7 @@ ImmutableList.of( "java.lang.Object", "long", "java.lang.Object", "java.lang.Object"))); }) - .run(parameters.getRuntime(), TestClass.class) + .run(parameters.getRuntime(), testClass) .assertSuccessWithOutputLines("true"); }
diff --git a/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java b/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterGetTest.java similarity index 79% copy from src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java copy to src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterGetTest.java index 7c2f94dc..a220e21 100644 --- a/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java +++ b/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterGetTest.java
@@ -11,8 +11,9 @@ import com.android.tools.r8.Diagnostic; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestShrinkerBuilder; import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeMatchers; import com.android.tools.r8.utils.codeinspector.MethodSubject; @@ -26,18 +27,23 @@ import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class AtomicFieldUpdaterInstrumentorTest extends TestBase { +public class AtomicFieldUpdaterGetTest extends TestBase { @Parameter(0) public TestParameters parameters; - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return TestParameters.builder() - .withDexRuntimesStartingFromIncluding( - Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4. - .withAllApiLevels() - .build(); + @Parameter(1) + public boolean dontObfuscate; + + @Parameters(name = "{0}, dontObfuscate:{1}") + public static List<Object[]> data() { + return buildParameters( + TestParameters.builder() + .withDexRuntimesStartingFromIncluding( + Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4. + .withAllApiLevels() + .build(), + BooleanUtils.values()); } @Test @@ -54,6 +60,7 @@ .addProgramClasses(testClass) .allowDiagnosticInfoMessages() .addKeepMainRule(testClass) + .applyIf(dontObfuscate, TestShrinkerBuilder::addDontObfuscate) .compile() .inspectDiagnosticMessages( diagnostics -> { @@ -82,14 +89,13 @@ assertThat( method, CodeMatchers.invokesMethod( - "boolean", + "java.lang.Object", "sun.misc.Unsafe", - "compareAndSwapObject", - ImmutableList.of( - "java.lang.Object", "long", "java.lang.Object", "java.lang.Object"))); + "getObjectVolatile", + ImmutableList.of("java.lang.Object", "long"))); }) - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutputLines("true"); + .run(parameters.getRuntime(), testClass) + .assertSuccessWithOutputLines("Hello"); } // Corresponding to simple kotlin usage of `atomic("Hello")` via atomicfu. @@ -110,7 +116,7 @@ } public static void main(String[] args) { - System.out.println(myString$FU.compareAndSet(new TestClass(), "Hello", "World!")); + System.out.println(myString$FU.get(new TestClass())); } } }
diff --git a/src/test/java/com/android/tools/r8/repackage/AdaptResourceFileNamesRepackageCollisionTest.java b/src/test/java/com/android/tools/r8/repackage/AdaptResourceFileNamesRepackageCollisionTest.java index 3a1493c..d94f4b6 100644 --- a/src/test/java/com/android/tools/r8/repackage/AdaptResourceFileNamesRepackageCollisionTest.java +++ b/src/test/java/com/android/tools/r8/repackage/AdaptResourceFileNamesRepackageCollisionTest.java
@@ -48,15 +48,17 @@ .compile() .inspect( inspector -> { - // TODO(b/480068080): Enable repackaging by default when -adaptresourcefilenames is - // enabled. Account for resource collisions in repackaging. + // Class A should be repackaged to the default package "". ClassSubject aClass = inspector.clazz("pkg1.A"); assertThat(aClass, isPresent()); - assertEquals("a", aClass.getDexProgramClass().getType().getPackageName()); + assertEquals("", aClass.getDexProgramClass().getType().getPackageName()); + // Due to the collision between pkg1/build.properties and pkg2/build.properties, + // class B should not be repackaged to the default package "". + // We fallback to -flattenpackagehierarchy and move it to package "a". ClassSubject bClass = inspector.clazz("pkg2.B"); assertThat(bClass, isPresent()); - assertEquals("b", bClass.getDexProgramClass().getType().getPackageName()); + assertEquals("a", bClass.getDexProgramClass().getType().getPackageName()); }) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("Hello, world!");
diff --git a/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java b/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java index 4c6b744..73e663d 100644 --- a/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java +++ b/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java
@@ -39,8 +39,6 @@ testForR8(parameters.getBackend()) .addInnerClasses(getClass()) .addKeepMainRule(Main.class) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .addKeepAttributeLineNumberTable() .addKeepAttributeSourceFile() .addHorizontallyMergedClassesInspector(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/HorizontalClassMergingStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/HorizontalClassMergingStackSampleRetraceTest.java index e20575b..0a43112 100644 --- a/src/test/java/com/android/tools/r8/retrace/stacksamples/HorizontalClassMergingStackSampleRetraceTest.java +++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/HorizontalClassMergingStackSampleRetraceTest.java
@@ -57,8 +57,6 @@ testBuilder -> testBuilder .addProgramClassFileData(programClassFileData) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .addOptionsModification( options -> options.inlinerOptions().enableConstructorInlining = false) .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithInlinePositionsStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithInlinePositionsStackSampleRetraceTest.java index 1dfb7d7..780fd12 100644 --- a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithInlinePositionsStackSampleRetraceTest.java +++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithInlinePositionsStackSampleRetraceTest.java
@@ -38,8 +38,6 @@ testBuilder -> testBuilder .addProgramClassFileData(programClassFileData) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .enableInliningAnnotations()); }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithOverloadStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithOverloadStackSampleRetraceTest.java index 54f9737..8e9b80d 100644 --- a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithOverloadStackSampleRetraceTest.java +++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithOverloadStackSampleRetraceTest.java
@@ -65,8 +65,6 @@ testBuilder -> testBuilder .addProgramClassFileData(programClassFileData) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .addDontOptimize() .applyIf( keep,
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithRemovedArgumentStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithRemovedArgumentStackSampleRetraceTest.java index c7da7b2..9b4bef0 100644 --- a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithRemovedArgumentStackSampleRetraceTest.java +++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithRemovedArgumentStackSampleRetraceTest.java
@@ -40,8 +40,6 @@ testBuilder -> testBuilder .addProgramClassFileData(programClassFileData) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .enableInliningAnnotations() .enableNeverClassInliningAnnotations()); }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/StaticizedMethodStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/StaticizedMethodStackSampleRetraceTest.java index edf5000..7574d25 100644 --- a/src/test/java/com/android/tools/r8/retrace/stacksamples/StaticizedMethodStackSampleRetraceTest.java +++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/StaticizedMethodStackSampleRetraceTest.java
@@ -42,8 +42,6 @@ testBuilder -> testBuilder .addProgramClassFileData(programClassFileData) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .enableInliningAnnotations() .enableNeverClassInliningAnnotations()); }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/VerticalClassMergingStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/VerticalClassMergingStackSampleRetraceTest.java index bb2fc16..7651c90 100644 --- a/src/test/java/com/android/tools/r8/retrace/stacksamples/VerticalClassMergingStackSampleRetraceTest.java +++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/VerticalClassMergingStackSampleRetraceTest.java
@@ -57,8 +57,6 @@ .addVerticallyMergedClassesInspector( inspector -> inspector.assertMergedIntoSubtype(A.class).assertNoOtherClassesMerged()) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .enableInliningAnnotations() .enableNeverClassInliningAnnotations() .enableNoMethodStaticizingAnnotations());
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java index ec4d5f4..c76b8c1 100644 --- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java +++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -124,8 +124,6 @@ testForR8(backend) .addProgramFiles(getProgramFiles(test)) .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt")) - // TODO(b/480068080): Should not need to explicitly add -repackageclasses. - .addKeepRules("-repackageclasses") .addOptionsModification(options -> options.inlinerOptions().enableInlining = false) .setMinApi(minApi) .compile()
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java index 0e14cde..1353022 100644 --- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -1708,15 +1708,8 @@ public static ProguardConfiguration loadProguardConfiguration( DexItemFactory factory, List<Path> configPaths) { Reporter reporter = new Reporter(); + assert !configPaths.isEmpty(); ProguardConfiguration.Builder builder = ProguardConfiguration.builder(factory, reporter); - if (configPaths.isEmpty()) { - return builder - .disableShrinking() - .disableObfuscation() - .disableOptimization() - .addKeepAttributePatterns(ImmutableList.of("*")) - .build(); - } ProguardConfigurationParser parser = new ProguardConfigurationParser(factory, reporter, builder); for (Path configPath : configPaths) {