Merge commit 'e13467c2cee237bceaa4d002472ede43d9a116e8' into dev-release
diff --git a/build.gradle b/build.gradle
index 7429345..2e2132d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@
ext {
androidSupportVersion = '25.4.0'
- asmVersion = '8.0'
+ asmVersion = '8.0' // When updating update tools/asmifier.py as well.
espressoVersion = '3.0.0'
fastutilVersion = '7.2.0'
guavaVersion = '23.0'
@@ -438,7 +438,8 @@
"youtube/youtube.android_14.19",
"youtube/youtube.android_14.44",
"youtube/youtube.android_15.08",
- "youtube/youtube.android_15.09"
+ "youtube/youtube.android_15.09",
+ "youtube/youtube.android_15.33"
],
]
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index e502f2f..8def7ae 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
@@ -24,6 +25,7 @@
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.PrefixRewritingNamingLens;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
@@ -166,7 +168,7 @@
DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
PrefixRewritingMapper rewritePrefix =
options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
- AppInfo appInfo = new AppInfo(app);
+ AppInfo appInfo = AppInfo.createInitialAppInfo(app);
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
@@ -189,6 +191,10 @@
AppView<?> appView = AppView.createForD8(appInfo, rewritePrefix);
+ if (options.testing.enableD8ResourcesPassThrough) {
+ appView.setAppServices(AppServices.builder(appView).build());
+ }
+
IRConverter converter = new IRConverter(appView, timing, printer);
app = converter.convert(app, executor);
@@ -259,6 +265,7 @@
: PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
new GenericSignatureRewriter(appView, namingLens)
.run(appView.appInfo().classes(), executor);
+ new KotlinMetadataRewriter(appView, namingLens).runForD8(executor);
} else {
// There are both cf and dex inputs in the program, and rewriting is required for
// desugared library only on cf inputs. We cannot easily rewrite part of the program
@@ -322,6 +329,7 @@
PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
.run(appView.appInfo().classes(), executor);
+ new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForD8(executor);
new ApplicationWriter(
cfApp,
null,
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 1171440..8e6c389 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -90,7 +90,7 @@
null,
executor,
new DexFileMergerHelper(inputOrdering)::keepFirstProgramClassConflictResolver);
- AppInfo appInfo = new AppInfo(app);
+ AppInfo appInfo = AppInfo.createInitialAppInfo(app);
app = D8.optimize(app, appInfo, options, timing, executor);
List<Marker> markers = app.dexItemFactory.extractMarkers();
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 3ca4bf2..300588f 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -129,18 +129,19 @@
this(archive, null, false);
}
- public ArchiveConsumer(Path archive, boolean consumeDataResouces) {
- this(archive, null, consumeDataResouces);
+ public ArchiveConsumer(Path archive, boolean consumeDataResources) {
+ this(archive, null, consumeDataResources);
}
public ArchiveConsumer(Path archive, DexIndexedConsumer consumer) {
this(archive, consumer, false);
}
- public ArchiveConsumer(Path archive, DexIndexedConsumer consumer, boolean consumeDataResouces) {
+ public ArchiveConsumer(
+ Path archive, DexIndexedConsumer consumer, boolean consumeDataResources) {
super(consumer);
this.outputBuilder = new ArchiveBuilder(archive);
- this.consumeDataResources = consumeDataResouces;
+ this.consumeDataResources = consumeDataResources;
this.outputBuilder.open();
if (getDataResourceConsumer() != null) {
this.outputBuilder.open();
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 8d2d47d..b7ece81 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -86,7 +86,7 @@
assert !options.hasMethodsFilter();
// Run d8 optimize to ensure jumbo strings are handled.
- AppInfo appInfo = new AppInfo(featureApp);
+ AppInfo appInfo = AppInfo.createInitialAppInfo(featureApp);
featureApp = D8.optimize(featureApp, appInfo, options, timing, executor);
// We create a specific consumer for each split.
Path outputDir = Paths.get(output).resolve(entry.getKey());
diff --git a/src/main/java/com/android/tools/r8/FeatureSplit.java b/src/main/java/com/android/tools/r8/FeatureSplit.java
index 9bb637e..d0eafdb 100644
--- a/src/main/java/com/android/tools/r8/FeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/FeatureSplit.java
@@ -28,9 +28,15 @@
* </pre>
*/
@Keep
-public final class FeatureSplit {
+public class FeatureSplit {
- public static final FeatureSplit BASE = new FeatureSplit(null, null);
+ public static final FeatureSplit BASE =
+ new FeatureSplit(null, null) {
+ @Override
+ public boolean isBase() {
+ return true;
+ }
+ };
private final ProgramConsumer programConsumer;
private final List<ProgramResourceProvider> programResourceProviders;
@@ -41,6 +47,10 @@
this.programResourceProviders = programResourceProviders;
}
+ public boolean isBase() {
+ return false;
+ }
+
public List<ProgramResourceProvider> getProgramResourceProviders() {
return programResourceProviders;
}
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 7913783..2bfba83 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -311,7 +311,7 @@
lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
// Write a header jar with the desugared APIs.
- AppInfo appInfo = new AppInfo(app);
+ AppInfo appInfo = AppInfo.createInitialAppInfo(app);
AppView<?> appView = AppView.createForD8(appInfo);
CfApplicationWriter writer =
new CfApplicationWriter(
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 8049508..99a87d3 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -126,7 +126,7 @@
options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
DexApplication app = new L8TreePruner(options).prune(lazyApp, rewritePrefix);
- AppInfo appInfo = new AppInfo(app);
+ AppInfo appInfo = AppInfo.createInitialAppInfo(app);
AppView<?> appView = AppView.createForL8(appInfo, rewritePrefix);
IRConverter converter = new IRConverter(appView, timing);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 45ce043..47ed5a0 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -93,13 +93,12 @@
}
@Override
- public boolean registerInitClass(DexType clazz) {
+ public void registerInitClass(DexType clazz) {
addType(clazz);
- return false;
}
@Override
- public boolean registerInvokeVirtual(DexMethod method) {
+ public void registerInvokeVirtual(DexMethod method) {
ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
DexEncodedMethod target =
resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
@@ -109,17 +108,15 @@
} else {
addMethod(method);
}
- return false;
}
@Override
- public boolean registerInvokeDirect(DexMethod method) {
+ public void registerInvokeDirect(DexMethod method) {
addMethod(method);
- return false;
}
@Override
- public boolean registerInvokeStatic(DexMethod method) {
+ public void registerInvokeStatic(DexMethod method) {
DexEncodedMethod target = appInfo.unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
if (target != null && target.method != method) {
addType(method.holder);
@@ -127,65 +124,56 @@
} else {
addMethod(method);
}
- return false;
}
@Override
- public boolean registerInvokeInterface(DexMethod method) {
- return registerInvokeVirtual(method);
+ public void registerInvokeInterface(DexMethod method) {
+ registerInvokeVirtual(method);
}
@Override
- public boolean registerInvokeSuper(DexMethod method) {
+ public void registerInvokeSuper(DexMethod method) {
DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method, context);
if (superTarget != null) {
addMethod(superTarget.method);
} else {
addMethod(method);
}
- return false;
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
+ public void registerInstanceFieldWrite(DexField field) {
addField(field);
- return false;
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
+ public void registerInstanceFieldRead(DexField field) {
addField(field);
- return false;
}
@Override
- public boolean registerNewInstance(DexType type) {
+ public void registerNewInstance(DexType type) {
addType(type);
- return false;
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
+ public void registerStaticFieldRead(DexField field) {
addField(field);
- return false;
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
+ public void registerStaticFieldWrite(DexField field) {
addField(field);
- return false;
}
@Override
- public boolean registerTypeReference(DexType type) {
+ public void registerTypeReference(DexType type) {
addType(type);
- return false;
}
@Override
- public boolean registerInstanceOf(DexType type) {
+ public void registerInstanceOf(DexType type) {
addType(type);
- return false;
}
private void addType(DexType type) {
@@ -361,7 +349,7 @@
InternalOptions options = new InternalOptions();
application =
new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect();
- appInfo = new AppInfoWithClassHierarchy(application);
+ appInfo = AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(application);
}
private void analyze() {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index c006eb5..38e1556 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.DirectMappedDexApplication.Builder;
import com.android.tools.r8.graph.EnumValueInfoMapCollection;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
@@ -38,6 +39,7 @@
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
@@ -74,10 +76,12 @@
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.VisibilityBridgeRemover;
import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.repackaging.Repackaging;
import com.android.tools.r8.shaking.AbstractMethodRemover;
import com.android.tools.r8.shaking.AnnotationRemover;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ClassInitFieldSynthesizer;
+import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
import com.android.tools.r8.shaking.DiscardedChecker;
import com.android.tools.r8.shaking.Enqueuer;
@@ -323,6 +327,8 @@
timing.begin("Strip unused code");
Set<DexType> classesToRetainInnerClassAttributeFor = null;
Set<DexType> missingClasses = null;
+ ClassMergingEnqueuerExtension classMergingEnqueuerExtension =
+ new ClassMergingEnqueuerExtension(appView.dexItemFactory());
try {
// TODO(b/154849103): Find a better way to determine missing classes.
missingClasses = new SubtypingInfo(appView).getMissingClasses();
@@ -370,7 +376,12 @@
AnnotationRemover.Builder annotationRemoverBuilder =
options.isShrinking() ? AnnotationRemover.builder() : null;
AppView<AppInfoWithLiveness> appViewWithLiveness =
- runEnqueuer(annotationRemoverBuilder, executorService, appView, subtypingInfo);
+ runEnqueuer(
+ annotationRemoverBuilder,
+ executorService,
+ appView,
+ subtypingInfo,
+ classMergingEnqueuerExtension);
assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
@@ -487,8 +498,11 @@
if (options.shouldDesugarNests()) {
timing.begin("NestBasedAccessDesugaring");
R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
- NestedPrivateMethodLens lens = analyzer.run(executorService);
- appView.rewriteWithLens(lens);
+ Builder appBuilder = getDirectApp(appView).builder();
+ NestedPrivateMethodLens lens = analyzer.run(executorService, appBuilder);
+ if (lens != null) {
+ appView.rewriteWithLensAndApplication(lens, appBuilder.build());
+ }
timing.end();
} else {
timing.begin("NestReduction");
@@ -529,6 +543,17 @@
}
timing.end();
}
+ if (options.enableHorizontalClassMerging) {
+ timing.begin("HorizontalClassMerger");
+ HorizontalClassMerger merger =
+ new HorizontalClassMerger(appViewWithLiveness, mainDexClasses);
+ merger.run();
+ timing.end();
+ }
+
+ // Only required for class merging, clear instance to save memory.
+ classMergingEnqueuerExtension = null;
+
if (options.enableArgumentRemoval) {
SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
{
@@ -772,6 +797,12 @@
}
}
+ // Perform repackaging.
+ // TODO(b/165783399): Consider making repacking available without minification.
+ if (options.isMinifying() && options.testing.enableExperimentalRepackaging) {
+ new Repackaging(appView.withLiveness()).run(executorService, timing);
+ }
+
// Perform minification.
NamingLens namingLens;
if (options.getProguardConfiguration().hasApplyMappingFile()) {
@@ -791,10 +822,6 @@
namingLens = NamingLens.getIdentityLens();
}
- timing.begin("MinifyKotlinMetadata");
- new KotlinMetadataRewriter(appView, namingLens).run(executorService);
- timing.end();
-
assert verifyMovedMethodsHaveOriginalMethodPosition(appView, getDirectApp(appView));
timing.begin("Line number remapping");
@@ -855,10 +882,9 @@
assert appView
.graphLens()
.verifyMappingToOriginalProgram(
- appView.appInfo().classesWithDeterministicOrder(),
+ appView,
new ApplicationReader(inputApp.withoutMainDexList(), options, timing)
- .read(executorService),
- appView.dexItemFactory());
+ .read(executorService));
// Report synthetic rules (only for testing).
// TODO(b/120959039): Move this to being reported through the graph consumer.
@@ -869,6 +895,10 @@
NamingLens prefixRewritingNamingLens =
PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
+ timing.begin("MinifyKotlinMetadata");
+ new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForR8(executorService);
+ timing.end();
+
new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
.run(appView.appInfo().classes(), executorService);
@@ -951,7 +981,8 @@
AnnotationRemover.Builder annotationRemoverBuilder,
ExecutorService executorService,
AppView<AppInfoWithClassHierarchy> appView,
- SubtypingInfo subtypingInfo)
+ SubtypingInfo subtypingInfo,
+ ClassMergingEnqueuerExtension classMergingEnqueuerExtension)
throws ExecutionException {
Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
@@ -964,6 +995,10 @@
appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance()));
}
+ if (options.isClassMergingExtensionRequired()) {
+ classMergingEnqueuerExtension.attach(enqueuer);
+ }
+
AppView<AppInfoWithLiveness> appViewWithLiveness =
appView.setAppInfo(
enqueuer.traceApplication(
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index e15a4eb..dd179c1 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -88,10 +88,11 @@
new StringDiagnostic(
"Merging program compiled with multiple desugared libraries."));
}
- if (identifier.equals(NO_LIBRARY_DESUGARING) && marker.tool == Tool.R8) {
- continue;
+ if (marker.isDesugared()) {
+ desugaredLibraryIdentifiers.add(identifier);
+ } else {
+ assert identifier.equals(NO_LIBRARY_DESUGARING);
}
- desugaredLibraryIdentifiers.add(identifier);
}
}
@@ -134,6 +135,12 @@
return this;
}
+ public boolean isDesugared() {
+ // For both DEX and CF output from D8 and R8 a min-api setting implies that the code has been
+ // desugared, as even the highest min-api require desugaring of lambdas.
+ return hasMinApi();
+ }
+
public boolean hasMinApi() {
return jsonObject.has(MIN_API);
}
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 536515d..84459b4 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardPathFilter;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -26,6 +27,7 @@
import it.unimi.dsi.fastutil.ints.IntStack;
import java.io.InputStream;
import java.nio.charset.Charset;
+import java.util.function.Function;
public class ResourceAdapter {
@@ -50,22 +52,14 @@
public DataEntryResource adaptIfNeeded(DataEntryResource file) {
// Adapt name, if needed.
- ProguardPathFilter adaptResourceFileNamesFilter =
- options.getProguardConfiguration().getAdaptResourceFilenames();
String name =
- adaptResourceFileNamesFilter.isEnabled()
- && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
- && adaptResourceFileNamesFilter.matches(file.getName())
+ shouldAdapt(file, options, ProguardConfiguration::getAdaptResourceFilenames)
? adaptFileName(file)
: file.getName();
assert name != null;
// Adapt contents, if needed.
- ProguardPathFilter adaptResourceFileContentsFilter =
- options.getProguardConfiguration().getAdaptResourceFileContents();
byte[] contents =
- adaptResourceFileContentsFilter.isEnabled()
- && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
- && adaptResourceFileContentsFilter.matches(file.getName())
+ shouldAdapt(file, options, ProguardConfiguration::getAdaptResourceFileContents)
? adaptFileContents(file)
: null;
// Return a new resource if the name or contents changed. Otherwise return the original
@@ -85,14 +79,31 @@
public DataDirectoryResource adaptIfNeeded(DataDirectoryResource directory) {
// First check if this directory should even be in the output.
- ProguardPathFilter keepDirectoriesFilter =
- options.getProguardConfiguration().getKeepDirectories();
- if (!keepDirectoriesFilter.matches(directory.getName())) {
+ if (options.getProguardConfiguration() == null) {
+ assert options.testing.enableD8ResourcesPassThrough;
+ return null;
+ }
+ if (!options.getProguardConfiguration().getKeepDirectories().matches(directory.getName())) {
return null;
}
return DataDirectoryResource.fromName(adaptDirectoryName(directory), directory.getOrigin());
}
+ private boolean shouldAdapt(
+ DataEntryResource file,
+ InternalOptions options,
+ Function<ProguardConfiguration, ProguardPathFilter> getFilter) {
+ final ProguardConfiguration proguardConfiguration = options.getProguardConfiguration();
+ if (proguardConfiguration == null) {
+ assert options.testing.enableD8ResourcesPassThrough;
+ return false;
+ }
+ ProguardPathFilter filter = getFilter.apply(proguardConfiguration);
+ return filter.isEnabled()
+ && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+ && filter.matches(file.getName());
+ }
+
public boolean isService(DataEntryResource file) {
return file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME);
}
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index 610104a..6e5b2ab 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -9,9 +9,8 @@
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Reporter;
@@ -26,8 +25,10 @@
import java.util.Set;
public class FeatureSplitConfiguration {
+
private final List<FeatureSplit> featureSplits;
+ // TODO(141451259): Consider doing the mapping from DexType to Feature (with support in mapper).
private final Map<String, FeatureSplit> javaTypeToFeatureSplitMapping = new HashMap<>();
public FeatureSplitConfiguration(List<FeatureSplit> featureSplits, Reporter reporter) {
@@ -108,28 +109,23 @@
}
public boolean inBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
- FeatureSplit split = getFeatureSplit(clazz.type);
- return split == null || split == getFeatureSplit(context.type);
+ FeatureSplit split = getFeatureSplit(clazz);
+ return split.isBase() || split == getFeatureSplit(context);
}
public boolean isInFeature(DexProgramClass clazz) {
- return getFeatureSplit(clazz.type) != null;
+ return !isInBase(clazz);
}
public boolean isInBase(DexProgramClass clazz) {
- return !isInFeature(clazz);
+ return getFeatureSplit(clazz).isBase();
}
- public boolean inSameFeatureOrBase(DexMethod a, DexMethod b){
- return inSameFeatureOrBase(a.holder, b.holder);
+ public boolean inSameFeatureOrBothInBase(ProgramMethod a, ProgramMethod b) {
+ return inSameFeatureOrBothInBase(a.getHolder(), b.getHolder());
}
- public boolean inSameFeatureOrBase(DexType a, DexType b) {
- assert a.isClassType() && b.isClassType();
- if (javaTypeToFeatureSplitMapping.isEmpty()) {
- return true;
- }
- // TODO(141451259): Consider doing the mapping from DexType to Feature (with support in mapper)
+ public boolean inSameFeatureOrBothInBase(DexProgramClass a, DexProgramClass b) {
return getFeatureSplit(a) == getFeatureSplit(b);
}
@@ -141,8 +137,8 @@
return javaTypeToFeatureSplitMapping.get(DescriptorUtils.descriptorToJavaType(classDescriptor));
}
- private FeatureSplit getFeatureSplit(DexType type) {
- assert type.isClassType();
- return javaTypeToFeatureSplitMapping.get(type.toSourceString());
+ public FeatureSplit getFeatureSplit(DexProgramClass clazz) {
+ return javaTypeToFeatureSplitMapping.getOrDefault(
+ clazz.type.toSourceString(), FeatureSplit.BASE);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index a0a1b99..8792d0c 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -128,6 +128,10 @@
return !isPublic() && !isPrivate() && !isProtected();
}
+ public boolean isPackagePrivateOrProtected() {
+ return !isPublic() && !isPrivate();
+ }
+
public boolean isPublic() {
return isSet(Constants.ACC_PUBLIC);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index ee67876..66b00b8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,56 +3,44 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy.CreateDesugaringViewOnAppInfo;
import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMap.Builder;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
public class AppInfo implements DexDefinitionSupplier {
private final DexApplication app;
private final DexItemFactory dexItemFactory;
-
- // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
- // class being optimized.
- private final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses;
+ private final SyntheticItems syntheticItems;
// Set when a new AppInfo replaces a previous one. All public methods should verify that the
// current instance is not obsolete, to ensure that we almost use the most recent AppInfo.
private final BooleanBox obsolete;
- public AppInfo(DexApplication application) {
- this(application, new ConcurrentHashMap<>(), new BooleanBox());
+ public static AppInfo createInitialAppInfo(DexApplication application) {
+ return new AppInfo(application, SyntheticItems.createInitialSyntheticItems(), new BooleanBox());
+ }
+
+ public AppInfo(DexApplication application, SyntheticItems.CommittedItems committedItems) {
+ this(application, committedItems.toSyntheticItems(), new BooleanBox());
}
// For desugaring.
- protected AppInfo(AppInfo appInfo) {
- this(appInfo.app, appInfo.synthesizedClasses, appInfo.obsolete);
+ // This is a view onto the app info and is the only place the pending synthetics are shared.
+ AppInfo(CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
+ this(appInfo.app, appInfo.syntheticItems, appInfo.obsolete);
+ assert witness != null;
}
- // For AppInfoWithLiveness.
- protected AppInfo(AppInfoWithClassHierarchy previous) {
- this(
- ((AppInfo) previous).app,
- new ConcurrentHashMap<>(((AppInfo) previous).synthesizedClasses),
- new BooleanBox());
- }
-
- private AppInfo(
- DexApplication application,
- ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses,
- BooleanBox obsolete) {
+ private AppInfo(DexApplication application, SyntheticItems syntheticItems, BooleanBox obsolete) {
this.app = application;
this.dexItemFactory = application.dexItemFactory;
- this.synthesizedClasses = synthesizedClasses;
+ this.syntheticItems = syntheticItems;
this.obsolete = obsolete;
}
@@ -60,10 +48,6 @@
return app.options;
}
- public void copyMetadataFromPrevious(AppInfo previous) {
- this.synthesizedClasses.putAll(previous.synthesizedClasses);
- }
-
public boolean isObsolete() {
return obsolete.get();
}
@@ -92,25 +76,18 @@
return dexItemFactory;
}
+ public SyntheticItems getSyntheticItems() {
+ return syntheticItems;
+ }
+
public void addSynthesizedClass(DexProgramClass clazz) {
assert checkIfObsolete();
- assert clazz.type.isD8R8SynthesizedClassType();
- DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
- assert previous == null || previous == clazz;
+ syntheticItems.addSyntheticClass(clazz);
}
public Collection<DexProgramClass> synthesizedClasses() {
assert checkIfObsolete();
- return Collections.unmodifiableCollection(synthesizedClasses.values());
- }
-
- private Map<DexField, DexEncodedField> computeFieldDefinitions(DexType type) {
- Builder<DexField, DexEncodedField> builder = ImmutableMap.builder();
- DexClass clazz = definitionFor(type);
- if (clazz != null) {
- clazz.forEachField(field -> builder.put(field.field, field));
- }
- return builder.build();
+ return syntheticItems.getPendingSyntheticClasses();
}
public Collection<DexProgramClass> classes() {
@@ -130,12 +107,7 @@
public final DexClass definitionForWithoutExistenceAssert(DexType type) {
assert checkIfObsolete();
- DexProgramClass cached = synthesizedClasses.get(type);
- if (cached != null) {
- assert app.definitionFor(type) == null;
- return cached;
- }
- return app.definitionFor(type);
+ return syntheticItems.definitionFor(type, app::definitionFor);
}
public DexClass definitionForDesugarDependency(DexClass dependent, DexType type) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 481af1e..f1b0dc2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -39,27 +39,37 @@
*/
public class AppInfoWithClassHierarchy extends AppInfo {
- public AppInfoWithClassHierarchy(DexApplication application) {
- super(application);
+ private static final CreateDesugaringViewOnAppInfo WITNESS = new CreateDesugaringViewOnAppInfo();
+
+ static class CreateDesugaringViewOnAppInfo {
+ private CreateDesugaringViewOnAppInfo() {}
}
- // For desugaring.
- private AppInfoWithClassHierarchy(AppInfo appInfo) {
- super(appInfo);
+ public static AppInfoWithClassHierarchy createInitialAppInfoWithClassHierarchy(
+ DexApplication application) {
+ return new AppInfoWithClassHierarchy(
+ application, SyntheticItems.createInitialSyntheticItems().commit(application));
}
// For AppInfoWithLiveness.
- protected AppInfoWithClassHierarchy(AppInfoWithClassHierarchy previous) {
- super(previous);
+ protected AppInfoWithClassHierarchy(
+ DexApplication application, SyntheticItems.CommittedItems committedItems) {
+ super(application, committedItems);
+ }
+
+ // For desugaring.
+ private AppInfoWithClassHierarchy(CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
+ super(witness, appInfo);
}
public static AppInfoWithClassHierarchy createForDesugaring(AppInfo appInfo) {
assert !appInfo.hasClassHierarchy();
- return new AppInfoWithClassHierarchy(appInfo);
+ return new AppInfoWithClassHierarchy(WITNESS, appInfo);
}
public AppInfoWithClassHierarchy rebuild(Function<DexApplication, DexApplication> fn) {
- return new AppInfoWithClassHierarchy(fn.apply(app()));
+ DexApplication application = fn.apply(app());
+ return new AppInfoWithClassHierarchy(application, getSyntheticItems().commit(application));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 3bacf89..ccdfd2f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -126,7 +126,8 @@
}
public static AppView<AppInfoWithClassHierarchy> createForR8(DexApplication application) {
- AppInfoWithClassHierarchy appInfo = new AppInfoWithClassHierarchy(application);
+ AppInfoWithClassHierarchy appInfo =
+ AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(application);
return new AppView<>(
appInfo, WholeProgramOptimizations.ON, defaultPrefixRewritingMapper(appInfo));
}
@@ -396,6 +397,15 @@
return collection;
}
+ public boolean hasBeenMerged(DexProgramClass clazz) {
+ // TODO(b/165227525): Add support for the horizontal class merger here.
+ if (horizontallyMergedLambdaClasses != null
+ && horizontallyMergedLambdaClasses.hasBeenMerged(clazz)) {
+ return true;
+ }
+ return verticallyMergedClasses != null && verticallyMergedClasses.hasBeenMerged(clazz);
+ }
+
// Get the result of horizontal lambda class merging. Returns null if horizontal lambda class
// merging has not been run.
public HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses() {
@@ -470,16 +480,24 @@
}
public void rewriteWithLens(NestedGraphLens lens) {
- rewriteWithLens(lens, withLiveness());
+ if (lens != null) {
+ rewriteWithLens(lens, appInfo().app().asDirect(), withLiveness());
+ }
}
- private static void rewriteWithLens(NestedGraphLens lens, AppView<AppInfoWithLiveness> appView) {
- if (lens == null) {
- return;
- }
+ public void rewriteWithLensAndApplication(
+ NestedGraphLens lens, DirectMappedDexApplication application) {
+ assert lens != null;
+ assert application != null;
+ rewriteWithLens(lens, application, withLiveness());
+ }
+
+ private static void rewriteWithLens(
+ NestedGraphLens lens,
+ DirectMappedDexApplication application,
+ AppView<AppInfoWithLiveness> appView) {
boolean changed = appView.setGraphLens(lens);
assert changed;
- DirectMappedDexApplication application = appView.appInfo().app().asDirect();
assert application.verifyWithLens(lens);
appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
}
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index f1ddad4..491178a 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -48,7 +48,7 @@
this.writeIR = writeIR;
this.writeCode = writeCode;
if (writeIR) {
- this.appInfo = new AppInfo(application.toDirect());
+ this.appInfo = AppInfo.createInitialAppInfo(application.toDirect());
if (options.programConsumer == null) {
// Use class-file backend, since the CF frontend for testing does not support desugaring of
// synchronized methods for the DEX backend (b/109789541).
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
deleted file mode 100644
index a267d5b..0000000
--- a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph;
-
-public class DefaultUseRegistry extends UseRegistry {
-
- public DefaultUseRegistry(DexItemFactory factory) {
- super(factory);
- }
-
- @Override
- public boolean registerInitClass(DexType clazz) {
- return true;
- }
-
- @Override
- public boolean registerInvokeVirtual(DexMethod method) {
- return true;
- }
-
- @Override
- public boolean registerInvokeDirect(DexMethod method) {
- return true;
- }
-
- @Override
- public boolean registerInvokeStatic(DexMethod method) {
- return true;
- }
-
- @Override
- public boolean registerInvokeInterface(DexMethod method) {
- return true;
- }
-
- @Override
- public boolean registerInvokeSuper(DexMethod method) {
- return true;
- }
-
- @Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return true;
- }
-
- @Override
- public boolean registerInstanceFieldRead(DexField field) {
- return true;
- }
-
- @Override
- public boolean registerNewInstance(DexType type) {
- return true;
- }
-
- @Override
- public boolean registerStaticFieldRead(DexField field) {
- return true;
- }
-
- @Override
- public boolean registerStaticFieldWrite(DexField field) {
- return true;
- }
-
- @Override
- public boolean registerTypeReference(DexType type) {
- return true;
- }
-
- @Override
- public boolean registerInstanceOf(DexType type) {
- return true;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index a471dc2..d6d69a6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -208,6 +208,11 @@
return mainDexList;
}
+ public Builder<T> addToMainDexList(DexType mainDex) {
+ mainDexList.add(mainDex);
+ return this;
+ }
+
public Builder<T> addToMainDexList(Collection<DexType> mainDexList) {
this.mainDexList.addAll(mainDexList);
return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index ae39752..fe2eabd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -108,6 +108,11 @@
}
}
+ @Override
+ public ClassAccessFlags getAccessFlags() {
+ return accessFlags;
+ }
+
public Iterable<DexEncodedField> fields() {
return fields(Predicates.alwaysTrue());
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index 09daf79..6168bd8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -25,6 +25,8 @@
return annotations;
}
+ public abstract AccessFlags<?> getAccessFlags();
+
public DexAnnotationSet liveAnnotations(AppView<AppInfoWithLiveness> appView) {
return annotations.keepIf(
annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 7a85dc8..5d9fa45 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
@@ -76,6 +77,11 @@
return kotlinMemberInfo;
}
+ @Override
+ public FieldAccessFlags getAccessFlags() {
+ return accessFlags;
+ }
+
public void setKotlinMemberInfo(KotlinFieldLevelInfo kotlinMemberInfo) {
assert this.kotlinMemberInfo == NO_KOTLIN_INFO;
this.kotlinMemberInfo = kotlinMemberInfo;
@@ -126,6 +132,20 @@
return this;
}
+ @Override
+ public ProgramField asProgramMember(DexDefinitionSupplier definitions) {
+ return asProgramField(definitions);
+ }
+
+ public ProgramField asProgramField(DexDefinitionSupplier definitions) {
+ assert holder().isClassType();
+ DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(field));
+ if (clazz != null) {
+ return new ProgramField(clazz, this);
+ }
+ return null;
+ }
+
public boolean isEnum() {
return accessFlags.isEnum();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 638a9be..df06501 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -27,6 +27,8 @@
return this;
}
+ public abstract ProgramMember<D, R> asProgramMember(DexDefinitionSupplier definitions);
+
@Override
public final boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index cab8542..b087d77 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -180,8 +180,9 @@
obsolete = true;
}
- public CompilationState getCompilationState() {
- return compilationState;
+ @Override
+ public MethodAccessFlags getAccessFlags() {
+ return accessFlags;
}
public DexEncodedMethod getDefaultInterfaceMethodImplementation() {
@@ -321,6 +322,11 @@
return false;
}
+ @Override
+ public ProgramMethod asProgramMember(DexDefinitionSupplier definitions) {
+ return asProgramMethod(definitions);
+ }
+
public ProgramMethod asProgramMethod(DexDefinitionSupplier definitions) {
assert method.holder.isClassType();
DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(method));
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index fc7ae3e..1190570 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -280,11 +280,13 @@
return isDoubleType() || isLongType();
}
+ // TODO(b/158159959): Remove usage of name-based identification.
public boolean isD8R8SynthesizedLambdaClassType() {
String name = toSourceString();
return name.contains(LAMBDA_CLASS_NAME_PREFIX);
}
+ // TODO(b/158159959): Remove usage of name-based identification.
public boolean isD8R8SynthesizedClassType() {
String name = toSourceString();
return name.contains(COMPANION_CLASS_NAME_SUFFIX)
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 612329c..08e5190 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -19,7 +19,7 @@
import java.util.List;
import java.util.Map;
-public class DirectMappedDexApplication extends DexApplication implements DexDefinitionSupplier {
+public class DirectMappedDexApplication extends DexApplication {
// Mapping from code objects to their encoded-method owner. Used for asserting unique ownership
// and debugging purposes.
@@ -73,6 +73,21 @@
return classpathClasses;
}
+ public DexDefinitionSupplier getDefinitionsSupplier(SyntheticItems syntheticItems) {
+ DirectMappedDexApplication self = this;
+ return new DexDefinitionSupplier() {
+ @Override
+ public DexClass definitionFor(DexType type) {
+ return syntheticItems.definitionFor(type, self::definitionFor);
+ }
+
+ @Override
+ public DexItemFactory dexItemFactory() {
+ return self.dexItemFactory;
+ }
+ };
+ }
+
@Override
public DexClass definitionFor(DexType type) {
assert type.isClassType() : "Cannot lookup definition for type: " + type;
@@ -80,11 +95,6 @@
}
@Override
- public DexItemFactory dexItemFactory() {
- return dexItemFactory;
- }
-
- @Override
public DexProgramClass programDefinitionFor(DexType type) {
// The direct mapped application has no duplicates so this coincides with definitionFor.
return DexProgramClass.asProgramClassOrNull(definitionFor(type));
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 0d2390b..486996e 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -6,7 +6,8 @@
import com.android.tools.r8.utils.OptionalBool;
-public abstract class FieldResolutionResult {
+public abstract class FieldResolutionResult
+ implements MemberResolutionResult<DexEncodedField, DexField> {
public static FailedFieldResolutionResult failure() {
return FailedFieldResolutionResult.INSTANCE;
@@ -31,11 +32,26 @@
return null;
}
+ @Override
+ public boolean isSuccessfulMemberResolutionResult() {
+ return false;
+ }
+
+ @Override
+ public SuccessfulFieldResolutionResult asSuccessfulMemberResolutionResult() {
+ return null;
+ }
+
public boolean isFailedOrUnknownResolution() {
return false;
}
- public static class SuccessfulFieldResolutionResult extends FieldResolutionResult {
+ public DexClass getInitialResolutionHolder() {
+ return null;
+ }
+
+ public static class SuccessfulFieldResolutionResult extends FieldResolutionResult
+ implements SuccessfulMemberResolutionResult<DexEncodedField, DexField> {
private final DexClass initialResolutionHolder;
private final DexClass resolvedHolder;
@@ -49,10 +65,12 @@
this.resolvedField = resolvedField;
}
+ @Override
public DexClass getInitialResolutionHolder() {
return initialResolutionHolder;
}
+ @Override
public DexClass getResolvedHolder() {
return resolvedHolder;
}
@@ -62,6 +80,11 @@
return resolvedField;
}
+ @Override
+ public DexEncodedField getResolvedMember() {
+ return resolvedField;
+ }
+
public DexClassAndField getResolutionPair() {
return DexClassAndField.create(resolvedHolder, resolvedField);
}
@@ -81,6 +104,16 @@
public SuccessfulFieldResolutionResult asSuccessfulResolution() {
return this;
}
+
+ @Override
+ public boolean isSuccessfulMemberResolutionResult() {
+ return true;
+ }
+
+ @Override
+ public SuccessfulFieldResolutionResult asSuccessfulMemberResolutionResult() {
+ return this;
+ }
}
public static class FailedFieldResolutionResult extends FieldResolutionResult {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index bf6c5c8..07b3205 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -347,9 +347,9 @@
}
public boolean verifyMappingToOriginalProgram(
- Iterable<DexProgramClass> classes,
- DexApplication originalApplication,
- DexItemFactory dexItemFactory) {
+ AppView<?> appView, DexApplication originalApplication) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ Iterable<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
// Collect all original fields and methods for efficient querying.
Set<DexField> originalFields = Sets.newIdentityHashSet();
Set<DexMethod> originalMethods = Sets.newIdentityHashSet();
@@ -365,7 +365,7 @@
// Check that all fields and methods in the generated program can be mapped back to one of the
// original fields or methods.
for (DexProgramClass clazz : classes) {
- if (clazz.type.isD8R8SynthesizedClassType()) {
+ if (appView.appInfo().getSyntheticItems().isSyntheticClass(clazz)) {
continue;
}
for (DexEncodedField field : clazz.fields()) {
diff --git a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
new file mode 100644
index 0000000..da70e09
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+public interface MemberResolutionResult<
+ D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+
+ boolean isSuccessfulMemberResolutionResult();
+
+ SuccessfulMemberResolutionResult<D, R> asSuccessfulMemberResolutionResult();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 0997dc4..fb6e5d1 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -370,21 +370,6 @@
DexDefinitionSupplier definitions,
GraphLens lens) {
instantiatedHierarchy = null;
- objectAllocationInfos.classesWithAllocationSiteTracking.forEach(
- (clazz, allocationSitesForClass) -> {
- DexType type = lens.lookupType(clazz.type);
- if (type.isPrimitiveType()) {
- assert !objectAllocationInfos.hasInstantiatedStrictSubtype(clazz);
- return;
- }
- DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
- assert rewrittenClass != null;
- assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
- classesWithAllocationSiteTracking.put(
- rewrittenClass,
- LensUtils.rewrittenWithRenamedSignature(
- allocationSitesForClass, definitions, lens));
- });
objectAllocationInfos.classesWithoutAllocationSiteTracking.forEach(
clazz -> {
DexType type = lens.lookupType(clazz.type);
@@ -394,10 +379,28 @@
}
DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
assert rewrittenClass != null;
- assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
- assert !classesWithoutAllocationSiteTracking.contains(rewrittenClass);
classesWithoutAllocationSiteTracking.add(rewrittenClass);
});
+ objectAllocationInfos.classesWithAllocationSiteTracking.forEach(
+ (clazz, allocationSitesForClass) -> {
+ DexType type = lens.lookupType(clazz.type);
+ if (type.isPrimitiveType()) {
+ assert !objectAllocationInfos.hasInstantiatedStrictSubtype(clazz);
+ return;
+ }
+ DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
+ assert rewrittenClass != null;
+ if (classesWithoutAllocationSiteTracking.contains(rewrittenClass)) {
+ // Either this class was merged into another class without allocation site tracking,
+ // or a class without allocation site tracking was merged into this class.
+ return;
+ }
+ classesWithAllocationSiteTracking
+ .computeIfAbsent(rewrittenClass, ignore -> Sets.newIdentityHashSet())
+ .addAll(
+ LensUtils.rewrittenWithRenamedSignature(
+ allocationSitesForClass, definitions, lens));
+ });
for (DexProgramClass abstractType :
objectAllocationInfos.interfacesWithUnknownSubtypeHierarchy) {
DexType type = lens.lookupType(abstractType.type);
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 29949d8..3069f4e 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.graph;
-public class ProgramField extends DexClassAndField {
+public class ProgramField extends DexClassAndField
+ implements ProgramMember<DexEncodedField, DexField> {
public ProgramField(DexProgramClass holder, DexEncodedField field) {
super(holder, field);
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMember.java b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
new file mode 100644
index 0000000..7dc209c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+public interface ProgramMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+
+ D getDefinition();
+
+ DexType getHolderType();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 6ff5f1d..e69ade7 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -11,7 +11,8 @@
import com.android.tools.r8.origin.Origin;
/** Type representing a method definition in the programs compilation unit and its holder. */
-public final class ProgramMethod extends DexClassAndMethod {
+public final class ProgramMethod extends DexClassAndMethod
+ implements ProgramMember<DexEncodedMethod, DexMethod> {
public ProgramMethod(DexProgramClass holder, DexEncodedMethod method) {
super(holder, method);
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
new file mode 100644
index 0000000..28fc4e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.google.common.collect.Sets;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class ProgramPackage implements Iterable<DexProgramClass> {
+
+ private final String packageDescriptor;
+ private final Set<DexProgramClass> classes = Sets.newIdentityHashSet();
+
+ public ProgramPackage(String packageDescriptor) {
+ this.packageDescriptor = packageDescriptor;
+ }
+
+ public void add(DexProgramClass clazz) {
+ assert clazz.getType().getPackageDescriptor().equals(packageDescriptor);
+ classes.add(clazz);
+ }
+
+ public void forEachClass(Consumer<DexProgramClass> consumer) {
+ forEach(consumer);
+ }
+
+ public void forEachField(Consumer<ProgramField> consumer) {
+ forEach(clazz -> clazz.forEachProgramField(consumer));
+ }
+
+ public void forEachMethod(Consumer<ProgramMethod> consumer) {
+ forEach(clazz -> clazz.forEachProgramMethod(consumer));
+ }
+
+ @Override
+ public Iterator<DexProgramClass> iterator() {
+ return classes.iterator();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
new file mode 100644
index 0000000..57fc75a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class ProgramPackageCollection implements Iterable<ProgramPackage> {
+
+ private final Map<String, ProgramPackage> packages;
+
+ private ProgramPackageCollection(Map<String, ProgramPackage> packages) {
+ this.packages = packages;
+ }
+
+ public static ProgramPackageCollection create(AppView<?> appView) {
+ Map<String, ProgramPackage> packages = new HashMap<>();
+ assert !appView.appInfo().getSyntheticItems().hasPendingSyntheticClasses();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ packages
+ .computeIfAbsent(clazz.getType().getPackageDescriptor(), ProgramPackage::new)
+ .add(clazz);
+ }
+ return new ProgramPackageCollection(packages);
+ }
+
+ @Override
+ public Iterator<ProgramPackage> iterator() {
+ return packages.values().iterator();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index d8eab37..3316b64 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -18,7 +18,8 @@
import java.util.function.BiPredicate;
import java.util.function.Consumer;
-public abstract class ResolutionResult {
+public abstract class ResolutionResult
+ implements MemberResolutionResult<DexEncodedMethod, DexMethod> {
/**
* Returns true if resolution succeeded *and* the resolved method has a known definition.
@@ -36,6 +37,17 @@
return null;
}
+ @Override
+ public boolean isSuccessfulMemberResolutionResult() {
+ return false;
+ }
+
+ @Override
+ public SuccessfulMemberResolutionResult<DexEncodedMethod, DexMethod>
+ asSuccessfulMemberResolutionResult() {
+ return null;
+ }
+
/**
* Returns true if resolution failed.
*
@@ -59,6 +71,10 @@
return isSingleResolution() ? asSingleResolution().getResolvedMethod() : null;
}
+ public DexClass getInitialResolutionHolder() {
+ return null;
+ }
+
public abstract OptionalBool isAccessibleFrom(
DexProgramClass context, AppInfoWithClassHierarchy appInfo);
@@ -121,7 +137,8 @@
LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo);
/** Result for a resolution that succeeds with a known declaration/definition. */
- public static class SingleResolutionResult extends ResolutionResult {
+ public static class SingleResolutionResult extends ResolutionResult
+ implements SuccessfulMemberResolutionResult<DexEncodedMethod, DexMethod> {
private final DexClass initialResolutionHolder;
private final DexClass resolvedHolder;
private final DexEncodedMethod resolvedMethod;
@@ -141,10 +158,21 @@
|| initialResolutionHolder.type == resolvedMethod.holder();
}
+ @Override
+ public DexClass getInitialResolutionHolder() {
+ return initialResolutionHolder;
+ }
+
+ @Override
public DexClass getResolvedHolder() {
return resolvedHolder;
}
+ @Override
+ public DexEncodedMethod getResolvedMember() {
+ return resolvedMethod;
+ }
+
public DexEncodedMethod getResolvedMethod() {
return resolvedMethod;
}
@@ -164,6 +192,17 @@
}
@Override
+ public boolean isSuccessfulMemberResolutionResult() {
+ return true;
+ }
+
+ @Override
+ public SuccessfulMemberResolutionResult<DexEncodedMethod, DexMethod>
+ asSuccessfulMemberResolutionResult() {
+ return this;
+ }
+
+ @Override
public OptionalBool isAccessibleFrom(
DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
return AccessControl.isMethodAccessible(
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index dd2cde4..2c50bf8 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -325,20 +325,20 @@
private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
- private final boolean extraNullParameter;
+ private final int extraUnusedNullParameters;
private final ArgumentInfoCollection argumentInfoCollection;
private final RewrittenTypeInfo rewrittenReturnInfo;
private RewrittenPrototypeDescription() {
- this(false, null, ArgumentInfoCollection.empty());
+ this(0, null, ArgumentInfoCollection.empty());
}
private RewrittenPrototypeDescription(
- boolean extraNullParameter,
+ int extraUnusedNullParameters,
RewrittenTypeInfo rewrittenReturnInfo,
ArgumentInfoCollection argumentsInfo) {
assert argumentsInfo != null;
- this.extraNullParameter = extraNullParameter;
+ this.extraUnusedNullParameters = extraUnusedNullParameters;
this.rewrittenReturnInfo = rewrittenReturnInfo;
this.argumentInfoCollection = argumentsInfo;
}
@@ -350,12 +350,12 @@
DexType returnType = method.proto.returnType;
RewrittenTypeInfo returnInfo =
returnType.isAlwaysNull(appView) ? RewrittenTypeInfo.toVoid(returnType, appView) : null;
- return new RewrittenPrototypeDescription(false, returnInfo, removedArgumentsInfo);
+ return new RewrittenPrototypeDescription(0, returnInfo, removedArgumentsInfo);
}
public static RewrittenPrototypeDescription createForRewrittenTypes(
RewrittenTypeInfo returnInfo, ArgumentInfoCollection rewrittenArgumentsInfo) {
- return new RewrittenPrototypeDescription(false, returnInfo, rewrittenArgumentsInfo);
+ return new RewrittenPrototypeDescription(0, returnInfo, rewrittenArgumentsInfo);
}
public static RewrittenPrototypeDescription none() {
@@ -363,11 +363,13 @@
}
public boolean isEmpty() {
- return !extraNullParameter && rewrittenReturnInfo == null && argumentInfoCollection.isEmpty();
+ return extraUnusedNullParameters == 0
+ && rewrittenReturnInfo == null
+ && argumentInfoCollection.isEmpty();
}
- public boolean hasExtraNullParameter() {
- return extraNullParameter;
+ public int numberOfExtraUnusedNullParameters() {
+ return extraUnusedNullParameters;
}
public boolean hasBeenChangedToReturnVoid(AppView<?> appView) {
@@ -418,7 +420,7 @@
assert rewrittenReturnInfo == null;
return !hasBeenChangedToReturnVoid(appView)
? new RewrittenPrototypeDescription(
- extraNullParameter,
+ extraUnusedNullParameters,
RewrittenTypeInfo.toVoid(oldReturnType, appView),
argumentInfoCollection)
: this;
@@ -426,12 +428,21 @@
public RewrittenPrototypeDescription withRemovedArguments(ArgumentInfoCollection other) {
return new RewrittenPrototypeDescription(
- extraNullParameter, rewrittenReturnInfo, argumentInfoCollection.combine(other));
+ extraUnusedNullParameters, rewrittenReturnInfo, argumentInfoCollection.combine(other));
}
- public RewrittenPrototypeDescription withExtraNullParameter() {
- return !extraNullParameter
- ? new RewrittenPrototypeDescription(true, rewrittenReturnInfo, argumentInfoCollection)
- : this;
+ public RewrittenPrototypeDescription withExtraUnusedNullParameter() {
+ return withExtraUnusedNullParameters(1);
+ }
+
+ public RewrittenPrototypeDescription withExtraUnusedNullParameters(
+ int numberOfExtraUnusedNullParameters) {
+ if (numberOfExtraUnusedNullParameters == 0) {
+ return this;
+ }
+ return new RewrittenPrototypeDescription(
+ extraUnusedNullParameters + numberOfExtraUnusedNullParameters,
+ rewrittenReturnInfo,
+ argumentInfoCollection);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java
new file mode 100644
index 0000000..84d89d0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+public interface SuccessfulMemberResolutionResult<
+ D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+
+ DexClass getInitialResolutionHolder();
+
+ DexClass getResolvedHolder();
+
+ D getResolvedMember();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/SyntheticItems.java b/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
new file mode 100644
index 0000000..20c073a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+public class SyntheticItems {
+
+ public static class CommittedItems {
+ // Set of all types that represent synthesized items.
+ private final ImmutableSet<DexType> syntheticTypes;
+
+ private CommittedItems(ImmutableSet<DexType> syntheticTypes) {
+ this.syntheticTypes = syntheticTypes;
+ }
+
+ SyntheticItems toSyntheticItems() {
+ return new SyntheticItems(syntheticTypes);
+ }
+ }
+
+ // Thread safe collection of synthesized classes that are not yet committed to the application.
+ private final Map<DexType, DexProgramClass> pendingClasses = new ConcurrentHashMap<>();
+
+ // Immutable set of types that represent synthetic definitions in the application (eg, committed).
+ private final ImmutableSet<DexType> syntheticTypes;
+
+ private SyntheticItems(ImmutableSet<DexType> syntheticTypes) {
+ this.syntheticTypes = syntheticTypes;
+ }
+
+ public static SyntheticItems createInitialSyntheticItems() {
+ return new SyntheticItems(ImmutableSet.of());
+ }
+
+ public boolean hasPendingSyntheticClasses() {
+ return !pendingClasses.isEmpty();
+ }
+
+ public Collection<DexProgramClass> getPendingSyntheticClasses() {
+ return Collections.unmodifiableCollection(pendingClasses.values());
+ }
+
+ public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
+ DexProgramClass pending = pendingClasses.get(type);
+ if (pending != null) {
+ assert baseDefinitionFor.apply(type) == null
+ : "Pending synthetic definition also present in the active program: " + type;
+ return pending;
+ }
+ return baseDefinitionFor.apply(type);
+ }
+
+ // TODO(b/158159959): Remove the usage of this direct class addition (and name-based id).
+ public void addSyntheticClass(DexProgramClass clazz) {
+ assert clazz.type.isD8R8SynthesizedClassType();
+ assert !syntheticTypes.contains(clazz.type);
+ DexProgramClass previous = pendingClasses.put(clazz.type, clazz);
+ assert previous == null || previous == clazz;
+ }
+
+ public boolean isSyntheticClass(DexType type) {
+ return syntheticTypes.contains(type)
+ || pendingClasses.containsKey(type)
+ // TODO(b/158159959): Remove usage of name-based identification.
+ || type.isD8R8SynthesizedClassType();
+ }
+
+ public boolean isSyntheticClass(DexProgramClass clazz) {
+ return isSyntheticClass(clazz.type);
+ }
+
+ public CommittedItems commit(DexApplication application) {
+ assert verifyAllPendingSyntheticsAreInApp(application, this);
+ // All synthetics are in the app proper and no further meta-data is present so the empty
+ // collection is currently returned here.
+ ImmutableSet<DexType> merged = syntheticTypes;
+ if (!pendingClasses.isEmpty()) {
+ merged =
+ ImmutableSet.<DexType>builder()
+ .addAll(syntheticTypes)
+ .addAll(pendingClasses.keySet())
+ .build();
+ }
+ return new CommittedItems(merged);
+ }
+
+ public CommittedItems commit(DexApplication application, NestedGraphLens lens) {
+ return new CommittedItems(lens.rewriteTypes(commit(application).syntheticTypes));
+ }
+
+ private static boolean verifyAllPendingSyntheticsAreInApp(
+ DexApplication app, SyntheticItems synthetics) {
+ for (DexProgramClass clazz : synthetics.getPendingSyntheticClasses()) {
+ assert app.programDefinitionFor(clazz.type) != null;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index de4021e..75e6565 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -17,54 +17,54 @@
this.factory = factory;
}
- public abstract boolean registerInitClass(DexType type);
+ public abstract void registerInitClass(DexType type);
- public abstract boolean registerInvokeVirtual(DexMethod method);
+ public abstract void registerInvokeVirtual(DexMethod method);
- public abstract boolean registerInvokeDirect(DexMethod method);
+ public abstract void registerInvokeDirect(DexMethod method);
- public abstract boolean registerInvokeStatic(DexMethod method);
+ public abstract void registerInvokeStatic(DexMethod method);
- public abstract boolean registerInvokeInterface(DexMethod method);
+ public abstract void registerInvokeInterface(DexMethod method);
- public abstract boolean registerInvokeSuper(DexMethod method);
+ public abstract void registerInvokeSuper(DexMethod method);
- public abstract boolean registerInstanceFieldRead(DexField field);
+ public abstract void registerInstanceFieldRead(DexField field);
- public boolean registerInstanceFieldReadFromMethodHandle(DexField field) {
- return registerInstanceFieldRead(field);
+ public void registerInstanceFieldReadFromMethodHandle(DexField field) {
+ registerInstanceFieldRead(field);
}
- public abstract boolean registerInstanceFieldWrite(DexField field);
+ public abstract void registerInstanceFieldWrite(DexField field);
- public boolean registerInstanceFieldWriteFromMethodHandle(DexField field) {
- return registerInstanceFieldWrite(field);
+ public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
+ registerInstanceFieldWrite(field);
}
- public abstract boolean registerNewInstance(DexType type);
+ public abstract void registerNewInstance(DexType type);
- public abstract boolean registerStaticFieldRead(DexField field);
+ public abstract void registerStaticFieldRead(DexField field);
- public boolean registerStaticFieldReadFromMethodHandle(DexField field) {
- return registerStaticFieldRead(field);
+ public void registerStaticFieldReadFromMethodHandle(DexField field) {
+ registerStaticFieldRead(field);
}
- public abstract boolean registerStaticFieldWrite(DexField field);
+ public abstract void registerStaticFieldWrite(DexField field);
- public boolean registerStaticFieldWriteFromMethodHandle(DexField field) {
- return registerStaticFieldWrite(field);
+ public void registerStaticFieldWriteFromMethodHandle(DexField field) {
+ registerStaticFieldWrite(field);
}
- public abstract boolean registerTypeReference(DexType type);
+ public abstract void registerTypeReference(DexType type);
- public abstract boolean registerInstanceOf(DexType type);
+ public abstract void registerInstanceOf(DexType type);
- public boolean registerConstClass(DexType type) {
- return registerTypeReference(type);
+ public void registerConstClass(DexType type) {
+ registerTypeReference(type);
}
- public boolean registerCheckCast(DexType type) {
- return registerTypeReference(type);
+ public void registerCheckCast(DexType type) {
+ registerTypeReference(type);
}
public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
index 1facc45..fc8e49b 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.graph.classmerging;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Set;
@@ -27,4 +28,9 @@
}
return true;
}
+
+ @Override
+ public boolean hasBeenMerged(DexProgramClass clazz) {
+ return sources.contains(clazz.type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
index 7b9bba3..66f9f6a 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClasses.java
@@ -5,9 +5,12 @@
package com.android.tools.r8.graph.classmerging;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
public interface MergedClasses {
boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView);
+
+ boolean hasBeenMerged(DexProgramClass clazz);
}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
index ad7c71d..1ced397 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/MergedClassesCollection.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.graph.classmerging;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.ArrayList;
import java.util.List;
@@ -24,4 +25,14 @@
}
return true;
}
+
+ @Override
+ public boolean hasBeenMerged(DexProgramClass clazz) {
+ for (MergedClasses mergedClasses : collection) {
+ if (mergedClasses.hasBeenMerged(clazz)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
index 944d680..d4c730a 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.graph.classmerging;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
@@ -53,4 +54,9 @@
}
return true;
}
+
+ @Override
+ public boolean hasBeenMerged(DexProgramClass clazz) {
+ return hasBeenMergedIntoSubtype(clazz.type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java
new file mode 100644
index 0000000..37222de
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+public class FieldMultiset {
+
+ private final Multiset<DexType> fields = HashMultiset.create();
+
+ public FieldMultiset(DexProgramClass clazz) {
+ for (DexEncodedField field : clazz.instanceFields()) {
+ fields.add(field.type());
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return fields.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof FieldMultiset) {
+ FieldMultiset other = (FieldMultiset) object;
+ return fields.equals(other.fields);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
new file mode 100644
index 0000000..56da905
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexClasses;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class HorizontalClassMerger {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final MainDexClasses mainDexClasses;
+
+ private final PolicyExecutor policyExecutor;
+
+ public HorizontalClassMerger(
+ AppView<AppInfoWithLiveness> appView, MainDexClasses mainDexClasses) {
+ this.appView = appView;
+ this.mainDexClasses = mainDexClasses;
+
+ Policy[] policies = {
+ // TODO: add policies
+ };
+ this.policyExecutor = new SimplePolicyExecutor(Arrays.asList(policies));
+ }
+
+ public Collection<Collection<DexProgramClass>> run() {
+ Map<FieldMultiset, Collection<DexProgramClass>> classes = new HashMap<>();
+
+ // Group classes by same field signature using the hash map.
+ for (DexProgramClass clazz : appView.appInfo().app().classesWithDeterministicOrder()) {
+ classes.computeIfAbsent(new FieldMultiset(clazz), ignore -> new ArrayList<>()).add(clazz);
+ }
+
+ // Run the policies on all collected classes to produce a final grouping.
+ Collection<Collection<DexProgramClass>> groups = policyExecutor.run(classes.values());
+
+ return groups;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
new file mode 100644
index 0000000..261e8d2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import java.util.Collection;
+
+public abstract class MultiClassPolicy extends Policy {
+
+ /**
+ * Apply the multi class policy to a group of program classes.
+ *
+ * @param group This is a group of program classes which can currently still be merged.
+ * @return The same collection of program classes split into new groups of candidates which can be
+ * merged. If the policy detects no issues then `group` will be returned unchanged. If classes
+ * cannot be merged with any other classes they are returned as singleton lists.
+ */
+ public abstract Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group);
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
new file mode 100644
index 0000000..d8dea98
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+/**
+ * The super class of all horizontal class merging policies. Most classes will either implement
+ * {@link SingleClassPolicy} or {@link MultiClassPolicy}.
+ */
+public abstract class Policy {}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
new file mode 100644
index 0000000..7ffaa1b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import java.util.Collection;
+
+public abstract class PolicyExecutor {
+ protected final Collection<Policy> policies;
+
+ public PolicyExecutor(Collection<Policy> policies) {
+ this.policies = policies;
+ }
+
+ /**
+ * Given an initial collection of class groups which can potentially be merged, run all of the
+ * policies registered to this policy executor on the class groups yielding a new collection of
+ * class groups.
+ */
+ public abstract Collection<Collection<DexProgramClass>> run(
+ Collection<Collection<DexProgramClass>> classes);
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
new file mode 100644
index 0000000..3e6254c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.stream.Collectors;
+
+/**
+ * This is a simple policy executor that ensures regular sequential execution of policies. It should
+ * primarily be readable and correct. The SimplePolicyExecutor should be a reference implementation,
+ * against which more efficient policy executors can be compared.
+ */
+public class SimplePolicyExecutor extends PolicyExecutor {
+ public SimplePolicyExecutor(Collection<Policy> policies) {
+ super(policies);
+ }
+
+ // TODO(b/165506334): if performing mutable operation ensure that linked lists are used
+ private Collection<Collection<DexProgramClass>> applySingleClassPolicy(
+ SingleClassPolicy policy, Collection<Collection<DexProgramClass>> groups) {
+ Iterator<Collection<DexProgramClass>> i = groups.iterator();
+ while (i.hasNext()) {
+ Collection<DexProgramClass> group = i.next();
+ Iterator<DexProgramClass> j = group.iterator();
+ while (j.hasNext()) {
+ DexProgramClass clazz = j.next();
+ if (!policy.canMerge(clazz)) {
+ j.remove();
+ }
+ }
+ if (group.isEmpty()) {
+ i.remove();
+ }
+ }
+ return groups;
+ }
+
+ private Collection<Collection<DexProgramClass>> applyMultiClassPolicy(
+ MultiClassPolicy policy, Collection<Collection<DexProgramClass>> groups) {
+ // For each group apply the multi class policy and add all the new groups together.
+ return groups.stream()
+ .flatMap(group -> policy.apply(group).stream())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Collection<Collection<DexProgramClass>> run(
+ Collection<Collection<DexProgramClass>> groups) {
+ for (Policy policy : policies) {
+ if (policy instanceof SingleClassPolicy) {
+ groups = applySingleClassPolicy((SingleClassPolicy) policy, groups);
+ } else if (policy instanceof MultiClassPolicy) {
+ groups = applyMultiClassPolicy((MultiClassPolicy) policy, groups);
+ }
+ }
+
+ return groups;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
new file mode 100644
index 0000000..b0757c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+public abstract class SingleClassPolicy extends Policy {
+ /**
+ * Determine if {@param program} can be merged with any other classes.
+ *
+ * @return {@code false} if the class should not be merged, otherwise {@code true}.
+ */
+ public abstract boolean canMerge(DexProgramClass program);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index f06a1c7..8d33495 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -183,68 +183,50 @@
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return registerFieldAccess(field, false);
+ public void registerInstanceFieldWrite(DexField field) {
+ registerFieldAccess(field, false);
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
- return registerFieldAccess(field, false);
+ public void registerInstanceFieldRead(DexField field) {
+ registerFieldAccess(field, false);
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
- return registerFieldAccess(field, true);
+ public void registerStaticFieldRead(DexField field) {
+ registerFieldAccess(field, true);
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
- return registerFieldAccess(field, true);
+ public void registerStaticFieldWrite(DexField field) {
+ registerFieldAccess(field, true);
}
@Override
- public boolean registerInitClass(DexType clazz) {
- return false;
- }
+ public void registerInitClass(DexType clazz) {}
@Override
- public boolean registerInvokeVirtual(DexMethod method) {
- return false;
- }
+ public void registerInvokeVirtual(DexMethod method) {}
@Override
- public boolean registerInvokeDirect(DexMethod method) {
- return false;
- }
+ public void registerInvokeDirect(DexMethod method) {}
@Override
- public boolean registerInvokeStatic(DexMethod method) {
- return false;
- }
+ public void registerInvokeStatic(DexMethod method) {}
@Override
- public boolean registerInvokeInterface(DexMethod method) {
- return false;
- }
+ public void registerInvokeInterface(DexMethod method) {}
@Override
- public boolean registerInvokeSuper(DexMethod method) {
- return false;
- }
+ public void registerInvokeSuper(DexMethod method) {}
@Override
- public boolean registerNewInstance(DexType type) {
- return false;
- }
+ public void registerNewInstance(DexType type) {}
@Override
- public boolean registerTypeReference(DexType type) {
- return false;
- }
+ public void registerTypeReference(DexType type) {}
@Override
- public boolean registerInstanceOf(DexType type) {
- return false;
- }
+ public void registerInstanceOf(DexType type) {}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index d5fdc33..2ef11c9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -37,12 +37,12 @@
* traced manually by {@link ProtoEnqueuerExtension#tracePendingInstructionsInDynamicMethods}.
*/
@Override
- public boolean registerConstClass(DexType type) {
+ public void registerConstClass(DexType type) {
if (references.isDynamicMethod(getContextMethod())) {
enqueuer.addDeadProtoTypeCandidate(type);
- return false;
+ return;
}
- return super.registerConstClass(type);
+ super.registerConstClass(type);
}
/**
@@ -53,11 +53,11 @@
* traced manually by {@link ProtoEnqueuerExtension#tracePendingInstructionsInDynamicMethods}.
*/
@Override
- public boolean registerStaticFieldRead(DexField field) {
+ public void registerStaticFieldRead(DexField field) {
if (references.isDynamicMethod(getContextMethod())) {
enqueuer.addDeadProtoTypeCandidate(field.holder);
- return false;
+ return;
}
- return super.registerStaticFieldRead(field);
+ super.registerStaticFieldRead(field);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 07783f5..52ab07a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -300,82 +300,67 @@
}
@Override
- public boolean registerInitClass(DexType clazz) {
+ public void registerInitClass(DexType clazz) {
processInitClass(clazz);
- return false;
}
@Override
- public boolean registerInvokeVirtual(DexMethod method) {
+ public void registerInvokeVirtual(DexMethod method) {
processInvoke(Invoke.Type.VIRTUAL, method);
- return false;
}
@Override
- public boolean registerInvokeDirect(DexMethod method) {
+ public void registerInvokeDirect(DexMethod method) {
processInvoke(Invoke.Type.DIRECT, method);
- return false;
}
@Override
- public boolean registerInvokeStatic(DexMethod method) {
+ public void registerInvokeStatic(DexMethod method) {
processInvoke(Invoke.Type.STATIC, method);
- return false;
}
@Override
- public boolean registerInvokeInterface(DexMethod method) {
+ public void registerInvokeInterface(DexMethod method) {
processInvoke(Invoke.Type.INTERFACE, method);
- return false;
}
@Override
- public boolean registerInvokeSuper(DexMethod method) {
+ public void registerInvokeSuper(DexMethod method) {
processInvoke(Invoke.Type.SUPER, method);
- return false;
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
+ public void registerInstanceFieldRead(DexField field) {
processFieldRead(field);
- return false;
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
+ public void registerInstanceFieldWrite(DexField field) {
processFieldWrite(field);
- return false;
}
@Override
- public boolean registerNewInstance(DexType type) {
+ public void registerNewInstance(DexType type) {
if (type.isClassType()) {
addClassInitializerTarget(type);
}
- return false;
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
+ public void registerStaticFieldRead(DexField field) {
processFieldRead(field);
- return false;
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
+ public void registerStaticFieldWrite(DexField field) {
processFieldWrite(field);
- return false;
}
@Override
- public boolean registerTypeReference(DexType type) {
- return false;
- }
+ public void registerTypeReference(DexType type) {}
@Override
- public boolean registerInstanceOf(DexType type) {
- return false;
- }
+ public void registerInstanceOf(DexType type) {}
@Override
public void registerCallSite(DexCallSite callSite) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 6b7b402..dfce72e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -538,13 +538,14 @@
register++;
}
- int numberOfArguments =
+ int originalNumberOfArguments =
method.method.proto.parameters.values.length
+ argumentsInfo.numberOfRemovedArguments()
- + (method.isStatic() ? 0 : 1);
+ + (method.isStatic() ? 0 : 1)
+ - prototypeChanges.numberOfExtraUnusedNullParameters();
int usedArgumentIndex = 0;
- while (argumentIndex < numberOfArguments) {
+ while (argumentIndex < originalNumberOfArguments) {
TypeElement type;
ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(argumentIndex);
if (argumentInfo.isRemovedArgumentInfo()) {
@@ -558,14 +559,14 @@
DexType argType;
if (argumentInfo.isRewrittenTypeInfo()) {
RewrittenTypeInfo argumentRewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo();
- assert method.method.proto.parameters.values[usedArgumentIndex]
+ assert method.method.proto.getParameter(usedArgumentIndex)
== argumentRewrittenTypeInfo.getNewType();
// The old type is used to prevent that a changed value from reference to primitive
// type breaks IR building. Rewriting from the old to the new type will be done in the
// IRConverter (typically through the lensCodeRewriter).
argType = argumentRewrittenTypeInfo.getOldType();
} else {
- argType = method.method.proto.parameters.values[usedArgumentIndex];
+ argType = method.method.proto.getParameter(usedArgumentIndex);
}
usedArgumentIndex++;
writeCallback.accept(register, argType);
@@ -579,6 +580,16 @@
register += type.requiredRegisters();
argumentIndex++;
}
+
+ for (int i = 0; i < prototypeChanges.numberOfExtraUnusedNullParameters(); i++) {
+ DexType argType = method.method.proto.getParameter(usedArgumentIndex);
+ assert argType.isClassType();
+ TypeElement type = TypeElement.fromDexType(argType, Nullability.maybeNull(), appView);
+ register += type.requiredRegisters();
+ usedArgumentIndex++;
+ addExtraUnusedNullArgument(register);
+ }
+
flushArgumentInstructions();
}
@@ -954,6 +965,14 @@
value.markAsThis();
}
+ private void addExtraUnusedNullArgument(int register) {
+ // Extra unused null arguments should bypass the register check, they may use registers
+ // beyond the limit of what the method can use. They don't have debug information and are
+ // always null.
+ Value value = new Value(valueNumberGenerator.next(), TypeElement.getNull(), null);
+ addNonThisArgument(new Argument(value, currentBlock.size(), false));
+ }
+
public void addNonThisArgument(int register, TypeElement typeLattice) {
DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 6cbf350..61a2f18 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -594,7 +594,7 @@
if (appView.options().enableNeverMergePrefixes) {
for (DexString neverMergePrefix : neverMergePrefixes) {
// Synthetic classes will always be merged.
- if (method.getHolderType().isD8R8SynthesizedClassType()) {
+ if (appView.appInfo().getSyntheticItems().isSyntheticClass(method.getHolder())) {
continue;
}
if (method.getHolderType().descriptor.startsWith(neverMergePrefix)) {
@@ -739,11 +739,7 @@
}
if (enumUnboxer != null) {
enumUnboxer.finishAnalysis();
- enumUnboxer.unboxEnums(
- postMethodProcessorBuilder,
- executorService,
- feedback,
- classStaticizer == null ? Collections.emptySet() : classStaticizer.getCandidates());
+ enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
}
if (!options.debug) {
new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
@@ -779,7 +775,7 @@
}
// Build a new application with jumbo string info.
- Builder<?> builder = application.builder();
+ Builder<?> builder = appView.appInfo().app().builder();
builder.setHighestSortingString(highestSortingString);
printPhase("Lambda class synthesis");
@@ -1767,11 +1763,6 @@
|| definition.getOptimizationInfo().isReachabilitySensitive()) {
return false;
}
- if (appView.options().enableEnumUnboxing && method.getHolder().isEnum()) {
- // Although the method is pinned, we compute the inlining constraint for enum unboxing,
- // but the inliner won't be able to inline the method (marked as pinned).
- return true;
- }
if (appView.appInfo().hasLiveness()
&& appView.appInfo().withLiveness().isPinned(method.getReference())) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 6c9877c..3e59e47 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -31,6 +31,7 @@
import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
import static com.android.tools.r8.utils.ObjectUtils.getBooleanOrElse;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
@@ -283,12 +284,21 @@
? null
: makeOutValue(invoke, code);
- if (prototypeChanges.hasExtraNullParameter()) {
+ if (prototypeChanges.numberOfExtraUnusedNullParameters() > 0) {
iterator.previous();
- Value extraNullValue =
+ Value nullInstruction =
iterator.insertConstNullInstruction(code, appView.options());
iterator.next();
- newInValues.add(extraNullValue);
+ for (int i = 0; i < prototypeChanges.numberOfExtraUnusedNullParameters(); i++) {
+ newInValues.add(nullInstruction);
+ }
+ // TODO(b/164901008): Fix when the number of arguments overflows.
+ if (newInValues.size() > 255) {
+ throw new CompilationError(
+ "The addition of extra unused null parameters in R8 led to the overflow of"
+ + " the number of arguments of the method "
+ + actualTarget);
+ }
}
assert newInValues.size()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 8c65c1e..347f495 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -105,7 +105,7 @@
if (androidApp != null) {
DexApplication app =
new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
- appInfo = new AppInfo(app);
+ appInfo = AppInfo.createInitialAppInfo(app);
}
AppView<?> appView = AppView.createForD8(appInfo, rewritePrefix);
BackportedMethodRewriter.RewritableMethods rewritableMethods =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 3709176..c4709c7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -85,7 +85,9 @@
Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
SortedProgramMethodSet nonDexAccessibilityBridges = SortedProgramMethodSet.create();
- for (LambdaClass lambdaClass : lambdaClasses) {
+ List<LambdaClass> sortedLambdaClasses = new ArrayList<>(lambdaClasses);
+ sortedLambdaClasses.sort((x, y) -> x.type.slowCompareTo(y.type));
+ for (LambdaClass lambdaClass : sortedLambdaClasses) {
// This call may cause originalMethodSignatures to be updated.
ProgramMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
if (accessibilityBridge != null
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index c7f6f2c..67571b3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -194,16 +194,10 @@
DexProgramClass::checksumFromType);
}
- void synthesizeNestConstructor() {
- synthesizeNestConstructor(null);
- }
-
void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
if (nestConstructorUsed) {
appView.appInfo().addSynthesizedClass(nestConstructor);
- if (builder != null) {
- builder.addSynthesizedClass(nestConstructor, true);
- }
+ builder.addSynthesizedClass(nestConstructor, true);
}
}
@@ -367,105 +361,97 @@
this.context = context;
}
- private boolean registerInvoke(DexMethod method, Invoke.Type invokeType) {
+ private void registerInvoke(DexMethod method, Invoke.Type invokeType) {
// Calls to non class type are not done through nest based access control.
// Work-around for calls to enum.clone().
if (!method.holder.isClassType()) {
- return false;
+ return;
}
DexEncodedMethod encodedMethod = lookupOnHolder(method, context, invokeType);
if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, context)) {
ensureInvokeBridge(encodedMethod);
- return true;
}
- return false;
}
- private boolean registerFieldAccess(DexField field, boolean isGet) {
+ private void registerFieldAccess(DexField field, boolean isGet) {
// Since we only need to desugar accesses to private fields, and all accesses to private
// fields must be accessing the private field directly on its holder, we can lookup the field
// on the holder instead of resolving the field.
DexEncodedField encodedField = lookupOnHolder(field);
if (encodedField != null && fieldAccessRequiresRewriting(encodedField, context)) {
ensureFieldAccessBridge(encodedField, isGet);
- return true;
}
- return false;
}
@Override
- public boolean registerInitClass(DexType clazz) {
+ public void registerInitClass(DexType clazz) {
// Nothing to do since we always use a public field for initializing the class.
- return true;
}
@Override
- public boolean registerInvokeVirtual(DexMethod method) {
+ public void registerInvokeVirtual(DexMethod method) {
// Calls to class nest mate private methods are targeted by invokeVirtual in jdk11.
// The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
- return registerInvoke(method, Invoke.Type.VIRTUAL);
+ registerInvoke(method, Invoke.Type.VIRTUAL);
}
@Override
- public boolean registerInvokeDirect(DexMethod method) {
- return registerInvoke(method, Invoke.Type.DIRECT);
+ public void registerInvokeDirect(DexMethod method) {
+ registerInvoke(method, Invoke.Type.DIRECT);
}
@Override
- public boolean registerInvokeStatic(DexMethod method) {
- return registerInvoke(method, Invoke.Type.STATIC);
+ public void registerInvokeStatic(DexMethod method) {
+ registerInvoke(method, Invoke.Type.STATIC);
}
@Override
- public boolean registerInvokeInterface(DexMethod method) {
+ public void registerInvokeInterface(DexMethod method) {
// Calls to interface nest mate private methods are targeted by invokeInterface in jdk11.
// The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
- return registerInvoke(method, Invoke.Type.INTERFACE);
+ registerInvoke(method, Invoke.Type.INTERFACE);
}
@Override
- public boolean registerInvokeSuper(DexMethod method) {
- return registerInvoke(method, Invoke.Type.SUPER);
+ public void registerInvokeSuper(DexMethod method) {
+ registerInvoke(method, Invoke.Type.SUPER);
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return registerFieldAccess(field, false);
+ public void registerInstanceFieldWrite(DexField field) {
+ registerFieldAccess(field, false);
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
- return registerFieldAccess(field, true);
+ public void registerInstanceFieldRead(DexField field) {
+ registerFieldAccess(field, true);
}
@Override
- public boolean registerNewInstance(DexType type) {
+ public void registerNewInstance(DexType type) {
// Unrelated to access based control.
// The <init> method has to be rewritten instead
// and <init> is called through registerInvoke.
- return false;
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
- return registerFieldAccess(field, true);
+ public void registerStaticFieldRead(DexField field) {
+ registerFieldAccess(field, true);
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
- return registerFieldAccess(field, false);
+ public void registerStaticFieldWrite(DexField field) {
+ registerFieldAccess(field, false);
}
@Override
- public boolean registerTypeReference(DexType type) {
+ public void registerTypeReference(DexType type) {
// Unrelated to access based control.
- return false;
}
@Override
- public boolean registerInstanceOf(DexType type) {
+ public void registerInstanceOf(DexType type) {
// Unrelated to access based control.
- return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
index cdca27a..2669724 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
@@ -100,7 +100,8 @@
if (isConstructorBridge(method)) {
// TODO (b/132767654): Try to write a test which breaks that assertion.
assert previousLens.lookupPrototypeChanges(method).isEmpty();
- return RewrittenPrototypeDescription.none().withExtraNullParameter();
+ // TODO(b/164901008): Fix when the number of arguments overflows.
+ return RewrittenPrototypeDescription.none().withExtraUnusedNullParameter();
} else {
return previousLens.lookupPrototypeChanges(method);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index d275f4a..e9ac116 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -32,14 +33,16 @@
super(appView);
}
- public NestedPrivateMethodLens run(ExecutorService executorService) throws ExecutionException {
+ public NestedPrivateMethodLens run(
+ ExecutorService executorService, DexApplication.Builder<?> appBuilder)
+ throws ExecutionException {
assert !appView.options().canUseNestBasedAccess()
|| appView.options().testing.enableForceNestBasedAccessDesugaringForTest;
computeAndProcessNestsConcurrently(executorService);
NestedPrivateMethodLens.Builder lensBuilder = NestedPrivateMethodLens.builder();
addDeferredBridgesAndMapMethods(lensBuilder);
clearNestAttributes();
- synthesizeNestConstructor();
+ synthesizeNestConstructor(appBuilder);
return lensBuilder.build(appView, getNestConstructorType());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index f1ed11b..cbb86df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -153,8 +153,7 @@
InternalOptions options = appView.options();
if (options.featureSplitConfiguration != null
- && !options.featureSplitConfiguration.inSameFeatureOrBase(
- singleTarget.getReference(), method.getReference())) {
+ && !options.featureSplitConfiguration.inSameFeatureOrBothInBase(singleTarget, method)) {
// Still allow inlining if we inline from the base into a feature.
if (!options.featureSplitConfiguration.isInBase(singleTarget.getHolder())) {
whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index a078c9b..89681f0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -23,7 +24,6 @@
import com.android.tools.r8.ir.code.InvokeSuper;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -166,10 +166,9 @@
if (holderClass == null || holderClass.isInterface()) {
continue;
}
+
// Due to the potential downcast below, make sure the new target holder is visible.
- ConstraintWithTarget visibility =
- ConstraintWithTarget.classIsVisible(context.getHolder(), holderType, appView);
- if (visibility == ConstraintWithTarget.NEVER) {
+ if (AccessControl.isClassAccessible(holderClass, context, appView).isPossiblyFalse()) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 67b6310..fc01ffe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -297,6 +297,13 @@
assert SUBCLASS.ordinal() < ALWAYS.ordinal();
}
+ public Constraint meet(Constraint otherConstraint) {
+ if (this.ordinal() < otherConstraint.ordinal()) {
+ return this;
+ }
+ return otherConstraint;
+ }
+
boolean isSet(int value) {
return (this.value & value) != 0;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 3b3d15d..f1bcf3e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -261,7 +261,6 @@
return newClass;
});
assert clazz != null;
- appView.appInfo().addSynthesizedClass(clazz);
return clazz;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index a74b4b6..3f910f5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -7,11 +7,17 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClass.FieldSetter;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -20,15 +26,19 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
+import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -56,9 +66,9 @@
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -71,7 +81,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -79,6 +88,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Predicate;
public class EnumUnboxer implements PostOptimization {
@@ -333,8 +343,7 @@
public void unboxEnums(
PostMethodProcessor.Builder postBuilder,
ExecutorService executorService,
- OptimizationFeedbackDelayed feedback,
- Set<DexType> hostsToAvoidIfPossible)
+ OptimizationFeedbackDelayed feedback)
throws ExecutionException {
// At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
if (enumsUnboxingCandidates.isEmpty()) {
@@ -344,13 +353,13 @@
// Update keep info on any of the enum methods of the removed classes.
updatePinnedItems(enumsToUnbox);
enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
- Map<DexType, DexType> newMethodLocation =
- findNewMethodLocationOfUnboxableEnums(enumsToUnbox, hostsToAvoidIfPossible);
+ DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
+ Map<DexType, DexType> newMethodLocation = synthesizeUnboxedEnumsMethodsLocations(appBuilder);
NestedGraphLens enumUnboxingLens =
new TreeFixer(enumsToUnbox).fixupTypeReferences(newMethodLocation);
appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
GraphLens previousLens = appView.graphLens();
- appView.rewriteWithLens(enumUnboxingLens);
+ appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
// Update optimization info.
feedback.fixupOptimizationInfos(
appView,
@@ -388,82 +397,68 @@
}
// Some enums may have methods which require to stay in the current package for accessibility,
- // in this case we find another class than the enum unboxing utility class to host these methods.
- private Map<DexType, DexType> findNewMethodLocationOfUnboxableEnums(
- Set<DexType> enumsToUnbox, Set<DexType> hostsToAvoidIfPossible) {
+ // in this case we create another class than the enum unboxing utility class to host these
+ // methods.
+ private Map<DexType, DexType> synthesizeUnboxedEnumsMethodsLocations(
+ DirectMappedDexApplication.Builder appBuilder) {
if (enumsToUnboxWithPackageRequirement.isEmpty()) {
return Collections.emptyMap();
}
Map<DexType, DexType> newMethodLocationMap = new IdentityHashMap<>();
- Map<String, DexProgramClass> packageToClassMap =
- getPackageToClassMapExcluding(enumsToUnbox, hostsToAvoidIfPossible);
+ Map<String, DexProgramClass> packageToClassMap = new HashMap<>();
for (DexType toUnbox : enumsToUnboxWithPackageRequirement) {
- DexProgramClass packageClass = packageToClassMap.get(toUnbox.getPackageDescriptor());
- if (packageClass != null) {
- newMethodLocationMap.put(toUnbox, packageClass.type);
+ String packageDescriptor = toUnbox.getPackageDescriptor();
+ DexProgramClass syntheticClass = packageToClassMap.get(packageDescriptor);
+ if (syntheticClass == null) {
+ syntheticClass = synthesizeUtilityClassInPackage(packageDescriptor, appBuilder);
+ packageToClassMap.put(packageDescriptor, syntheticClass);
}
+ if (appView.appInfo().isInMainDexList(toUnbox)) {
+ appBuilder.addToMainDexList(syntheticClass.type);
+ }
+ newMethodLocationMap.put(toUnbox, syntheticClass.type);
}
enumsToUnboxWithPackageRequirement.clear();
return newMethodLocationMap;
}
- // We are looking for another class in the same package as the unboxed enum to host the unboxed
- // enum methods. We go through all classes, and for each package where a host is needed, we
- // select a class.
- private Map<String, DexProgramClass> getPackageToClassMapExcluding(
- Set<DexType> enumsToUnbox, Set<DexType> hostsToAvoidIfPossible) {
- HashSet<String> relevantPackages = new HashSet<>();
- for (DexType toUnbox : enumsToUnbox) {
- relevantPackages.add(toUnbox.getPackageDescriptor());
+ private DexProgramClass synthesizeUtilityClassInPackage(
+ String packageDescriptor, DirectMappedDexApplication.Builder appBuilder) {
+ DexType type =
+ factory.createType(
+ "L"
+ + packageDescriptor
+ + "/"
+ + EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME
+ + ";");
+ if (type == factory.enumUnboxingUtilityType) {
+ return appView.definitionFor(type).asProgramClass();
}
-
- Map<String, DexProgramClass> packageToClassMap = new HashMap<>();
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- String packageDescriptor = clazz.type.getPackageDescriptor();
- if (relevantPackages.contains(packageDescriptor) && !enumsToUnbox.contains(clazz.type)) {
- DexProgramClass previousClass = packageToClassMap.get(packageDescriptor);
- if (previousClass == null) {
- packageToClassMap.put(packageDescriptor, clazz);
- } else {
- packageToClassMap.put(
- packageDescriptor, selectHost(clazz, previousClass, hostsToAvoidIfPossible));
- }
- }
- }
-
- return packageToClassMap;
+ DexProgramClass syntheticClass =
+ new DexProgramClass(
+ type,
+ null,
+ new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+ ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
+ factory.objectType,
+ DexTypeList.empty(),
+ null,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ factory.getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ appBuilder.addSynthesizedClass(syntheticClass, false);
+ appView.appInfo().addSynthesizedClass(syntheticClass);
+ return syntheticClass;
}
- // We are trying to select a host for the enum unboxing methods, but multiple candidates are
- // available. We need to pick one of the two classes and the result has to be deterministic.
- // We follow the heuristics, in order:
- // 1. don't pick a class from hostToAvoidIfPossible if possible
- // 2. pick the class with the least number of methods
- // 3. pick the first class name in alphabetical order for determinism.
- private DexProgramClass selectHost(
- DexProgramClass class1, DexProgramClass class2, Set<DexType> hostsToAvoidIfPossible) {
- boolean avoid1 = hostsToAvoidIfPossible.contains(class1.type);
- boolean avoid2 = hostsToAvoidIfPossible.contains(class2.type);
- if (avoid1 && !avoid2) {
- return class2;
- }
- if (avoid2 && !avoid1) {
- return class1;
- }
- int size1 = class1.getMethodCollection().size();
- int size2 = class2.getMethodCollection().size();
- if (size1 < size2) {
- return class1;
- }
- if (size2 < size1) {
- return class2;
- }
- assert class1.type != class2.type;
- if (class1.type.slowCompareTo(class2.type) < 0) {
- return class1;
- }
- return class2;
- }
private void updatePinnedItems(Set<DexType> enumsToUnbox) {
appView
@@ -504,11 +499,15 @@
private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) {
Constraint classConstraint = Constraint.ALWAYS;
+ EnumAccessibilityUseRegistry useRegistry = null;
for (DexEncodedMethod method : enumClass.methods()) {
// Enum initializer are analyzed in analyzeInitializers instead.
if (!method.isInitializer()) {
- Constraint methodConstraint = constraintForEnumUnboxing(method);
- classConstraint = meet(classConstraint, methodConstraint);
+ if (useRegistry == null) {
+ useRegistry = new EnumAccessibilityUseRegistry(factory);
+ }
+ Constraint methodConstraint = constraintForEnumUnboxing(method, useRegistry);
+ classConstraint = classConstraint.meet(methodConstraint);
if (classConstraint == Constraint.NEVER) {
return classConstraint;
}
@@ -517,45 +516,203 @@
return classConstraint;
}
- private Constraint meet(Constraint constraint1, Constraint constraint2) {
- if (constraint1 == Constraint.NEVER || constraint2 == Constraint.NEVER) {
- return Constraint.NEVER;
- }
- if (constraint1 == Constraint.ALWAYS) {
- return constraint2;
- }
- if (constraint2 == Constraint.ALWAYS) {
- return constraint1;
- }
- assert constraint1 == Constraint.PACKAGE && constraint2 == Constraint.PACKAGE;
- return Constraint.PACKAGE;
+ public Constraint constraintForEnumUnboxing(
+ DexEncodedMethod method, EnumAccessibilityUseRegistry useRegistry) {
+ return useRegistry.computeConstraint(method.asProgramMethod(appView));
}
- public Constraint constraintForEnumUnboxing(DexEncodedMethod method) {
- // TODO(b/160939354): Use a UseRegistry instead of inlining constraints.
- assert appView.definitionForProgramType(method.holder()) != null;
- assert appView.definitionForProgramType(method.holder()).isEnum();
- assert appView.definitionForProgramType(method.holder()).isEffectivelyFinal(appView);
- assert appView.definitionForProgramType(method.holder()).superType
- == appView.dexItemFactory().enumType;
- assert !appView.definitionForProgramType(method.holder()).isInANest()
- : "Unboxable enum is in a nest, this should not happen in cf to dex compilation, R8 needs"
- + " to take care of nest access control when relocating unboxable enums methods";
- switch (method.getCompilationState()) {
- case PROCESSED_INLINING_CANDIDATE_ANY:
+ private class EnumAccessibilityUseRegistry extends UseRegistry {
+
+ private ProgramMethod context;
+ private Constraint constraint;
+
+ public EnumAccessibilityUseRegistry(DexItemFactory factory) {
+ super(factory);
+ }
+
+ public Constraint computeConstraint(ProgramMethod method) {
+ constraint = Constraint.ALWAYS;
+ context = method;
+ method.registerCodeReferences(this);
+ return constraint;
+ }
+
+ public Constraint deriveConstraint(DexType targetHolder, AccessFlags<?> flags) {
+ DexProgramClass contextHolder = context.getHolder();
+ if (targetHolder == contextHolder.type) {
return Constraint.ALWAYS;
- case PROCESSED_INLINING_CANDIDATE_SAME_NEST:
- assert false;
- // fall through
- case PROCESSED_INLINING_CANDIDATE_SUBCLASS:
- // There is no such thing as subclass access in this context, enums analyzed here have no
- // subclasses, inherit directly from java.lang.Enum and are not analyzed if they call
- // the protected methods in java.lang.Enum and java.lang.Object.
- case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
- case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
- return Constraint.PACKAGE;
- default:
+ }
+ if (flags.isPublic()) {
+ return Constraint.ALWAYS;
+ }
+ if (flags.isPrivate()) {
+ // Enum unboxing is currently happening only cf to dex, and no class should be in a nest
+ // at this point. If that is the case, we just don't unbox the enum, or we would need to
+ // support Constraint.SAMENEST in the enum unboxer.
+ assert !contextHolder.isInANest();
+ // Only accesses within the enum are allowed since all enum methods and fields will be
+ // moved to the same class, and the enum itself becomes an integer, which is
+ // accessible everywhere.
return Constraint.NEVER;
+ }
+ assert flags.isProtected() || flags.isPackagePrivate();
+ // Protected is in practice equivalent to package private in this analysis since we are
+ // accessing the member from an enum context where subclassing is limited.
+ // At this point we don't support unboxing enums with subclasses, so we assume either
+ // same package access, or we just don't unbox.
+ // The only protected methods in java.lang.Enum are clone, finalize and the constructor.
+ // Besides calls to the constructor in the instance initializer, Enums with calls to such
+ // methods cannot be unboxed.
+ return targetHolder.isSamePackage(contextHolder.type) ? Constraint.PACKAGE : Constraint.NEVER;
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ if (type.isArrayType()) {
+ registerTypeReference(type.toBaseType(factory));
+ return;
+ }
+
+ if (type.isPrimitiveType()) {
+ return;
+ }
+
+ DexClass definition = appView.definitionFor(type);
+ if (definition == null) {
+ constraint = Constraint.NEVER;
+ return;
+ }
+ constraint = constraint.meet(deriveConstraint(type, definition.accessFlags));
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {
+ registerTypeReference(type);
+ }
+
+ @Override
+ public void registerInstanceOf(DexType type) {
+ registerTypeReference(type);
+ }
+
+ @Override
+ public void registerNewInstance(DexType type) {
+ registerTypeReference(type);
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ registerVirtualInvoke(method, false);
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ registerVirtualInvoke(method, true);
+ }
+
+ private void registerVirtualInvoke(DexMethod method, boolean isInterface) {
+ if (method.holder.isArrayType()) {
+ return;
+ }
+ // Perform resolution and derive unboxing constraints based on the accessibility of the
+ // resolution result.
+ ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method, isInterface);
+ if (!resolutionResult.isVirtualTarget()) {
+ constraint = Constraint.NEVER;
+ return;
+ }
+ registerTarget(
+ resolutionResult.getInitialResolutionHolder(), resolutionResult.getSingleTarget());
+ }
+
+ private void registerTarget(DexClass initialResolutionHolder, DexEncodedMember<?, ?> target) {
+ if (target == null) {
+ // This will fail at runtime.
+ constraint = Constraint.NEVER;
+ return;
+ }
+ DexType resolvedHolder = target.holder();
+ if (initialResolutionHolder == null) {
+ constraint = Constraint.NEVER;
+ return;
+ }
+ Constraint memberConstraint = deriveConstraint(resolvedHolder, target.getAccessFlags());
+ // We also have to take the constraint of the initial resolution holder into account.
+ Constraint classConstraint =
+ deriveConstraint(initialResolutionHolder.type, initialResolutionHolder.accessFlags);
+ Constraint instructionConstraint = memberConstraint.meet(classConstraint);
+ constraint = instructionConstraint.meet(constraint);
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ registerSingleTargetInvoke(method, DexEncodedMethod::isDirectMethod);
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ registerSingleTargetInvoke(method, DexEncodedMethod::isStatic);
+ }
+
+ private void registerSingleTargetInvoke(
+ DexMethod method, Predicate<DexEncodedMethod> methodValidator) {
+ if (method.holder.isArrayType()) {
+ return;
+ }
+ ResolutionResult resolutionResult =
+ appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
+ DexEncodedMethod target = resolutionResult.getSingleTarget();
+ if (target == null || !methodValidator.test(target)) {
+ constraint = Constraint.NEVER;
+ return;
+ }
+ registerTarget(resolutionResult.getInitialResolutionHolder(), target);
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ // Invoke-super can only target java.lang.Enum methods since we do not unbox enums with
+ // subclasses. Calls to java.lang.Object methods would have resulted in the enum to be marked
+ // as unboxable. The methods of java.lang.Enum called are already analyzed in the enum
+ // unboxer analysis, so invoke-super is always valid.
+ assert method.holder == factory.enumType;
+ }
+
+ @Override
+ public void registerCallSite(DexCallSite callSite) {
+ // This is reached after lambda desugaring, so this should not be a lambda call site.
+ // We do not unbox enums with invoke custom since it's not clear the accessibility
+ // constraints would be correct if the method holding the invoke custom is moved to
+ // another class.
+ assert !factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
+ constraint = Constraint.NEVER;
+ }
+
+ private void registerFieldInstruction(DexField field) {
+ FieldResolutionResult fieldResolutionResult = appView.appInfo().resolveField(field, context);
+ registerTarget(
+ fieldResolutionResult.getInitialResolutionHolder(),
+ fieldResolutionResult.getResolvedField());
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ registerFieldInstruction(field);
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ registerFieldInstruction(field);
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ registerFieldInstruction(field);
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ registerFieldInstruction(field);
}
}
@@ -908,11 +1065,9 @@
});
clazz.getMethodCollection().removeMethods(methodsToRemove);
} else {
- IntBox index = new IntBox(0);
clazz
.getMethodCollection()
- .replaceMethods(
- encodedMethod -> fixupEncodedMethod(encodedMethod, index.getAndIncrement()));
+ .replaceMethods(encodedMethod -> fixupEncodedMethod(encodedMethod));
fixupFields(clazz.staticFields(), clazz::setStaticField);
fixupFields(clazz.instanceFields(), clazz::setInstanceField);
}
@@ -962,22 +1117,44 @@
return encodedMethod.toTypeSubstitutedMethod(newMethod);
}
- private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod, int index) {
+ private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
DexProto newProto = fixupProto(encodedMethod.proto());
- if (newProto != encodedMethod.proto()) {
- DexString newMethodName =
- factory.createString(
- EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX
- + index
- + "$"
- + encodedMethod.getName().toString());
- DexMethod newMethod = factory.createMethod(encodedMethod.holder(), newProto, newMethodName);
- assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
- boolean isStatic = encodedMethod.isStatic();
- lensBuilder.move(encodedMethod.method, isStatic, newMethod, isStatic);
- return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ if (newProto == encodedMethod.proto()) {
+ return encodedMethod;
}
- return encodedMethod;
+ assert !encodedMethod.isClassInitializer();
+ DexMethod newMethod =
+ factory.createMethod(encodedMethod.holder(), newProto, encodedMethod.getName());
+ newMethod = ensureUniqueMethod(encodedMethod, newMethod);
+ int numberOfExtraNullParameters = newMethod.getArity() - encodedMethod.method.getArity();
+ boolean isStatic = encodedMethod.isStatic();
+ lensBuilder.move(
+ encodedMethod.method, isStatic, newMethod, isStatic, numberOfExtraNullParameters);
+ return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ }
+
+ private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
+ DexClass holder = appView.definitionFor(encodedMethod.holder());
+ assert holder != null;
+ if (encodedMethod.isInstanceInitializer()) {
+ while (holder.lookupMethod(newMethod) != null) {
+ newMethod =
+ factory.createMethod(
+ newMethod.holder,
+ factory.appendTypeToProto(newMethod.proto, factory.enumUnboxingUtilityType),
+ newMethod.name);
+ }
+ } else {
+ int index = 0;
+ while (holder.lookupMethod(newMethod) != null) {
+ newMethod =
+ factory.createMethod(
+ newMethod.holder,
+ newMethod.proto,
+ encodedMethod.getName().toString() + "$enumunboxing$" + index++);
+ }
+ }
+ return newMethod;
}
private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
@@ -1091,6 +1268,15 @@
new IdentityHashMap<>();
public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) {
+ move(from, fromStatic, to, toStatic, 0);
+ }
+
+ public void move(
+ DexMethod from,
+ boolean fromStatic,
+ DexMethod to,
+ boolean toStatic,
+ int numberOfExtraNullParameters) {
super.move(from, to);
int offsetDiff = 0;
int toOffset = BooleanUtils.intValue(!toStatic);
@@ -1114,7 +1300,9 @@
? null
: new RewrittenTypeInfo(from.proto.returnType, to.proto.returnType);
prototypeChanges.put(
- to, RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build()));
+ to,
+ RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build())
+ .withExtraUnusedNullParameters(numberOfExtraNullParameters));
}
public EnumUnboxingLens build(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index c4466cd..0372a33 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -113,11 +113,6 @@
this.converter = converter;
}
- // This set is an approximation and can be used only for heuristics.
- public final Set<DexType> getCandidates() {
- return candidates.keySet();
- }
-
// Before doing any usage-based analysis we collect a set of classes that can be
// candidates for staticizing. This analysis is very simple, but minimizes the
// set of eligible classes staticizer tracks and thus time and memory it needs.
@@ -692,85 +687,82 @@
super(factory);
}
- private boolean registerMethod(DexMethod method) {
+ private void registerMethod(DexMethod method) {
registerTypeReference(method.holder);
registerProto(method.proto);
- return true;
}
- private boolean registerField(DexField field) {
+ private void registerField(DexField field) {
registerTypeReference(field.holder);
registerTypeReference(field.type);
- return true;
}
@Override
- public boolean registerInitClass(DexType clazz) {
- return registerTypeReference(clazz);
+ public void registerInitClass(DexType clazz) {
+ registerTypeReference(clazz);
}
@Override
- public boolean registerInvokeVirtual(DexMethod method) {
- return registerMethod(method);
+ public void registerInvokeVirtual(DexMethod method) {
+ registerMethod(method);
}
@Override
- public boolean registerInvokeDirect(DexMethod method) {
- return registerMethod(method);
+ public void registerInvokeDirect(DexMethod method) {
+ registerMethod(method);
}
@Override
- public boolean registerInvokeStatic(DexMethod method) {
- return registerMethod(method);
+ public void registerInvokeStatic(DexMethod method) {
+ registerMethod(method);
}
@Override
- public boolean registerInvokeInterface(DexMethod method) {
- return registerMethod(method);
+ public void registerInvokeInterface(DexMethod method) {
+ registerMethod(method);
}
@Override
- public boolean registerInvokeSuper(DexMethod method) {
- return registerMethod(method);
+ public void registerInvokeSuper(DexMethod method) {
+ registerMethod(method);
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return registerField(field);
+ public void registerInstanceFieldWrite(DexField field) {
+ registerField(field);
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
- return registerField(field);
+ public void registerInstanceFieldRead(DexField field) {
+ registerField(field);
}
@Override
- public boolean registerNewInstance(DexType type) {
- return registerTypeReference(type);
+ public void registerNewInstance(DexType type) {
+ registerTypeReference(type);
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
- return registerField(field);
+ public void registerStaticFieldRead(DexField field) {
+ registerField(field);
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
- return registerField(field);
+ public void registerStaticFieldWrite(DexField field) {
+ registerField(field);
}
@Override
- public boolean registerTypeReference(DexType type) {
+ public void registerTypeReference(DexType type) {
CandidateInfo candidateInfo = candidates.get(type);
if (candidateInfo != null) {
candidateInfo.invalidate();
}
- return true;
}
@Override
- public boolean registerInstanceOf(DexType type) {
- return registerTypeReference(type);
+ public void registerInstanceOf(DexType type) {
+ registerTypeReference(type);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 7af08f9..477f516 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -473,8 +473,12 @@
? block.getPredecessors().get(0).exceptionalExit().getNumber()
: block.getPredecessors().get(0).exit().getNumber();
for (LocalRange open : openRanges) {
+ Value predecessorValue =
+ open.value.isPhi() && open.value.getBlock() == block
+ ? open.value.asPhi().getOperand(0)
+ : open.value;
int predecessorRegister =
- allocator.getArgumentOrAllocateRegisterForValue(open.value, predecessorExitIndex);
+ allocator.getArgumentOrAllocateRegisterForValue(predecessorValue, predecessorExitIndex);
initialLocals.put(predecessorRegister, open.local);
}
block.setLocalsAtEntry(initialLocals);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 45cbd27..df06499 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -38,33 +38,44 @@
Consumer<DexEncodedMethod> keepByteCode) {
DexAnnotation meta = clazz.annotations().getFirstMatching(factory.kotlinMetadataType);
if (meta != null) {
- try {
- KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, meta.annotation);
- if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) {
- return NO_KOTLIN_INFO;
- }
- return createKotlinInfo(kotlin, clazz, kMetadata, factory, reporter, keepByteCode);
- } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
- reporter.info(
- new StringDiagnostic(
- "Class "
- + clazz.type.toSourceString()
- + " has malformed kotlin.Metadata: "
- + e.getMessage()));
- return INVALID_KOTLIN_INFO;
- } catch (Throwable e) {
- reporter.info(
- new StringDiagnostic(
- "Unexpected error while reading "
- + clazz.type.toSourceString()
- + "'s kotlin.Metadata: "
- + e.getMessage()));
- return INVALID_KOTLIN_INFO;
- }
+ return getKotlinInfo(kotlin, clazz, factory, reporter, onlyProcessLambda, keepByteCode, meta);
}
return NO_KOTLIN_INFO;
}
+ public static KotlinClassLevelInfo getKotlinInfo(
+ Kotlin kotlin,
+ DexClass clazz,
+ DexItemFactory factory,
+ Reporter reporter,
+ boolean onlyProcessLambda,
+ Consumer<DexEncodedMethod> keepByteCode,
+ DexAnnotation annotation) {
+ try {
+ KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, annotation.annotation);
+ if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) {
+ return NO_KOTLIN_INFO;
+ }
+ return createKotlinInfo(kotlin, clazz, kMetadata, factory, reporter, keepByteCode);
+ } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
+ reporter.info(
+ new StringDiagnostic(
+ "Class "
+ + clazz.type.toSourceString()
+ + " has malformed kotlin.Metadata: "
+ + e.getMessage()));
+ return INVALID_KOTLIN_INFO;
+ } catch (Throwable e) {
+ reporter.info(
+ new StringDiagnostic(
+ "Unexpected error while reading "
+ + clazz.type.toSourceString()
+ + "'s kotlin.Metadata: "
+ + e.getMessage()));
+ return INVALID_KOTLIN_INFO;
+ }
+ }
+
public static boolean hasKotlinClassMetadataAnnotation(
DexClass clazz, DexDefinitionSupplier definitionSupplier) {
return clazz
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index ace8717..1001ca9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -18,6 +18,8 @@
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
@@ -42,7 +44,7 @@
final boolean writePackageName;
final boolean writeExtraInt;
- public WriteMetadataFieldInfo(
+ private WriteMetadataFieldInfo(
boolean writeKind,
boolean writeMetadataVersion,
boolean writeByteCodeVersion,
@@ -60,6 +62,10 @@
this.writePackageName = writePackageName;
this.writeExtraInt = writeExtraInt;
}
+
+ private static WriteMetadataFieldInfo rewriteAll() {
+ return new WriteMetadataFieldInfo(true, true, true, true, true, true, true, true);
+ }
}
private final AppView<?> appView;
@@ -78,7 +84,7 @@
return annotation.annotation.type != appView.dexItemFactory().kotlinMetadataType;
}
- public void run(ExecutorService executorService) throws ExecutionException {
+ public void runForR8(ExecutorService executorService) throws ExecutionException {
final DexClass kotlinMetadata =
appView.definitionFor(appView.dexItemFactory().kotlinMetadataType);
final WriteMetadataFieldInfo writeMetadataFieldInfo =
@@ -112,26 +118,58 @@
}
return;
}
- try {
- KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
- DexAnnotation newMeta =
- createKotlinMetadataAnnotation(
- kotlinClassHeader,
- kotlinInfo.getPackageName(),
- getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()),
- writeMetadataFieldInfo);
- clazz.setAnnotations(
- clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
- } catch (Throwable t) {
- appView
- .options()
- .reporter
- .warning(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t));
- }
+ writeKotlinInfoToAnnotation(clazz, kotlinInfo, oldMeta, writeMetadataFieldInfo);
},
executorService);
}
+ public void runForD8(ExecutorService executorService) throws ExecutionException {
+ if (lens.isIdentityLens()) {
+ return;
+ }
+ final Kotlin kotlin = factory.kotlin;
+ final Reporter reporter = appView.options().reporter;
+ final WriteMetadataFieldInfo writeMetadataFieldInfo = WriteMetadataFieldInfo.rewriteAll();
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz -> {
+ DexAnnotation metadata = clazz.annotations().getFirstMatching(factory.kotlinMetadataType);
+ if (metadata == null) {
+ return;
+ }
+ final KotlinClassLevelInfo kotlinInfo =
+ KotlinClassMetadataReader.getKotlinInfo(
+ kotlin, clazz, factory, reporter, false, ConsumerUtils.emptyConsumer(), metadata);
+ if (kotlinInfo == NO_KOTLIN_INFO) {
+ return;
+ }
+ writeKotlinInfoToAnnotation(clazz, kotlinInfo, metadata, writeMetadataFieldInfo);
+ },
+ executorService);
+ }
+
+ private void writeKotlinInfoToAnnotation(
+ DexClass clazz,
+ KotlinClassLevelInfo kotlinInfo,
+ DexAnnotation oldMeta,
+ WriteMetadataFieldInfo writeMetadataFieldInfo) {
+ try {
+ KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
+ DexAnnotation newMeta =
+ createKotlinMetadataAnnotation(
+ kotlinClassHeader,
+ kotlinInfo.getPackageName(),
+ getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()),
+ writeMetadataFieldInfo);
+ clazz.setAnnotations(clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
+ } catch (Throwable t) {
+ appView
+ .options()
+ .reporter
+ .warning(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t));
+ }
+ }
+
private boolean kotlinMetadataFieldExists(
DexClass kotlinMetadata, AppView<?> appView, DexString fieldName) {
if (!appView.appInfo().hasLiveness()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 998fad3..2d63c1e 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -64,7 +64,10 @@
this.packageNamingStrategy = packageNamingStrategy;
this.classes = classes;
InternalOptions options = appView.options();
- this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
+ this.packageObfuscationMode =
+ options.testing.enableExperimentalRepackaging
+ ? PackageObfuscationMode.NONE
+ : options.getProguardConfiguration().getPackageObfuscationMode();
this.isAccessModificationAllowed =
options.getProguardConfiguration().isAccessModificationAllowed();
this.keepInnerClassStructure = options.keepInnerClassStructure();
diff --git a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
index 37df60c..fadc223 100644
--- a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
+++ b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
@@ -17,7 +17,7 @@
super(factory);
}
- private boolean setTarget(DexMethod target, InvokeKind kind) {
+ private void setTarget(DexMethod target, InvokeKind kind) {
if (this.kind != InvokeKind.NONE) {
this.kind = InvokeKind.ILLEGAL;
this.target = null;
@@ -26,12 +26,10 @@
this.target = target;
this.kind = kind;
}
- return false;
}
- private boolean invalid() {
+ private void invalid() {
kind = InvokeKind.ILLEGAL;
- return false;
}
public DexMethod getTarget() {
@@ -43,68 +41,68 @@
}
@Override
- public boolean registerInitClass(DexType clazz) {
- return invalid();
+ public void registerInitClass(DexType clazz) {
+ invalid();
}
@Override
- public boolean registerInvokeVirtual(DexMethod method) {
- return setTarget(method, InvokeKind.VIRTUAL);
+ public void registerInvokeVirtual(DexMethod method) {
+ setTarget(method, InvokeKind.VIRTUAL);
}
@Override
- public boolean registerInvokeDirect(DexMethod method) {
- return invalid();
+ public void registerInvokeDirect(DexMethod method) {
+ invalid();
}
@Override
- public boolean registerInvokeStatic(DexMethod method) {
- return setTarget(method, InvokeKind.STATIC);
+ public void registerInvokeStatic(DexMethod method) {
+ setTarget(method, InvokeKind.STATIC);
}
@Override
- public boolean registerInvokeInterface(DexMethod method) {
- return invalid();
+ public void registerInvokeInterface(DexMethod method) {
+ invalid();
}
@Override
- public boolean registerInvokeSuper(DexMethod method) {
- return setTarget(method, InvokeKind.SUPER);
+ public void registerInvokeSuper(DexMethod method) {
+ setTarget(method, InvokeKind.SUPER);
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return invalid();
+ public void registerInstanceFieldWrite(DexField field) {
+ invalid();
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
- return invalid();
+ public void registerInstanceFieldRead(DexField field) {
+ invalid();
}
@Override
- public boolean registerNewInstance(DexType type) {
- return invalid();
+ public void registerNewInstance(DexType type) {
+ invalid();
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
- return invalid();
+ public void registerStaticFieldRead(DexField field) {
+ invalid();
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
- return invalid();
+ public void registerStaticFieldWrite(DexField field) {
+ invalid();
}
@Override
- public boolean registerTypeReference(DexType type) {
- return invalid();
+ public void registerTypeReference(DexType type) {
+ invalid();
}
@Override
- public boolean registerInstanceOf(DexType type) {
- return invalid();
+ public void registerInstanceOf(DexType type) {
+ invalid();
}
public enum InvokeKind {
diff --git a/src/main/java/com/android/tools/r8/relocator/Relocator.java b/src/main/java/com/android/tools/r8/relocator/Relocator.java
index a33424b..e45afd6 100644
--- a/src/main/java/com/android/tools/r8/relocator/Relocator.java
+++ b/src/main/java/com/android/tools/r8/relocator/Relocator.java
@@ -79,8 +79,7 @@
Timing timing = Timing.create("Relocator", options);
try {
DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
-
- AppInfo appInfo = new AppInfo(app);
+ AppInfo appInfo = AppInfo.createInitialAppInfo(app);
AppView<?> appView = AppView.createForRelocator(appInfo);
appView.setAppServices(AppServices.builder(appView).build());
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
new file mode 100644
index 0000000..4077f36
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackaging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramPackage;
+import com.android.tools.r8.graph.ProgramPackageCollection;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.utils.Timing;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Entry-point for supporting the -repackageclasses and -flattenpackagehierarchy directives.
+ *
+ * <p>This pass moves all classes in the program into a user-specified package. Some classes may not
+ * be allowed to be renamed, and thus must remain in the original package.
+ *
+ * <p>A complication is that there can be (i) references to package-private or protected items that
+ * must remain in the package, and (ii) references from methods that must remain in the package to
+ * package-private or protected items. To ensure that such references remain valid after
+ * repackaging, an analysis is run that finds the minimal set of classes that must remain in the
+ * original package due to accessibility constraints.
+ */
+public class Repackaging {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final ProguardConfiguration proguardConfiguration;
+
+ public Repackaging(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.proguardConfiguration = appView.options().getProguardConfiguration();
+ }
+
+ public void run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ timing.begin("Repackage classes");
+ run(executorService);
+ timing.end();
+ }
+
+ private void run(ExecutorService executorService) throws ExecutionException {
+ if (proguardConfiguration.getPackageObfuscationMode().isNone()) {
+ return;
+ }
+
+ // For each package, find the set of classes that can be repackaged, and move them to the
+ // desired namespace.
+ ProgramPackageCollection packages = ProgramPackageCollection.create(appView);
+ for (ProgramPackage pkg : packages) {
+ Iterable<DexProgramClass> classesToRepackage =
+ computeClassesToRepackage(pkg, executorService);
+ // TODO(b/165783399): Move each class in `classesToRepackage`.
+ // TODO(b/165783399): Investigate if repackaging can lead to different dynamic dispatch. See,
+ // for example, CrossPackageInvokeSuperToPackagePrivateMethodTest.
+ }
+ }
+
+ private Iterable<DexProgramClass> computeClassesToRepackage(
+ ProgramPackage pkg, ExecutorService executorService) throws ExecutionException {
+ RepackagingConstraintGraph constraintGraph = new RepackagingConstraintGraph(appView, pkg);
+ boolean canRepackageAllClasses = constraintGraph.initializeGraph();
+ if (canRepackageAllClasses) {
+ return pkg;
+ }
+ constraintGraph.populateConstraints(executorService);
+ return constraintGraph.computeClassesToRepackage();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
new file mode 100644
index 0000000..9619f5a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackaging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ProgramPackage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * An undirected graph that contains a node for each class, field, and method in a given package.
+ *
+ * <p>An edge X <-> Y is added if X contains a reference to Y and Y is only accessible to X if X and
+ * Y are in the same package.
+ *
+ * <p>Once the graph is populated, we compute the set of reachable nodes from the set of root nodes
+ * that cannot be repackaged due to a -keep rule. The remaining nodes in the graph can all be
+ * repackaged.
+ */
+public class RepackagingConstraintGraph {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final ProgramPackage pkg;
+ private final Map<DexDefinition, Node> nodes = new IdentityHashMap<>();
+
+ public RepackagingConstraintGraph(AppView<AppInfoWithLiveness> appView, ProgramPackage pkg) {
+ this.appView = appView;
+ this.pkg = pkg;
+ }
+
+ /** Returns true if all classes in the package can be repackaged. */
+ public boolean initializeGraph() {
+ // Add all the items in the package into the graph. This way we know which items belong to the
+ // package without having to extract package descriptor strings and comparing them with the
+ // package descriptor.
+ boolean hasPackagePrivateOrProtectedItem = false;
+ boolean hasPinnedItem = false;
+ for (DexProgramClass clazz : pkg) {
+ boolean isPinned = !appView.appInfo().isMinificationAllowed(clazz.getType());
+ hasPinnedItem |= isPinned;
+ nodes.put(clazz, new Node(clazz));
+ hasPackagePrivateOrProtectedItem |= clazz.getAccessFlags().isPackagePrivateOrProtected();
+ for (DexEncodedMember<?, ?> member : clazz.members()) {
+ nodes.put(member, new Node(member));
+ hasPackagePrivateOrProtectedItem |= member.getAccessFlags().isPackagePrivateOrProtected();
+ }
+ }
+ return !hasPinnedItem || !hasPackagePrivateOrProtectedItem;
+ }
+
+ Node getNode(DexDefinition definition) {
+ return nodes.get(definition);
+ }
+
+ public void populateConstraints(ExecutorService executorService) throws ExecutionException {
+ // Concurrently add references from methods to the graph.
+ ThreadUtils.processItems(
+ pkg::forEachMethod, this::registerReferencesFromMethod, executorService);
+
+ // TODO(b/165783399): Evaluate if it is worth to parallelize this. The work per field and class
+ // should be little, so it may not be.
+ pkg.forEachClass(this::registerReferencesFromClass);
+ pkg.forEachField(this::registerReferencesFromField);
+ }
+
+ private void registerReferencesFromClass(DexProgramClass clazz) {
+ // TODO(b/165783399): Trace the references to the immediate super types.
+ // TODO(b/165783399): Maybe trace the references in the nest host and/or members.
+ // TODO(b/165783399): Maybe trace the references to the inner classes.
+ // TODO(b/165783399): Maybe trace the references in @kotlin.Metadata.
+ }
+
+ private void registerReferencesFromField(ProgramField field) {
+ // TODO(b/165783399): Trace the type of the field.
+ // TODO(b/165783399): Trace the references in the field annotations.
+ }
+
+ private void registerReferencesFromMethod(ProgramMethod method) {
+ // TODO(b/165783399): Trace the type references in the method signature.
+ // TODO(b/165783399): Trace the references in the method and method parameter annotations.
+ DexEncodedMethod definition = method.getDefinition();
+ if (definition.hasCode()) {
+ RepackagingUseRegistry registry = new RepackagingUseRegistry(appView, this, method);
+ definition.getCode().registerCodeReferences(method, registry);
+ }
+ }
+
+ public Iterable<DexProgramClass> computeClassesToRepackage() {
+ // TODO(b/165783399): From each node in the graph that cannot be moved elsewhere due to a -keep
+ // rule, mark all neighbors as pinned, and repeat.
+ return Collections.emptyList();
+ }
+
+ static class Node {
+
+ private final DexDefinition definition;
+
+ private final Set<Node> neighbors = Sets.newConcurrentHashSet();
+
+ private Node(DexDefinition definition) {
+ this.definition = definition;
+ }
+
+ public void addNeighbor(Node neighbor) {
+ neighbors.add(neighbor);
+ neighbor.neighbors.add(this);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
new file mode 100644
index 0000000..531e6c8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -0,0 +1,182 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackaging;
+
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MemberResolutionResult;
+import com.android.tools.r8.graph.ProgramMember;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.SuccessfulMemberResolutionResult;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class RepackagingUseRegistry extends UseRegistry {
+
+ private final AppInfoWithLiveness appInfo;
+ private final RepackagingConstraintGraph constraintGraph;
+ private final ProgramMethod context;
+ private final RepackagingConstraintGraph.Node node;
+
+ public RepackagingUseRegistry(
+ AppView<AppInfoWithLiveness> appView,
+ RepackagingConstraintGraph constraintGraph,
+ ProgramMethod context) {
+ super(appView.dexItemFactory());
+ this.appInfo = appView.appInfo();
+ this.constraintGraph = constraintGraph;
+ this.context = context;
+ this.node = constraintGraph.getNode(context.getDefinition());
+ }
+
+ private boolean isOnlyAccessibleFromSamePackage(DexProgramClass referencedClass) {
+ ClassAccessFlags accessFlags = referencedClass.getAccessFlags();
+ if (accessFlags.isPackagePrivate()) {
+ return true;
+ }
+ if (accessFlags.isProtected()
+ && !appInfo.isSubtype(context.getHolderType(), referencedClass.getType())) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isOnlyAccessibleFromSamePackage(ProgramMember<?, ?> member) {
+ AccessFlags<?> accessFlags = member.getDefinition().getAccessFlags();
+ if (accessFlags.isPackagePrivate()) {
+ return true;
+ }
+ if (accessFlags.isProtected()
+ && !appInfo.isSubtype(context.getHolderType(), member.getHolderType())) {
+ return true;
+ }
+ return false;
+ }
+
+ private void registerMemberAccess(MemberResolutionResult<?, ?> resolutionResult) {
+ SuccessfulMemberResolutionResult<?, ?> successfulResolutionResult =
+ resolutionResult.asSuccessfulMemberResolutionResult();
+ if (successfulResolutionResult == null) {
+ // TODO(b/165783399): If we want to preserve errors in the original program, we need to look
+ // at the failure dependencies. For example, if this method accesses in a package-private
+ // method in another package, and we move the two methods to the same package, then the
+ // invoke would no longer fail with an IllegalAccessError.
+ return;
+ }
+
+ // Check access to the initial resolution holder.
+ registerTypeAccess(successfulResolutionResult.getInitialResolutionHolder());
+
+ // Similarly, check access to the resolved member.
+ ProgramMember<?, ?> resolvedMember =
+ successfulResolutionResult.getResolvedMember().asProgramMember(appInfo);
+ if (resolvedMember != null) {
+ RepackagingConstraintGraph.Node resolvedMemberNode =
+ constraintGraph.getNode(resolvedMember.getDefinition());
+ if (resolvedMemberNode != null && isOnlyAccessibleFromSamePackage(resolvedMember)) {
+ node.addNeighbor(resolvedMemberNode);
+ }
+ }
+ }
+
+ private void registerTypeAccess(DexType type) {
+ if (type.isArrayType()) {
+ registerTypeAccess(type.toBaseType(appInfo.dexItemFactory()));
+ return;
+ }
+ if (type.isPrimitiveType()) {
+ return;
+ }
+ assert type.isClassType();
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null) {
+ registerTypeAccess(clazz);
+ }
+ }
+
+ private void registerTypeAccess(DexClass clazz) {
+ // We only want to connect the current method node to the class node if the access requires the
+ // two nodes to be in the same package. Therefore, we ignore accesses to non-program classes
+ // and program classes outside the current package.
+ DexProgramClass programClass = clazz.asProgramClass();
+ if (programClass != null) {
+ RepackagingConstraintGraph.Node classNode = constraintGraph.getNode(programClass);
+ if (classNode != null && isOnlyAccessibleFromSamePackage(programClass)) {
+ node.addNeighbor(classNode);
+ }
+ }
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {
+ registerTypeAccess(type);
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod invokedMethod) {
+ registerMemberAccess(appInfo.resolveMethod(invokedMethod, false));
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod invokedMethod) {
+ registerMemberAccess(appInfo.unsafeResolveMethodDueToDexFormat(invokedMethod));
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod invokedMethod) {
+ registerMemberAccess(appInfo.unsafeResolveMethodDueToDexFormat(invokedMethod));
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod invokedMethod) {
+ registerMemberAccess(appInfo.resolveMethod(invokedMethod, true));
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod invokedMethod) {
+ registerMemberAccess(appInfo.unsafeResolveMethodDueToDexFormat(invokedMethod));
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ registerMemberAccess(appInfo.resolveField(field));
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ registerMemberAccess(appInfo.resolveField(field));
+ }
+
+ @Override
+ public void registerNewInstance(DexType type) {
+ registerTypeAccess(type);
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ registerMemberAccess(appInfo.resolveField(field));
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ registerMemberAccess(appInfo.resolveField(field));
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ registerTypeAccess(type);
+ }
+
+ @Override
+ public void registerInstanceOf(DexType type) {
+ registerTypeAccess(type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index b35bb12..611dea6 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -34,22 +34,19 @@
private static final int NO_MATCH = -1;
- private final RegularExpressionGroup[] syntheticGroups =
- new RegularExpressionGroup[] {new SourceFileLineNumberGroup()};
-
- private final RegularExpressionGroup[] groups =
- new RegularExpressionGroup[] {
- new TypeNameGroup(),
- new BinaryNameGroup(),
- new MethodNameGroup(),
- new FieldNameGroup(),
- new SourceFileGroup(),
- new LineNumberGroup(),
- new FieldOrReturnTypeGroup(),
- new MethodArgumentsGroup()
- };
+ private final SourceFileLineNumberGroup sourceFileLineNumberGroup =
+ new SourceFileLineNumberGroup();
+ private final TypeNameGroup typeNameGroup = new TypeNameGroup();
+ private final BinaryNameGroup binaryNameGroup = new BinaryNameGroup();
+ private final MethodNameGroup methodNameGroup = new MethodNameGroup();
+ private final FieldNameGroup fieldNameGroup = new FieldNameGroup();
+ private final SourceFileGroup sourceFileGroup = new SourceFileGroup();
+ private final LineNumberGroup lineNumberGroup = new LineNumberGroup();
+ private final FieldOrReturnTypeGroup fieldOrReturnTypeGroup = new FieldOrReturnTypeGroup();
+ private final MethodArgumentsGroup methodArgumentsGroup = new MethodArgumentsGroup();
private static final String CAPTURE_GROUP_PREFIX = "captureGroup";
+ private static final int FIRST_CAPTURE_GROUP_INDEX = 0;
RetraceRegularExpression(
RetraceApi retracer,
@@ -64,8 +61,14 @@
public RetraceCommandLineResult retrace() {
List<RegularExpressionGroupHandler> handlers = new ArrayList<>();
- String regularExpression = registerGroups(this.regularExpression, handlers);
- Pattern compiledPattern = Pattern.compile(regularExpression);
+ StringBuilder refinedRegularExpressionBuilder = new StringBuilder();
+ registerGroups(
+ this.regularExpression,
+ refinedRegularExpressionBuilder,
+ handlers,
+ FIRST_CAPTURE_GROUP_INDEX);
+ String refinedRegularExpression = refinedRegularExpressionBuilder.toString();
+ Pattern compiledPattern = Pattern.compile(refinedRegularExpression);
List<String> result = new ArrayList<>();
for (String string : stackTrace) {
Matcher matcher = compiledPattern.matcher(string);
@@ -130,80 +133,68 @@
}
}
- private String registerGroups(
- String regularExpression, List<RegularExpressionGroupHandler> handlers) {
- int currentIndex = 0;
- int captureGroupIndex = 0;
- regularExpression = registerSyntheticGroups(regularExpression);
- while (currentIndex < regularExpression.length()) {
- RegularExpressionGroup firstGroup = null;
- int firstIndexFromCurrent = regularExpression.length();
- for (RegularExpressionGroup group : groups) {
- int firstIndex =
- firstIndexOfGroup(
- currentIndex, firstIndexFromCurrent, regularExpression, group.shortName());
- if (firstIndex > NO_MATCH) {
- firstGroup = group;
- firstIndexFromCurrent = firstIndex;
- }
- }
- if (firstGroup != null) {
- String captureGroupName = CAPTURE_GROUP_PREFIX + (captureGroupIndex++);
- String patternToInsert = "(?<" + captureGroupName + ">" + firstGroup.subExpression() + ")";
- regularExpression =
- regularExpression.substring(0, firstIndexFromCurrent)
- + patternToInsert
- + regularExpression.substring(
- firstIndexFromCurrent + firstGroup.shortName().length());
- handlers.add(firstGroup.createHandler(captureGroupName));
- firstIndexFromCurrent += patternToInsert.length();
- }
- currentIndex = firstIndexFromCurrent;
- }
- return regularExpression;
- }
-
- private int firstIndexOfGroup(int startIndex, int endIndex, String expression, String shortName) {
- int nextIndexOf = startIndex;
- while (nextIndexOf != NO_MATCH) {
- nextIndexOf = expression.indexOf(shortName, nextIndexOf);
- if (nextIndexOf > NO_MATCH) {
- if (nextIndexOf < endIndex && !isEscaped(expression, nextIndexOf)) {
- return nextIndexOf;
- }
- nextIndexOf++;
- }
- }
- return NO_MATCH;
- }
-
- private boolean isEscaped(String expression, int index) {
+ private int registerGroups(
+ String regularExpression,
+ StringBuilder refinedRegularExpression,
+ List<RegularExpressionGroupHandler> handlers,
+ int captureGroupIndex) {
+ int lastCommittedIndex = 0;
+ boolean seenPercentage = false;
boolean escaped = false;
- while (index > 0 && expression.charAt(--index) == '\\') {
- escaped = !escaped;
+ for (int i = 0; i < regularExpression.length(); i++) {
+ if (seenPercentage) {
+ assert !escaped;
+ final RegularExpressionGroup group = getGroupFromVariable(regularExpression.charAt(i));
+ refinedRegularExpression.append(regularExpression, lastCommittedIndex, i - 1);
+ lastCommittedIndex = i + 1;
+ if (group.isSynthetic()) {
+ captureGroupIndex =
+ registerGroups(
+ group.subExpression(), refinedRegularExpression, handlers, captureGroupIndex);
+ } else {
+ String captureGroupName = CAPTURE_GROUP_PREFIX + (captureGroupIndex++);
+ refinedRegularExpression
+ .append("(?<")
+ .append(captureGroupName)
+ .append(">")
+ .append(group.subExpression())
+ .append(")");
+ handlers.add(group.createHandler(captureGroupName));
+ }
+ seenPercentage = false;
+ } else {
+ seenPercentage = !escaped && regularExpression.charAt(i) == '%';
+ escaped = !escaped && regularExpression.charAt(i) == '\\';
+ }
}
- return escaped;
+ refinedRegularExpression.append(
+ regularExpression, lastCommittedIndex, regularExpression.length());
+ return captureGroupIndex;
}
- private String registerSyntheticGroups(String regularExpression) {
- boolean modifiedExpression;
- do {
- modifiedExpression = false;
- for (RegularExpressionGroup syntheticGroup : syntheticGroups) {
- int firstIndex =
- firstIndexOfGroup(
- 0, regularExpression.length(), regularExpression, syntheticGroup.shortName());
- if (firstIndex > NO_MATCH) {
- regularExpression =
- regularExpression.substring(0, firstIndex)
- + syntheticGroup.subExpression()
- + regularExpression.substring(firstIndex + syntheticGroup.shortName().length());
- // Loop as long as we can replace.
- modifiedExpression = true;
- }
- }
- } while (modifiedExpression);
- return regularExpression;
+ private RegularExpressionGroup getGroupFromVariable(char variable) {
+ switch (variable) {
+ case 'c':
+ return typeNameGroup;
+ case 'C':
+ return binaryNameGroup;
+ case 'm':
+ return methodNameGroup;
+ case 'f':
+ return fieldNameGroup;
+ case 's':
+ return sourceFileGroup;
+ case 'l':
+ return lineNumberGroup;
+ case 'S':
+ return sourceFileLineNumberGroup;
+ case 't':
+ return fieldOrReturnTypeGroup;
+ case 'a':
+ return methodArgumentsGroup;
+ default:
+ throw new Unreachable("Unexpected variable: " + variable);
+ }
}
static class RetraceString {
@@ -427,8 +418,6 @@
private abstract static class RegularExpressionGroup {
- abstract String shortName();
-
abstract String subExpression();
abstract RegularExpressionGroupHandler createHandler(String captureGroup);
@@ -480,11 +469,6 @@
private static class TypeNameGroup extends ClassNameGroup {
@Override
- String shortName() {
- return "%c";
- }
-
- @Override
String subExpression() {
return "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment;
}
@@ -503,11 +487,6 @@
private static class BinaryNameGroup extends ClassNameGroup {
@Override
- String shortName() {
- return "%C";
- }
-
- @Override
String subExpression() {
return "(?:" + javaIdentifierSegment + "\\/)*" + javaIdentifierSegment;
}
@@ -526,11 +505,6 @@
private static class MethodNameGroup extends RegularExpressionGroup {
@Override
- String shortName() {
- return "%m";
- }
-
- @Override
String subExpression() {
return "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))";
}
@@ -595,11 +569,6 @@
private static class FieldNameGroup extends RegularExpressionGroup {
@Override
- String shortName() {
- return "%f";
- }
-
- @Override
String subExpression() {
return javaIdentifierSegment;
}
@@ -649,11 +618,6 @@
private static class SourceFileGroup extends RegularExpressionGroup {
@Override
- String shortName() {
- return "%s";
- }
-
- @Override
String subExpression() {
return "(?:(\\w*[\\. ])?(\\w*)?)";
}
@@ -696,11 +660,6 @@
private class LineNumberGroup extends RegularExpressionGroup {
@Override
- String shortName() {
- return "%l";
- }
-
- @Override
String subExpression() {
return "\\d*";
}
@@ -771,11 +730,6 @@
private static class SourceFileLineNumberGroup extends RegularExpressionGroup {
@Override
- String shortName() {
- return "%S";
- }
-
- @Override
String subExpression() {
return "%s(?::%l)?";
}
@@ -797,11 +751,6 @@
private static class FieldOrReturnTypeGroup extends RegularExpressionGroup {
@Override
- String shortName() {
- return "%t";
- }
-
- @Override
String subExpression() {
return JAVA_TYPE_REGULAR_EXPRESSION;
}
@@ -843,11 +792,6 @@
private class MethodArgumentsGroup extends RegularExpressionGroup {
@Override
- String shortName() {
- return "%a";
- }
-
- @Override
String subExpression() {
return "((" + JAVA_TYPE_REGULAR_EXPRESSION + "\\,)*" + JAVA_TYPE_REGULAR_EXPRESSION + ")?";
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 3197121..c716da8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -40,6 +41,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.SyntheticItems;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
@@ -195,6 +197,7 @@
// TODO(zerny): Clean up the constructors so we have just one.
AppInfoWithLiveness(
DirectMappedDexApplication application,
+ SyntheticItems.CommittedItems syntheticItems,
Set<DexType> deadProtoTypes,
Set<DexType> missingTypes,
Set<DexType> liveTypes,
@@ -235,7 +238,7 @@
EnumValueInfoMapCollection enumValueInfoMaps,
Set<DexType> constClassReferences,
Map<DexType, Visibility> initClassReferences) {
- super(application);
+ super(application, syntheticItems);
this.deadProtoTypes = deadProtoTypes;
this.missingTypes = missingTypes;
this.liveTypes = liveTypes;
@@ -320,7 +323,9 @@
EnumValueInfoMapCollection enumValueInfoMaps,
Set<DexType> constClassReferences,
Map<DexType, Visibility> initClassReferences) {
- super(appInfoWithClassHierarchy);
+ super(
+ appInfoWithClassHierarchy.app(),
+ appInfoWithClassHierarchy.getSyntheticItems().commit(appInfoWithClassHierarchy.app()));
this.deadProtoTypes = deadProtoTypes;
this.missingTypes = missingTypes;
this.liveTypes = liveTypes;
@@ -406,7 +411,6 @@
previous.enumValueInfoMaps,
previous.constClassReferences,
previous.initClassReferences);
- copyMetadataFromPrevious(previous);
}
private AppInfoWithLiveness(
@@ -416,6 +420,7 @@
Collection<DexReference> additionalPinnedItems) {
this(
application,
+ previous.getSyntheticItems().commit(application),
previous.deadProtoTypes,
previous.missingTypes,
previous.liveTypes,
@@ -458,7 +463,6 @@
previous.enumValueInfoMaps,
previous.constClassReferences,
previous.initClassReferences);
- copyMetadataFromPrevious(previous);
assert keepInfo.verifyNoneArePinned(removedClasses, previous);
}
@@ -503,7 +507,7 @@
AppInfoWithLiveness previous,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
EnumValueInfoMapCollection enumValueInfoMaps) {
- super(previous);
+ super(previous.app(), previous.getSyntheticItems().commit(previous.app()));
this.deadProtoTypes = previous.deadProtoTypes;
this.missingTypes = previous.missingTypes;
this.liveTypes = previous.liveTypes;
@@ -785,7 +789,9 @@
private boolean isInstantiatedDirectly(DexProgramClass clazz) {
assert checkIfObsolete();
DexType type = clazz.type;
- return type.isD8R8SynthesizedClassType()
+ return
+ // TODO(b/165224388): Synthetic classes should be represented in the allocation info.
+ getSyntheticItems().isSyntheticClass(clazz)
|| (!clazz.isInterface() && objectAllocationInfoCollection.isInstantiatedDirectly(clazz))
// TODO(b/145344105): Model annotations in the object allocation info.
|| (clazz.isAnnotation() && liveTypes.contains(type));
@@ -808,11 +814,16 @@
if (info != null && info.isRead()) {
return true;
}
- return keepInfo.isPinned(field, this)
- // Fields in the class that is synthesized by D8/R8 would be used soon.
- || field.holder.isD8R8SynthesizedClassType()
- // For library classes we don't know whether a field is read.
- || isLibraryOrClasspathField(encodedField);
+ if (keepInfo.isPinned(field, this)) {
+ return true;
+ }
+ // Fields in the class that is synthesized by D8/R8 would be used soon.
+ // TODO(b/165229577): Do we need this special handling of synthetics?
+ if (getSyntheticItems().isSyntheticClass(field.holder)) {
+ return true;
+ }
+ // For library classes we don't know whether a field is read.
+ return isLibraryOrClasspathField(encodedField);
}
public boolean isFieldWritten(DexEncodedField encodedField) {
@@ -828,15 +839,12 @@
// The field is written directly by the program itself.
return true;
}
- if (field.holder.isD8R8SynthesizedClassType()) {
- // Fields in the class that is synthesized by D8/R8 would be used soon.
+ // TODO(b/165229577): Do we need this special handling of synthetics?
+ if (getSyntheticItems().isSyntheticClass(field.holder)) {
return true;
}
- if (isLibraryOrClasspathField(encodedField)) {
- // For library classes we don't know whether a field is rewritten.
- return true;
- }
- return false;
+ // For library classes we don't know whether a field is rewritten.
+ return isLibraryOrClasspathField(encodedField);
}
public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
@@ -998,8 +1006,11 @@
.filter(AssertionUtils::assertNotNull)
.collect(Collectors.toList()));
+ DexDefinitionSupplier definitionSupplier =
+ application.getDefinitionsSupplier(SyntheticItems.createInitialSyntheticItems());
return new AppInfoWithLiveness(
application,
+ getSyntheticItems().commit(application, lens),
deadProtoTypes,
missingTypes,
lens.rewriteTypes(liveTypes),
@@ -1010,8 +1021,8 @@
lens.rewriteMethods(methodsTargetedByInvokeDynamic),
lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
lens.rewriteMethods(liveMethods),
- fieldAccessInfoCollection.rewrittenWithLens(application, lens),
- objectAllocationInfoCollection.rewrittenWithLens(application, lens),
+ fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
+ objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens),
rewriteInvokesWithContexts(virtualInvokes, lens),
rewriteInvokesWithContexts(interfaceInvokes, lens),
rewriteInvokesWithContexts(superInvokes, lens),
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java b/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java
new file mode 100644
index 0000000..a6d7326
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class ClassMergingEnqueuerExtension
+ implements EnqueuerInstanceOfAnalysis, EnqueuerCheckCastAnalysis {
+
+ private final Set<DexType> instanceOfTypes = Sets.newIdentityHashSet();
+ private final Set<DexType> checkCastTypes = Sets.newIdentityHashSet();
+ private final DexItemFactory factory;
+
+ public ClassMergingEnqueuerExtension(DexItemFactory factory) {
+ this.factory = factory;
+ }
+
+ @Override
+ public void traceCheckCast(DexType type, ProgramMethod context) {
+ checkCastTypes.add(type.toBaseType(factory));
+ }
+
+ @Override
+ public void traceInstanceOf(DexType type, ProgramMethod context) {
+ instanceOfTypes.add(type.toBaseType(factory));
+ }
+
+ public boolean isCheckCastType(DexProgramClass clazz) {
+ return checkCastTypes.contains(clazz.type);
+ }
+
+ public boolean isInstanceOfType(DexProgramClass clazz) {
+ return instanceOfTypes.contains(clazz.type);
+ }
+
+ public void attach(Enqueuer enqueuer) {
+ enqueuer.registerInstanceOfAnalysis(this).registerCheckCastAnalysis(this);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 9a488d1..34fb593 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -39,98 +39,98 @@
}
@Override
- public boolean registerInitClass(DexType clazz) {
- return enqueuer.traceInitClass(clazz, context);
+ public void registerInitClass(DexType clazz) {
+ enqueuer.traceInitClass(clazz, context);
}
@Override
- public boolean registerInvokeVirtual(DexMethod invokedMethod) {
- return enqueuer.traceInvokeVirtual(invokedMethod, context);
+ public void registerInvokeVirtual(DexMethod invokedMethod) {
+ enqueuer.traceInvokeVirtual(invokedMethod, context);
}
@Override
- public boolean registerInvokeDirect(DexMethod invokedMethod) {
- return enqueuer.traceInvokeDirect(invokedMethod, context);
+ public void registerInvokeDirect(DexMethod invokedMethod) {
+ enqueuer.traceInvokeDirect(invokedMethod, context);
}
@Override
- public boolean registerInvokeStatic(DexMethod invokedMethod) {
- return enqueuer.traceInvokeStatic(invokedMethod, context);
+ public void registerInvokeStatic(DexMethod invokedMethod) {
+ enqueuer.traceInvokeStatic(invokedMethod, context);
}
@Override
- public boolean registerInvokeInterface(DexMethod invokedMethod) {
- return enqueuer.traceInvokeInterface(invokedMethod, context);
+ public void registerInvokeInterface(DexMethod invokedMethod) {
+ enqueuer.traceInvokeInterface(invokedMethod, context);
}
@Override
- public boolean registerInvokeSuper(DexMethod invokedMethod) {
- return enqueuer.traceInvokeSuper(invokedMethod, context);
+ public void registerInvokeSuper(DexMethod invokedMethod) {
+ enqueuer.traceInvokeSuper(invokedMethod, context);
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
- return enqueuer.traceInstanceFieldRead(field, context);
+ public void registerInstanceFieldRead(DexField field) {
+ enqueuer.traceInstanceFieldRead(field, context);
}
@Override
- public boolean registerInstanceFieldReadFromMethodHandle(DexField field) {
- return enqueuer.traceInstanceFieldReadFromMethodHandle(field, context);
+ public void registerInstanceFieldReadFromMethodHandle(DexField field) {
+ enqueuer.traceInstanceFieldReadFromMethodHandle(field, context);
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return enqueuer.traceInstanceFieldWrite(field, context);
+ public void registerInstanceFieldWrite(DexField field) {
+ enqueuer.traceInstanceFieldWrite(field, context);
}
@Override
- public boolean registerInstanceFieldWriteFromMethodHandle(DexField field) {
- return enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context);
+ public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
+ enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context);
}
@Override
- public boolean registerNewInstance(DexType type) {
- return enqueuer.traceNewInstance(type, context);
+ public void registerNewInstance(DexType type) {
+ enqueuer.traceNewInstance(type, context);
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
- return enqueuer.traceStaticFieldRead(field, context);
+ public void registerStaticFieldRead(DexField field) {
+ enqueuer.traceStaticFieldRead(field, context);
}
@Override
- public boolean registerStaticFieldReadFromMethodHandle(DexField field) {
- return enqueuer.traceStaticFieldReadFromMethodHandle(field, context);
+ public void registerStaticFieldReadFromMethodHandle(DexField field) {
+ enqueuer.traceStaticFieldReadFromMethodHandle(field, context);
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
- return enqueuer.traceStaticFieldWrite(field, context);
+ public void registerStaticFieldWrite(DexField field) {
+ enqueuer.traceStaticFieldWrite(field, context);
}
@Override
- public boolean registerStaticFieldWriteFromMethodHandle(DexField field) {
- return enqueuer.traceStaticFieldWriteFromMethodHandle(field, context);
+ public void registerStaticFieldWriteFromMethodHandle(DexField field) {
+ enqueuer.traceStaticFieldWriteFromMethodHandle(field, context);
}
@Override
- public boolean registerConstClass(DexType type) {
- return enqueuer.traceConstClass(type, context);
+ public void registerConstClass(DexType type) {
+ enqueuer.traceConstClass(type, context);
}
@Override
- public boolean registerCheckCast(DexType type) {
- return enqueuer.traceCheckCast(type, context);
+ public void registerCheckCast(DexType type) {
+ enqueuer.traceCheckCast(type, context);
}
@Override
- public boolean registerTypeReference(DexType type) {
- return enqueuer.traceTypeReference(type, context);
+ public void registerTypeReference(DexType type) {
+ enqueuer.traceTypeReference(type, context);
}
@Override
- public boolean registerInstanceOf(DexType type) {
- return enqueuer.traceInstanceOf(type, context);
+ public void registerInstanceOf(DexType type) {
+ enqueuer.traceInstanceOf(type, context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 6ed64fe..ac6bad1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -918,12 +918,12 @@
}
}
- boolean traceCheckCast(DexType type, ProgramMethod currentMethod) {
+ void traceCheckCast(DexType type, ProgramMethod currentMethod) {
checkCastAnalyses.forEach(analysis -> analysis.traceCheckCast(type, currentMethod));
- return traceConstClassOrCheckCast(type, currentMethod);
+ traceConstClassOrCheckCast(type, currentMethod);
}
- boolean traceConstClass(DexType type, ProgramMethod currentMethod) {
+ void traceConstClass(DexType type, ProgramMethod currentMethod) {
// We conservatively group T.class and T[].class to ensure that we do not merge T with S if
// potential locks on T[].class and S[].class exists.
DexType baseType = type.toBaseType(appView.dexItemFactory());
@@ -933,12 +933,13 @@
constClassReferences.add(baseType);
}
}
- return traceConstClassOrCheckCast(type, currentMethod);
+ traceConstClassOrCheckCast(type, currentMethod);
}
- private boolean traceConstClassOrCheckCast(DexType type, ProgramMethod currentMethod) {
+ private void traceConstClassOrCheckCast(DexType type, ProgramMethod currentMethod) {
if (!forceProguardCompatibility) {
- return traceTypeReference(type, currentMethod);
+ traceTypeReference(type, currentMethod);
+ return;
}
DexType baseType = type.toBaseType(appView.dexItemFactory());
if (baseType.isClassType()) {
@@ -948,12 +949,10 @@
markClassAsInstantiatedWithCompatRule(
baseClass, graphReporter.reportCompatInstantiated(baseClass, currentMethod));
}
- return true;
}
- return false;
}
- boolean traceInitClass(DexType type, ProgramMethod currentMethod) {
+ void traceInitClass(DexType type, ProgramMethod currentMethod) {
assert type.isClassType();
Visibility oldMinimumRequiredVisibility = initClassReferences.get(type);
@@ -961,7 +960,7 @@
DexProgramClass clazz = getProgramClassOrNull(type);
if (clazz == null) {
assert false;
- return false;
+ return;
}
initClassReferences.put(
@@ -969,11 +968,11 @@
markTypeAsLive(type, classReferencedFromReporter(currentMethod));
markDirectAndIndirectClassInitializersAsLive(clazz);
- return true;
+ return;
}
if (oldMinimumRequiredVisibility.isPublic()) {
- return false;
+ return;
}
Visibility minimumRequiredVisibilityForCurrentMethod =
@@ -984,21 +983,20 @@
if (minimumRequiredVisibilityForCurrentMethod.isPublic()) {
initClassReferences.put(type, minimumRequiredVisibilityForCurrentMethod);
- return true;
+ return;
}
if (oldMinimumRequiredVisibility.isProtected()) {
- return false;
+ return;
}
if (minimumRequiredVisibilityForCurrentMethod.isProtected()) {
initClassReferences.put(type, minimumRequiredVisibilityForCurrentMethod);
- return true;
+ return;
}
assert oldMinimumRequiredVisibility.isPackagePrivate();
assert minimumRequiredVisibilityForCurrentMethod.isPackagePrivate();
- return false;
}
private Visibility computeMinimumRequiredVisibilityForInitClassField(
@@ -1037,17 +1035,16 @@
}
}
- boolean traceTypeReference(DexType type, ProgramMethod currentMethod) {
+ void traceTypeReference(DexType type, ProgramMethod currentMethod) {
markTypeAsLive(type, classReferencedFromReporter(currentMethod));
- return true;
}
- boolean traceInstanceOf(DexType type, ProgramMethod currentMethod) {
+ void traceInstanceOf(DexType type, ProgramMethod currentMethod) {
instanceOfAnalyses.forEach(analysis -> analysis.traceInstanceOf(type, currentMethod));
- return traceTypeReference(type, currentMethod);
+ traceTypeReference(type, currentMethod);
}
- boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
+ void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
boolean skipTracing =
registerDeferredActionForDeadProtoBuilder(
invokedMethod.holder,
@@ -1055,10 +1052,10 @@
() -> workList.enqueueTraceInvokeDirectAction(invokedMethod, context));
if (skipTracing) {
addDeadProtoTypeCandidate(invokedMethod.holder);
- return false;
+ return;
}
- return traceInvokeDirect(invokedMethod, context, KeepReason.invokedFrom(context));
+ traceInvokeDirect(invokedMethod, context, KeepReason.invokedFrom(context));
}
/** Returns true if a deferred action was registered. */
@@ -1075,56 +1072,51 @@
return false;
}
- boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeDirect(
- invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
+ void traceInvokeDirectFromLambda(DexMethod invokedMethod, ProgramMethod context) {
+ traceInvokeDirect(invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
}
- private boolean traceInvokeDirect(
+ private void traceInvokeDirect(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, context)) {
- return false;
+ return;
}
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeDirect `%s`.", invokedMethod);
}
handleInvokeOfDirectTarget(invokedMethod, reason);
invokeAnalyses.forEach(analysis -> analysis.traceInvokeDirect(invokedMethod, context));
- return true;
}
- boolean traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeInterface(invokedMethod, context, KeepReason.invokedFrom(context));
+ void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
+ traceInvokeInterface(invokedMethod, context, KeepReason.invokedFrom(context));
}
- boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeInterface(
- invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
+ void traceInvokeInterfaceFromLambda(DexMethod invokedMethod, ProgramMethod context) {
+ traceInvokeInterface(invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
}
- private boolean traceInvokeInterface(
+ private void traceInvokeInterface(
DexMethod method, ProgramMethod context, KeepReason keepReason) {
if (!registerMethodWithTargetAndContext(interfaceInvokes, method, context)) {
- return false;
+ return;
}
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeInterface `%s`.", method);
}
markVirtualMethodAsReachable(method, true, context, keepReason);
invokeAnalyses.forEach(analysis -> analysis.traceInvokeInterface(method, context));
- return true;
}
- boolean traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeStatic(invokedMethod, context, KeepReason.invokedFrom(context));
+ void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
+ traceInvokeStatic(invokedMethod, context, KeepReason.invokedFrom(context));
}
- boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeStatic(
- invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
+ void traceInvokeStaticFromLambda(DexMethod invokedMethod, ProgramMethod context) {
+ traceInvokeStatic(invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
}
- private boolean traceInvokeStatic(
+ private void traceInvokeStatic(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
@@ -1146,41 +1138,38 @@
pendingReflectiveUses.add(context);
}
if (!registerMethodWithTargetAndContext(staticInvokes, invokedMethod, context)) {
- return false;
+ return;
}
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeStatic `%s`.", invokedMethod);
}
handleInvokeOfStaticTarget(invokedMethod, reason);
invokeAnalyses.forEach(analysis -> analysis.traceInvokeStatic(invokedMethod, context));
- return true;
}
- boolean traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
+ void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
// We have to revisit super invokes based on the context they are found in. The same
// method descriptor will hit different targets, depending on the context it is used in.
DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, context);
if (!registerMethodWithTargetAndContext(superInvokes, invokedMethod, context)) {
- return false;
+ return;
}
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeSuper `%s`.", actualTarget);
}
workList.enqueueMarkReachableSuperAction(invokedMethod, context);
invokeAnalyses.forEach(analysis -> analysis.traceInvokeSuper(invokedMethod, context));
- return true;
}
- boolean traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeVirtual(invokedMethod, context, KeepReason.invokedFrom(context));
+ void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
+ traceInvokeVirtual(invokedMethod, context, KeepReason.invokedFrom(context));
}
- boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, ProgramMethod context) {
- return traceInvokeVirtual(
- invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
+ void traceInvokeVirtualFromLambda(DexMethod invokedMethod, ProgramMethod context) {
+ traceInvokeVirtual(invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
}
- private boolean traceInvokeVirtual(
+ private void traceInvokeVirtual(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
|| invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
@@ -1192,38 +1181,37 @@
pendingReflectiveUses.add(context);
}
if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context)) {
- return false;
+ return;
}
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeVirtual `%s`.", invokedMethod);
}
markVirtualMethodAsReachable(invokedMethod, false, context, reason);
invokeAnalyses.forEach(analysis -> analysis.traceInvokeVirtual(invokedMethod, context));
- return true;
}
- boolean traceNewInstance(DexType type, ProgramMethod context) {
+ void traceNewInstance(DexType type, ProgramMethod context) {
boolean skipTracing =
registerDeferredActionForDeadProtoBuilder(
type, context, () -> workList.enqueueTraceNewInstanceAction(type, context));
if (skipTracing) {
addDeadProtoTypeCandidate(type);
- return false;
+ return;
}
- return traceNewInstance(
+ traceNewInstance(
type,
context,
InstantiationReason.NEW_INSTANCE_INSTRUCTION,
KeepReason.instantiatedIn(context));
}
- boolean traceNewInstanceFromLambda(DexType type, ProgramMethod context) {
- return traceNewInstance(
+ void traceNewInstanceFromLambda(DexType type, ProgramMethod context) {
+ traceNewInstance(
type, context, InstantiationReason.LAMBDA, KeepReason.invokedFromLambdaCreatedIn(context));
}
- private boolean traceNewInstance(
+ private void traceNewInstance(
DexType type,
ProgramMethod context,
InstantiationReason instantiationReason,
@@ -1236,21 +1224,20 @@
workList.enqueueMarkInstantiatedAction(clazz, context, instantiationReason, keepReason);
}
}
- return true;
}
- boolean traceInstanceFieldRead(DexField field, ProgramMethod currentMethod) {
- return traceInstanceFieldRead(field, currentMethod, false);
+ void traceInstanceFieldRead(DexField field, ProgramMethod currentMethod) {
+ traceInstanceFieldRead(field, currentMethod, false);
}
- boolean traceInstanceFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
- return traceInstanceFieldRead(field, currentMethod, true);
+ void traceInstanceFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
+ traceInstanceFieldRead(field, currentMethod, true);
}
- private boolean traceInstanceFieldRead(
+ private void traceInstanceFieldRead(
DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
if (!registerFieldRead(fieldReference, currentMethod)) {
- return false;
+ return;
}
// Must mark the field as targeted even if it does not exist.
@@ -1258,14 +1245,14 @@
FieldResolutionResult resolutionResult = resolveField(fieldReference);
if (resolutionResult.isFailedOrUnknownResolution()) {
- return false;
+ return;
}
ProgramField field =
resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
if (field == null) {
// No need to trace into the non-program code.
- return false;
+ return;
}
if (fromMethodHandle) {
@@ -1287,21 +1274,20 @@
}
workList.enqueueMarkReachableFieldAction(field, KeepReason.fieldReferencedIn(currentMethod));
- return true;
}
- boolean traceInstanceFieldWrite(DexField field, ProgramMethod currentMethod) {
- return traceInstanceFieldWrite(field, currentMethod, false);
+ void traceInstanceFieldWrite(DexField field, ProgramMethod currentMethod) {
+ traceInstanceFieldWrite(field, currentMethod, false);
}
- boolean traceInstanceFieldWriteFromMethodHandle(DexField field, ProgramMethod currentMethod) {
- return traceInstanceFieldWrite(field, currentMethod, true);
+ void traceInstanceFieldWriteFromMethodHandle(DexField field, ProgramMethod currentMethod) {
+ traceInstanceFieldWrite(field, currentMethod, true);
}
- private boolean traceInstanceFieldWrite(
+ private void traceInstanceFieldWrite(
DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
if (!registerFieldWrite(fieldReference, currentMethod)) {
- return false;
+ return;
}
// Must mark the field as targeted even if it does not exist.
@@ -1309,14 +1295,14 @@
FieldResolutionResult resolutionResult = resolveField(fieldReference);
if (resolutionResult.isFailedOrUnknownResolution()) {
- return false;
+ return;
}
ProgramField field =
resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
if (field == null) {
// No need to trace into the non-program code.
- return false;
+ return;
}
if (fromMethodHandle) {
@@ -1339,35 +1325,34 @@
KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
workList.enqueueMarkReachableFieldAction(field, reason);
- return true;
}
- boolean traceStaticFieldRead(DexField field, ProgramMethod currentMethod) {
- return traceStaticFieldRead(field, currentMethod, false);
+ void traceStaticFieldRead(DexField field, ProgramMethod currentMethod) {
+ traceStaticFieldRead(field, currentMethod, false);
}
- boolean traceStaticFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
- return traceStaticFieldRead(field, currentMethod, true);
+ void traceStaticFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
+ traceStaticFieldRead(field, currentMethod, true);
}
- private boolean traceStaticFieldRead(
+ private void traceStaticFieldRead(
DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
if (!registerFieldRead(fieldReference, currentMethod)) {
- return false;
+ return;
}
FieldResolutionResult resolutionResult = resolveField(fieldReference);
if (resolutionResult.isFailedOrUnknownResolution()) {
// Must mark the field as targeted even if it does not exist.
markFieldAsTargeted(fieldReference, currentMethod);
- return false;
+ return;
}
ProgramField field =
resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
if (field == null) {
// No need to trace into the non-program code.
- return false;
+ return;
}
if (fromMethodHandle) {
@@ -1387,7 +1372,7 @@
false);
if (skipTracing) {
addDeadProtoTypeCandidate(field.getHolder());
- return false;
+ return;
}
}
@@ -1398,35 +1383,34 @@
}
markStaticFieldAsLive(field, KeepReason.fieldReferencedIn(currentMethod));
- return true;
}
- boolean traceStaticFieldWrite(DexField field, ProgramMethod currentMethod) {
- return traceStaticFieldWrite(field, currentMethod, false);
+ void traceStaticFieldWrite(DexField field, ProgramMethod currentMethod) {
+ traceStaticFieldWrite(field, currentMethod, false);
}
- boolean traceStaticFieldWriteFromMethodHandle(DexField field, ProgramMethod currentMethod) {
- return traceStaticFieldWrite(field, currentMethod, true);
+ void traceStaticFieldWriteFromMethodHandle(DexField field, ProgramMethod currentMethod) {
+ traceStaticFieldWrite(field, currentMethod, true);
}
- private boolean traceStaticFieldWrite(
+ private void traceStaticFieldWrite(
DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
if (!registerFieldWrite(fieldReference, currentMethod)) {
- return false;
+ return;
}
FieldResolutionResult resolutionResult = resolveField(fieldReference);
if (resolutionResult.isFailedOrUnknownResolution()) {
// Must mark the field as targeted even if it does not exist.
markFieldAsTargeted(fieldReference, currentMethod);
- return false;
+ return;
}
ProgramField field =
resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
if (field == null) {
// No need to trace into the non-program code.
- return false;
+ return;
}
if (fromMethodHandle) {
@@ -1446,7 +1430,7 @@
false);
if (skipTracing) {
addDeadProtoTypeCandidate(field.getHolder());
- return false;
+ return;
}
}
@@ -1457,7 +1441,6 @@
}
markStaticFieldAsLive(field, KeepReason.fieldReferencedIn(currentMethod));
- return true;
}
private Function<DexProgramClass, KeepReasonWitness> classReferencedFromReporter(
@@ -3032,6 +3015,7 @@
AppInfoWithLiveness appInfoWithLiveness =
new AppInfoWithLiveness(
app,
+ appInfo.getSyntheticItems().commit(app),
deadProtoTypes,
mode.isFinalTreeShaking()
? Sets.union(initialMissingTypes, missingTypes)
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 0382893..973691f 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -102,83 +102,78 @@
}
@Override
- public boolean registerInitClass(DexType clazz) {
+ public void registerInitClass(DexType clazz) {
consumer.accept(clazz);
- return true;
}
@Override
- public boolean registerInvokeVirtual(DexMethod method) {
- return registerInvoke(method);
+ public void registerInvokeVirtual(DexMethod method) {
+ registerInvoke(method);
}
@Override
- public boolean registerInvokeDirect(DexMethod method) {
- return registerInvoke(method);
+ public void registerInvokeDirect(DexMethod method) {
+ registerInvoke(method);
}
@Override
- public boolean registerInvokeStatic(DexMethod method) {
- return registerInvoke(method);
+ public void registerInvokeStatic(DexMethod method) {
+ registerInvoke(method);
}
@Override
- public boolean registerInvokeInterface(DexMethod method) {
- return registerInvoke(method);
+ public void registerInvokeInterface(DexMethod method) {
+ registerInvoke(method);
}
@Override
- public boolean registerInvokeSuper(DexMethod method) {
- return registerInvoke(method);
+ public void registerInvokeSuper(DexMethod method) {
+ registerInvoke(method);
}
- protected boolean registerInvoke(DexMethod method) {
+ protected void registerInvoke(DexMethod method) {
consumer.accept(method.holder);
traceMethodDirectDependencies(method, consumer);
- return true;
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return registerFieldAccess(field);
+ public void registerInstanceFieldWrite(DexField field) {
+ registerFieldAccess(field);
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
- return registerFieldAccess(field);
+ public void registerInstanceFieldRead(DexField field) {
+ registerFieldAccess(field);
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
- return registerFieldAccess(field);
+ public void registerStaticFieldRead(DexField field) {
+ registerFieldAccess(field);
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
- return registerFieldAccess(field);
+ public void registerStaticFieldWrite(DexField field) {
+ registerFieldAccess(field);
}
- protected boolean registerFieldAccess(DexField field) {
+ protected void registerFieldAccess(DexField field) {
consumer.accept(field.holder);
consumer.accept(field.type);
- return true;
}
@Override
- public boolean registerNewInstance(DexType type) {
+ public void registerNewInstance(DexType type) {
consumer.accept(type);
- return true;
}
@Override
- public boolean registerTypeReference(DexType type) {
+ public void registerTypeReference(DexType type) {
consumer.accept(type);
- return true;
}
@Override
- public boolean registerInstanceOf(DexType type) {
- return registerTypeReference(type);
+ public void registerInstanceOf(DexType type) {
+ registerTypeReference(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 58bbdae..1b51a73 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -74,9 +74,8 @@
"dontshrinkduringoptimization",
"convert_proto_enum_to_string");
- private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList.of(
- "isclassnamestring",
- "whyarenotsimple");
+ private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS =
+ ImmutableList.of("isclassnamestring", "whyarenotsimple", "convertchecknotnull");
private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList.of(
// TODO(b/37137994): -outjars should be reported as errors, not just as warnings!
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index ed87cff..0d803ea 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.shaking;
-import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -34,6 +35,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -59,75 +61,12 @@
*/
public class StaticClassMerger {
- enum MergeGroup {
+ private enum MergeGroup {
MAIN_DEX_ROOTS,
MAIN_DEX_DEPENDENCIES,
NOT_MAIN_DEX,
DONT_MERGE;
- private static final String GLOBAL = "<global>";
- private static Key mainDexRootsGlobalKey = new Key(MergeGroup.MAIN_DEX_ROOTS, GLOBAL);
- private static Key mainDexDependenciesGlobalKey =
- new Key(MergeGroup.MAIN_DEX_DEPENDENCIES, GLOBAL);
- private static Key notMainDexGlobalKey = new Key(MergeGroup.NOT_MAIN_DEX, GLOBAL);
-
- private static class Key {
- private final MergeGroup mergeGroup;
- private final String packageOrGlobal;
-
- public Key(MergeGroup mergeGroup, String packageOrGlobal) {
- this.mergeGroup = mergeGroup;
- this.packageOrGlobal = packageOrGlobal;
- }
-
- public MergeGroup getMergeGroup() {
- return mergeGroup;
- }
-
- public String getPackageOrGlobal() {
- return packageOrGlobal;
- }
-
- public boolean isGlobal() {
- return packageOrGlobal.equals(GLOBAL);
- }
-
- @Override
- public int hashCode() {
- return mergeGroup.ordinal() * 13 + packageOrGlobal.hashCode();
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
- if (other == null || this.getClass() != other.getClass()) {
- return false;
- }
- Key o = (Key) other;
- return o.mergeGroup == mergeGroup && o.packageOrGlobal.equals(packageOrGlobal);
- }
- }
-
- public Key globalKey() {
- switch (this) {
- case NOT_MAIN_DEX:
- return notMainDexGlobalKey;
- case MAIN_DEX_ROOTS:
- return mainDexRootsGlobalKey;
- case MAIN_DEX_DEPENDENCIES:
- return mainDexDependenciesGlobalKey;
- default:
- throw new Unreachable("Unexpected MergeGroup value");
- }
- }
-
- public Key key(String pkg) {
- assert this != DONT_MERGE;
- return new Key(this, pkg);
- }
-
@Override
public String toString() {
switch (this) {
@@ -144,6 +83,56 @@
}
}
+ private static class MergeKey {
+
+ private static final String GLOBAL = "<global>";
+
+ private final FeatureSplit featureSplit;
+ private final MergeGroup mergeGroup;
+ private final String packageOrGlobal;
+
+ public MergeKey(FeatureSplit featureSplit, MergeGroup mergeGroup, String packageOrGlobal) {
+ this.featureSplit = featureSplit;
+ this.mergeGroup = mergeGroup;
+ this.packageOrGlobal = packageOrGlobal;
+ }
+
+ public MergeGroup getMergeGroup() {
+ return mergeGroup;
+ }
+
+ public MergeKey toGlobal() {
+ return new MergeKey(featureSplit, mergeGroup, GLOBAL);
+ }
+
+ public String getPackageOrGlobal() {
+ return packageOrGlobal;
+ }
+
+ public boolean isGlobal() {
+ return packageOrGlobal.equals(GLOBAL);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(featureSplit, mergeGroup, packageOrGlobal);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (other == null || this.getClass() != other.getClass()) {
+ return false;
+ }
+ MergeKey o = (MergeKey) other;
+ return o.featureSplit == featureSplit
+ && o.mergeGroup == mergeGroup
+ && o.packageOrGlobal.equals(packageOrGlobal);
+ }
+ }
+
// There are 52 characters in [a-zA-Z], so with a capacity just below 52 the minifier should be
// able to find single-character names for all members, but around 30 appears to work better in
// practice.
@@ -204,7 +193,7 @@
private final Equivalence<DexField> fieldEquivalence;
private final Equivalence<DexMethod> methodEquivalence;
- private final Map<MergeGroup.Key, Representative> representatives = new HashMap<>();
+ private final Map<MergeKey, Representative> representatives = new HashMap<>();
private final BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
@@ -227,12 +216,7 @@
}
public NestedGraphLens run() {
- for (DexProgramClass clazz : appView.appInfo().app().classesWithDeterministicOrder()) {
- MergeGroup group = satisfiesMergeCriteria(clazz);
- if (group != MergeGroup.DONT_MERGE) {
- merge(clazz, group);
- }
- }
+ appView.appInfo().classesWithDeterministicOrder().forEach(this::merge);
if (Log.ENABLED) {
Log.info(
getClass(),
@@ -259,15 +243,14 @@
return null;
}
- private MergeGroup satisfiesMergeCriteria(DexProgramClass clazz) {
+ private FeatureSplitConfiguration getFeatureSplitConfiguration() {
+ return appView.options().featureSplitConfiguration;
+ }
+
+ private MergeGroup getMergeGroup(DexProgramClass clazz) {
if (appView.appInfo().neverMerge.contains(clazz.type)) {
return MergeGroup.DONT_MERGE;
}
- if (appView.options().featureSplitConfiguration != null &&
- appView.options().featureSplitConfiguration.isInFeature(clazz)) {
- // TODO(b/141452765): Allow class merging between classes in features.
- return MergeGroup.DONT_MERGE;
- }
if (clazz.staticFields().size() + clazz.getMethodCollection().size() == 0) {
return MergeGroup.DONT_MERGE;
}
@@ -291,7 +274,7 @@
if (Streams.stream(clazz.methods())
.anyMatch(
method ->
- method.accessFlags.isNative()
+ method.isNative()
|| appView.appInfo().isPinned(method.method)
// TODO(christofferqa): Remove the invariant that the graph lens should not
// modify any methods from the sets alwaysInline and noSideEffects.
@@ -319,33 +302,44 @@
return MergeGroup.NOT_MAIN_DEX;
}
+ private MergeKey getMergeKey(DexProgramClass clazz, MergeGroup mergeGroup) {
+ FeatureSplitConfiguration featureSplitConfiguration = getFeatureSplitConfiguration();
+ FeatureSplit featureSplit =
+ featureSplitConfiguration != null
+ ? featureSplitConfiguration.getFeatureSplit(clazz)
+ : FeatureSplit.BASE;
+ return new MergeKey(
+ featureSplit,
+ mergeGroup,
+ mayMergeAcrossPackageBoundaries(clazz)
+ ? MergeKey.GLOBAL
+ : clazz.type.getPackageDescriptor());
+ }
+
private boolean isValidRepresentative(DexProgramClass clazz) {
// Disallow interfaces from being representatives, since interface methods require desugaring.
return !clazz.isInterface();
}
- private boolean merge(DexProgramClass clazz, MergeGroup group) {
- assert satisfiesMergeCriteria(clazz) == group;
- assert group != MergeGroup.DONT_MERGE;
-
- return merge(
- clazz,
- mayMergeAcrossPackageBoundaries(clazz)
- ? group.globalKey()
- : group.key(clazz.type.getPackageDescriptor()));
+ private void merge(DexProgramClass clazz) {
+ MergeGroup mergeGroup = getMergeGroup(clazz);
+ if (mergeGroup != MergeGroup.DONT_MERGE) {
+ merge(clazz, mergeGroup);
+ }
}
- private boolean merge(DexProgramClass clazz, MergeGroup.Key key) {
+ private void merge(DexProgramClass clazz, MergeGroup mergeGroup) {
+ MergeKey key = getMergeKey(clazz, mergeGroup);
Representative representative = representatives.get(key);
if (representative != null) {
if (representative.hasSynchronizedMethods && clazz.hasStaticSynchronizedMethods()) {
// We are not allowed to merge synchronized classes with synchronized methods.
- return false;
+ return;
}
if (appView.appInfo().constClassReferences.contains(clazz.type)) {
// Since the type is const-class referenced (and the static merger does not create a lens
// to map the merged type) the class will likely remain and there is no gain from merging.
- return false;
+ return;
}
// Check if current candidate is a better choice depending on visibility. For package private
// or protected, the key is parameterized by the package name already, so we just have to
@@ -359,24 +353,23 @@
if (!newRepresentative.isFull()) {
setRepresentative(key, newRepresentative);
moveMembersFromSourceToTarget(representative.clazz, clazz);
- return true;
+ return;
}
} else {
representative.include(clazz);
if (!representative.isFull()) {
moveMembersFromSourceToTarget(clazz, representative.clazz);
- return true;
+ return;
}
}
}
if (isValidRepresentative(clazz)) {
setRepresentative(key, getOrCreateRepresentative(key, clazz));
}
- return false;
}
- private Representative getOrCreateRepresentative(MergeGroup.Key key, DexProgramClass clazz) {
- Representative globalRepresentative = representatives.get(key.getMergeGroup().globalKey());
+ private Representative getOrCreateRepresentative(MergeKey key, DexProgramClass clazz) {
+ Representative globalRepresentative = representatives.get(key.toGlobal());
if (globalRepresentative != null && globalRepresentative.clazz == clazz) {
return globalRepresentative;
}
@@ -387,7 +380,7 @@
return new Representative(clazz);
}
- private void setRepresentative(MergeGroup.Key key, Representative representative) {
+ private void setRepresentative(MergeKey key, Representative representative) {
assert isValidRepresentative(representative.clazz);
if (Log.ENABLED) {
if (key.isGlobal()) {
@@ -408,22 +401,10 @@
representatives.put(key, representative);
}
- private void clearRepresentative(MergeGroup.Key key) {
- if (Log.ENABLED) {
- if (key.isGlobal()) {
- Log.info(getClass(), "Removing the global representative");
- } else {
- Log.info(
- getClass(), "Removing the representative for package %s", key.getPackageOrGlobal());
- }
- }
- representatives.remove(key);
- }
-
private boolean mayMergeAcrossPackageBoundaries(DexProgramClass clazz) {
// Check that the class is public. Otherwise, accesses to `clazz` from within its current
// package may become illegal.
- if (!clazz.accessFlags.isPublic()) {
+ if (!clazz.isPublic()) {
return false;
}
// Check that all of the members are private or public.
@@ -471,6 +452,8 @@
assert targetClass.accessFlags.isAtLeastAsVisibleAs(sourceClass.accessFlags);
assert sourceClass.instanceFields().isEmpty();
assert targetClass.instanceFields().isEmpty();
+ assert getFeatureSplitConfiguration() == null
+ || getFeatureSplitConfiguration().inSameFeatureOrBothInBase(sourceClass, targetClass);
numberOfMergedClasses++;
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index be956c3..5231fe6 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -36,7 +36,7 @@
private final AppView<AppInfoWithLiveness> appView;
private final TreePrunerConfiguration configuration;
- private final UsagePrinter usagePrinter;
+ private final UnusedItemsPrinter unusedItemsPrinter;
private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
private final Set<DexMethod> methodsToKeepForConfigurationDebugging = Sets.newIdentityHashSet();
@@ -48,13 +48,13 @@
InternalOptions options = appView.options();
this.appView = appView;
this.configuration = configuration;
- this.usagePrinter =
+ this.unusedItemsPrinter =
options.hasUsageInformationConsumer()
- ? new UsagePrinter(
+ ? new UnusedItemsPrinter(
s ->
ExceptionUtils.withConsumeResourceHandler(
options.reporter, options.usageInformationConsumer, s))
- : UsagePrinter.DONT_PRINT;
+ : UnusedItemsPrinter.DONT_PRINT;
}
public DirectMappedDexApplication run() {
@@ -114,10 +114,11 @@
// clazz.type.isD8R8SynthesizedType, but such test is currently expensive since it is
// based on strings, so we check only against the enum unboxing utility class.
if (clazz.type != appView.dexItemFactory().enumUnboxingUtilityType) {
- usagePrinter.printUnusedClass(clazz);
+ unusedItemsPrinter.registerUnusedClass(clazz);
}
}
}
+ unusedItemsPrinter.finished();
return newClasses;
}
@@ -160,7 +161,7 @@
}
private void pruneMembersAndAttributes(DexProgramClass clazz) {
- usagePrinter.visiting(clazz);
+ unusedItemsPrinter.visiting(clazz);
DexEncodedMethod[] reachableDirectMethods = reachableMethods(clazz.directMethods(), clazz);
if (reachableDirectMethods != null) {
clazz.setDirectMethods(reachableDirectMethods);
@@ -181,7 +182,7 @@
clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
clazz.removeEnclosingMethodAttribute(this::isAttributeReferencingPrunedItem);
rewriteNestAttributes(clazz);
- usagePrinter.visited();
+ unusedItemsPrinter.visited();
assert verifyNoDeadFields(clazz);
}
@@ -318,7 +319,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Removing method %s.", method.method);
}
- usagePrinter.printUnusedMethod(method);
+ unusedItemsPrinter.registerUnusedMethod(method);
}
}
return reachableMethods.isEmpty()
@@ -338,7 +339,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Removing field %s.", fields.get(firstUnreachable));
}
- usagePrinter.printUnusedField(fields.get(firstUnreachable));
+ unusedItemsPrinter.registerUnusedField(fields.get(firstUnreachable));
ArrayList<DexEncodedField> reachableOrReferencedFields = new ArrayList<>(fields.size());
for (int i = 0; i < firstUnreachable; i++) {
reachableOrReferencedFields.add(fields.get(i));
@@ -351,7 +352,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Removing field %s.", field.field);
}
- usagePrinter.printUnusedField(field);
+ unusedItemsPrinter.registerUnusedField(field);
}
}
return reachableOrReferencedFields.isEmpty()
diff --git a/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java b/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java
new file mode 100644
index 0000000..f1e2fa3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+class UnusedItemsPrinter {
+
+ private static class Members {
+ final List<DexEncodedField> fields = new ArrayList<>();
+ final List<DexEncodedMethod> methods = new ArrayList<>();
+
+ boolean hasMembers() {
+ return !fields.isEmpty() || !methods.isEmpty();
+ }
+
+ void sort() {
+ fields.sort((a, b) -> a.toReference().slowCompareTo(b.toReference()));
+ methods.sort((a, b) -> a.toReference().slowCompareTo(b.toReference()));
+ }
+ }
+
+ private static final String INDENT = " ";
+
+ static final UnusedItemsPrinter DONT_PRINT = new NopPrinter();
+
+ private final Consumer<String> consumer;
+
+ private DexType currentType = null;
+ private Members currentMembers = new Members();
+
+ private List<Pair<DexType, Members>> classes = new ArrayList<>();
+
+ UnusedItemsPrinter(Consumer<String> consumer) {
+ this.consumer = consumer;
+ }
+
+ void registerUnusedClass(DexProgramClass clazz) {
+ assert currentType == null;
+ classes.add(new Pair<>(clazz.type, null));
+ }
+
+ // Visiting methods and fields of the given clazz.
+ void visiting(DexProgramClass clazz) {
+ assert currentType == null;
+ currentType = clazz.type;
+ }
+
+ // Visited methods and fields of the top at the clazz stack.
+ void visited() {
+ if (currentMembers.hasMembers()) {
+ classes.add(new Pair<>(currentType, currentMembers));
+ currentMembers = new Members();
+ }
+ currentType = null;
+ }
+
+ void registerUnusedMethod(DexEncodedMethod method) {
+ currentMembers.methods.add(method);
+ }
+
+ void registerUnusedField(DexEncodedField field) {
+ currentMembers.fields.add(field);
+ }
+
+ public void finished() {
+ classes.sort((a, b) -> a.getFirst().slowCompareTo(b.getFirst()));
+ for (Pair<DexType, Members> entry : classes) {
+ DexType type = entry.getFirst();
+ Members members = entry.getSecond();
+ consumer.accept(type.toSourceString());
+ if (members == null) {
+ consumer.accept(StringUtils.LINE_SEPARATOR);
+ } else {
+ consumer.accept(":" + StringUtils.LINE_SEPARATOR);
+ members.sort();
+ members.fields.forEach(this::printUnusedField);
+ members.methods.forEach(this::printUnusedMethod);
+ }
+ }
+ classes = null;
+ }
+
+ private void append(String string) {
+ consumer.accept(string);
+ }
+
+ private void newline() {
+ append(StringUtils.LINE_SEPARATOR);
+ }
+
+ private void printUnusedMethod(DexEncodedMethod method) {
+ append(INDENT);
+ String accessFlags = method.accessFlags.toString();
+ if (!accessFlags.isEmpty()) {
+ append(accessFlags);
+ append(" ");
+ }
+ append(method.method.proto.returnType.toSourceString());
+ append(" ");
+ append(method.method.name.toSourceString());
+ append("(");
+ for (int i = 0; i < method.method.proto.parameters.values.length; i++) {
+ if (i != 0) {
+ append(",");
+ }
+ append(method.method.proto.parameters.values[i].toSourceString());
+ }
+ append(")");
+ newline();
+ }
+
+ private void printUnusedField(DexEncodedField field) {
+ append(INDENT);
+ String accessFlags = field.accessFlags.toString();
+ if (!accessFlags.isEmpty()) {
+ append(accessFlags);
+ append(" ");
+ }
+ append(field.field.type.toSourceString());
+ append(" ");
+ append(field.field.name.toSourceString());
+ newline();
+ }
+
+ // Empty implementation to silently ignore printing dead code.
+ private static class NopPrinter extends UnusedItemsPrinter {
+
+ public NopPrinter() {
+ super(null);
+ }
+
+ @Override
+ void registerUnusedClass(DexProgramClass clazz) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ void visiting(DexProgramClass clazz) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ void visited() {
+ // Intentionally left empty.
+ }
+
+ @Override
+ void registerUnusedMethod(DexEncodedMethod method) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ void registerUnusedField(DexEncodedField field) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ public void finished() {
+ // Intentionally left empty.
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java b/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
deleted file mode 100644
index 4c04b52..0000000
--- a/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.shaking;
-
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.utils.StringUtils;
-import java.util.function.Consumer;
-
-class UsagePrinter {
- private static final String INDENT = " ";
-
- static final UsagePrinter DONT_PRINT = new NoOpUsagePrinter();
-
- private final Consumer<String> consumer;
- private DexProgramClass enclosingClazz = null;
- private boolean clazzPrefixPrinted = false;
-
- UsagePrinter(Consumer<String> consumer) {
- this.consumer = consumer;
- }
-
- void append(String string) {
- consumer.accept(string);
- }
-
- void printUnusedClass(DexProgramClass clazz) {
- append(clazz.toSourceString());
- append(StringUtils.LINE_SEPARATOR);
- }
-
- // Visiting methods and fields of the given clazz.
- void visiting(DexProgramClass clazz) {
- assert enclosingClazz == null;
- enclosingClazz = clazz;
- }
-
- // Visited methods and fields of the top at the clazz stack.
- void visited() {
- enclosingClazz = null;
- clazzPrefixPrinted = false;
- }
-
- private void printClazzPrefixIfNecessary() {
- assert enclosingClazz != null;
- if (!clazzPrefixPrinted) {
- append(enclosingClazz.toSourceString());
- append(":");
- append(StringUtils.LINE_SEPARATOR);
- clazzPrefixPrinted = true;
- }
- }
-
- void printUnusedMethod(DexEncodedMethod method) {
- printClazzPrefixIfNecessary();
- append(INDENT);
- String accessFlags = method.accessFlags.toString();
- if (!accessFlags.isEmpty()) {
- append(accessFlags);
- append(" ");
- }
- append(method.method.proto.returnType.toSourceString());
- append(" ");
- append(method.method.name.toSourceString());
- append("(");
- for (int i = 0; i < method.method.proto.parameters.values.length; i++) {
- if (i != 0) {
- append(",");
- }
- append(method.method.proto.parameters.values[i].toSourceString());
- }
- append(")");
- append(StringUtils.LINE_SEPARATOR);
- }
-
- void printUnusedField(DexEncodedField field) {
- printClazzPrefixIfNecessary();
- append(INDENT);
- String accessFlags = field.accessFlags.toString();
- if (!accessFlags.isEmpty()) {
- append(accessFlags);
- append(" ");
- }
- append(field.field.type.toSourceString());
- append(" ");
- append(field.field.name.toSourceString());
- append(StringUtils.LINE_SEPARATOR);
- }
-
- // Empty implementation to silently ignore printing dead code.
- private static class NoOpUsagePrinter extends UsagePrinter {
-
- public NoOpUsagePrinter() {
- super(null);
- }
-
- @Override
- void printUnusedClass(DexProgramClass clazz) {
- // Intentionally left empty.
- }
-
- @Override
- void visiting(DexProgramClass clazz) {
- // Intentionally left empty.
- }
-
- @Override
- void visited() {
- // Intentionally left empty.
- }
-
- @Override
- void printUnusedMethod(DexEncodedMethod method) {
- // Intentionally left empty.
- }
-
- @Override
- void printUnusedField(DexEncodedField field) {
- // Intentionally left empty.
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 1de617d..9e709fd 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
@@ -357,9 +358,10 @@
.map(DexEncodedMember::toReference)
.noneMatch(appInfo::isPinned);
- if (appView.options().featureSplitConfiguration != null
- && appView.options().featureSplitConfiguration.isInFeature(sourceClass)) {
- // TODO(b/141452765): Allow class merging between classes in features.
+ FeatureSplitConfiguration featureSplitConfiguration =
+ appView.options().featureSplitConfiguration;
+ if (featureSplitConfiguration != null
+ && !featureSplitConfiguration.inSameFeatureOrBothInBase(sourceClass, targetClass)) {
return false;
}
if (appView.appServices().allServiceTypes().contains(sourceClass.type)
@@ -1790,7 +1792,7 @@
this.context = context;
}
- private boolean checkFieldReference(DexField field) {
+ private void checkFieldReference(DexField field) {
if (!foundIllegalAccess) {
DexType baseType =
appView.graphLens().lookupType(field.holder.toBaseType(appView.dexItemFactory()));
@@ -1804,10 +1806,9 @@
}
}
}
- return true;
}
- private boolean checkMethodReference(DexMethod method, OptionalBool isInterface) {
+ private void checkMethodReference(DexMethod method, OptionalBool isInterface) {
if (!foundIllegalAccess) {
DexType baseType =
appView.graphLens().lookupType(method.holder.toBaseType(appView.dexItemFactory()));
@@ -1827,10 +1828,9 @@
}
}
}
- return true;
}
- private boolean checkTypeReference(DexType type) {
+ private void checkTypeReference(DexType type) {
if (!foundIllegalAccess) {
DexType baseType =
appView.graphLens().lookupType(type.toBaseType(appView.dexItemFactory()));
@@ -1841,87 +1841,86 @@
}
}
}
- return true;
}
@Override
- public boolean registerInitClass(DexType clazz) {
- return checkTypeReference(clazz);
+ public void registerInitClass(DexType clazz) {
+ checkTypeReference(clazz);
}
@Override
- public boolean registerInvokeVirtual(DexMethod method) {
+ public void registerInvokeVirtual(DexMethod method) {
assert context != null;
GraphLensLookupResult lookup =
appView.graphLens().lookupMethod(method, context.getReference(), Type.VIRTUAL);
- return checkMethodReference(lookup.getMethod(), OptionalBool.FALSE);
+ checkMethodReference(lookup.getMethod(), OptionalBool.FALSE);
}
@Override
- public boolean registerInvokeDirect(DexMethod method) {
+ public void registerInvokeDirect(DexMethod method) {
assert context != null;
GraphLensLookupResult lookup =
appView.graphLens().lookupMethod(method, context.getReference(), Type.DIRECT);
- return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
+ checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
}
@Override
- public boolean registerInvokeStatic(DexMethod method) {
+ public void registerInvokeStatic(DexMethod method) {
assert context != null;
GraphLensLookupResult lookup =
appView.graphLens().lookupMethod(method, context.getReference(), Type.STATIC);
- return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
+ checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
}
@Override
- public boolean registerInvokeInterface(DexMethod method) {
+ public void registerInvokeInterface(DexMethod method) {
assert context != null;
GraphLensLookupResult lookup =
appView.graphLens().lookupMethod(method, context.getReference(), Type.INTERFACE);
- return checkMethodReference(lookup.getMethod(), OptionalBool.TRUE);
+ checkMethodReference(lookup.getMethod(), OptionalBool.TRUE);
}
@Override
- public boolean registerInvokeSuper(DexMethod method) {
+ public void registerInvokeSuper(DexMethod method) {
assert context != null;
GraphLensLookupResult lookup =
appView.graphLens().lookupMethod(method, context.getReference(), Type.SUPER);
- return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
+ checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
}
@Override
- public boolean registerInstanceFieldWrite(DexField field) {
- return checkFieldReference(appView.graphLens().lookupField(field));
+ public void registerInstanceFieldWrite(DexField field) {
+ checkFieldReference(appView.graphLens().lookupField(field));
}
@Override
- public boolean registerInstanceFieldRead(DexField field) {
- return checkFieldReference(appView.graphLens().lookupField(field));
+ public void registerInstanceFieldRead(DexField field) {
+ checkFieldReference(appView.graphLens().lookupField(field));
}
@Override
- public boolean registerNewInstance(DexType type) {
- return checkTypeReference(type);
+ public void registerNewInstance(DexType type) {
+ checkTypeReference(type);
}
@Override
- public boolean registerStaticFieldRead(DexField field) {
- return checkFieldReference(appView.graphLens().lookupField(field));
+ public void registerStaticFieldRead(DexField field) {
+ checkFieldReference(appView.graphLens().lookupField(field));
}
@Override
- public boolean registerStaticFieldWrite(DexField field) {
- return checkFieldReference(appView.graphLens().lookupField(field));
+ public void registerStaticFieldWrite(DexField field) {
+ checkFieldReference(appView.graphLens().lookupField(field));
}
@Override
- public boolean registerTypeReference(DexType type) {
- return checkTypeReference(type);
+ public void registerTypeReference(DexType type) {
+ checkTypeReference(type);
}
@Override
- public boolean registerInstanceOf(DexType type) {
- return checkTypeReference(type);
+ public void registerInstanceOf(DexType type) {
+ checkTypeReference(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 2d78c0d..8c070a5 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -182,8 +182,14 @@
* @return className "org/foo/bar/Baz.Nested"
*/
public static String descriptorToKotlinClassifier(String descriptor) {
- return getBinaryNameFromDescriptor(descriptor)
- .replace(INNER_CLASS_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
+ final String classifier =
+ getBinaryNameFromDescriptor(descriptor)
+ .replace(INNER_CLASS_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
+ if (descriptor.startsWith("Lj$/")) {
+ assert classifier.startsWith("j./");
+ return "j$/" + classifier.substring(3);
+ }
+ return classifier;
}
/**
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 dfd253d..69410dd 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -226,8 +226,7 @@
public boolean enableInlining =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableinlining") == null;
- // TODO(b/160854837): re-enable enum unboxing.
- public boolean enableEnumUnboxing = false;
+ public boolean enableEnumUnboxing = true;
// TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
public boolean applyInliningToInlinee =
System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
@@ -326,7 +325,12 @@
.setVersion(Version.LABEL)
.setCompilationMode(debug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
.setHasChecksums(encodeChecksums);
- if (!isGeneratingClassFiles()) {
+ // Compiling with D8 and L8 is always with a min API level and desugaring to that level. If
+ // desugaring is explicitly turned off for D8 the input is expected to already have been
+ // desugared to the specified min API level. For R8 desugaring is optional.
+ if (tool == Tool.D8
+ || tool == Tool.L8
+ || (tool == Tool.R8 && desugarState != DesugarState.OFF)) {
marker.setMinApi(minApiLevel);
}
if (desugaredLibraryConfiguration.getIdentifier() != null) {
@@ -396,6 +400,10 @@
return programConsumer instanceof ClassFileConsumer;
}
+ public boolean isDesugaring() {
+ return !isGeneratingClassFiles() || cfToCfDesugar;
+ }
+
public DexIndexedConsumer getDexIndexedConsumer() {
return (DexIndexedConsumer) programConsumer;
}
@@ -505,6 +513,14 @@
return isMinifying();
}
+ /**
+ * If any non-static class merging is enabled, information about types referred to by instanceOf
+ * and check cast instructions needs to be collected.
+ */
+ public boolean isClassMergingExtensionRequired() {
+ return enableHorizontalClassMerging || enableVerticalClassMerging;
+ }
+
@Override
public boolean isAccessModificationEnabled() {
return getProguardConfiguration() != null
@@ -1061,7 +1077,19 @@
// Repackaging all classes into the single user-given (or top-level) package.
REPACKAGE,
// Repackaging all packages into the single user-given (or top-level) package.
- FLATTEN
+ FLATTEN;
+
+ public boolean isNone() {
+ return this == NONE;
+ }
+
+ public boolean isFlattenPackageHierarchy() {
+ return this == FLATTEN;
+ }
+
+ public boolean isRepackageClasses() {
+ return this == REPACKAGE;
+ }
}
public static class OutlineOptions {
@@ -1185,6 +1213,7 @@
public boolean alwaysUsePessimisticRegisterAllocation = false;
public boolean enableCheckCastAndInstanceOfRemoval = true;
public boolean enableDeadSwitchCaseElimination = true;
+ public boolean enableExperimentalRepackaging = false;
public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
public boolean enableSwitchToIfRewriting = true;
public boolean enableEnumUnboxingDebugLogs = false;
@@ -1208,6 +1237,10 @@
public boolean enumUnboxingRewriteJavaCGeneratedMethod = false;
public boolean assertConsistentRenamingOfSignature = false;
+ // Flag to allow processing of resources in D8. A data resource consumer still needs to be
+ // specified.
+ public boolean enableD8ResourcesPassThrough = false;
+
// TODO(b/144781417): This is disabled by default as some test apps appear to have such classes.
public boolean allowNonAbstractClassesWithAbstractMethods = true;
@@ -1266,7 +1299,6 @@
}
private boolean hasMinApi(AndroidApiLevel level) {
- assert isGeneratingDex();
return minApiLevel >= level.getLevel();
}
@@ -1317,23 +1349,23 @@
}
public boolean canUseDefaultAndStaticInterfaceMethods() {
- return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.N);
+ return !isDesugaring() || hasMinApi(AndroidApiLevel.N);
}
public boolean canUseNestBasedAccess() {
- return isGeneratingClassFiles();
+ return !isDesugaring();
}
public boolean canLeaveStaticInterfaceMethodInvokes() {
- return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.L);
+ return !isDesugaring() || hasMinApi(AndroidApiLevel.L);
}
public boolean canUseTwrCloseResourceMethod() {
- return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K);
+ return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
}
public boolean canUsePrivateInterfaceMethods() {
- return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.N);
+ return !isDesugaring() || hasMinApi(AndroidApiLevel.N);
}
public boolean canUseDexPcAsDebugInformation() {
@@ -1348,7 +1380,7 @@
}
return desugarState == DesugarState.ON
&& interfaceMethodDesugaring == OffOrAuto.Auto
- && (!canUseDefaultAndStaticInterfaceMethods() || cfToCfDesugar);
+ && !canUseDefaultAndStaticInterfaceMethods();
}
public boolean isStringSwitchConversionEnabled() {
@@ -1365,11 +1397,11 @@
}
public boolean canUseSuppressedExceptions() {
- return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K);
+ return (isGeneratingClassFiles() && !cfToCfDesugar) || hasMinApi(AndroidApiLevel.K);
}
public boolean canUseAssertionErrorTwoArgumentConstructor() {
- return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K);
+ return (isGeneratingClassFiles() && !cfToCfDesugar) || hasMinApi(AndroidApiLevel.K);
}
// The Apache Harmony-based AssertionError constructor which takes an Object on API 15 and older
@@ -1379,7 +1411,7 @@
//
// https://android.googlesource.com/platform/libcore/+/refs/heads/ics-mr1/luni/src/main/java/java/lang/AssertionError.java#56
public boolean canInitCauseAfterAssertionErrorObjectConstructor() {
- return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.J);
+ return (isGeneratingClassFiles() && !cfToCfDesugar) || hasMinApi(AndroidApiLevel.J);
}
// Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
diff --git a/src/test/java/com/android/tools/r8/D8TestRunResult.java b/src/test/java/com/android/tools/r8/D8TestRunResult.java
index cd4b663..4e13e54 100644
--- a/src/test/java/com/android/tools/r8/D8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestRunResult.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
-public class D8TestRunResult extends TestRunResult<D8TestRunResult> {
+public class D8TestRunResult extends SingleTestRunResult<D8TestRunResult> {
public D8TestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
super(app, runtime, result);
diff --git a/src/test/java/com/android/tools/r8/DXTestRunResult.java b/src/test/java/com/android/tools/r8/DXTestRunResult.java
index bcec2f3..b395e90 100644
--- a/src/test/java/com/android/tools/r8/DXTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestRunResult.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
-public class DXTestRunResult extends TestRunResult<DXTestRunResult> {
+public class DXTestRunResult extends SingleTestRunResult<DXTestRunResult> {
public DXTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
super(app, runtime, result);
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
index f364232..cb33702 100644
--- a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -11,7 +11,7 @@
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matcher;
-public class Dex2OatTestRunResult extends TestRunResult<Dex2OatTestRunResult> {
+public class Dex2OatTestRunResult extends SingleTestRunResult<Dex2OatTestRunResult> {
public Dex2OatTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
super(app, runtime, result);
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
index 28e2835..469e5b5 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
@@ -13,7 +13,7 @@
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
-public class ExternalR8TestRunResult extends TestRunResult<ExternalR8TestRunResult> {
+public class ExternalR8TestRunResult extends SingleTestRunResult<ExternalR8TestRunResult> {
private final Path outputJar;
private final String proguardMap;
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
index 166feeb..8f2e9b1 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
@@ -6,7 +6,8 @@
import java.util.List;
-public class GenerateMainDexListRunResult extends TestRunResult<GenerateMainDexListRunResult> {
+public class GenerateMainDexListRunResult
+ extends SingleTestRunResult<GenerateMainDexListRunResult> {
List<String> mainDexList;
diff --git a/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java b/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
new file mode 100644
index 0000000..fcefd23
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+
+public class IntermediateCfD8TestBuilder
+ extends TestBuilder<D8TestRunResult, IntermediateCfD8TestBuilder> {
+
+ public static IntermediateCfD8TestBuilder create(TestState state, AndroidApiLevel apiLevel) {
+ assert state != null;
+ assert apiLevel != null;
+ return new IntermediateCfD8TestBuilder(state, apiLevel);
+ }
+
+ private final D8TestBuilder cf2cf;
+ private final D8TestBuilder cf2dex;
+
+ private IntermediateCfD8TestBuilder(TestState state, AndroidApiLevel apiLevel) {
+ super(state);
+ cf2cf = D8TestBuilder.create(state, Backend.CF).setMinApi(apiLevel);
+ cf2dex =
+ D8TestBuilder.create(state, Backend.DEX).setMinApi(apiLevel).setDisableDesugaring(true);
+ }
+
+ @Override
+ IntermediateCfD8TestBuilder self() {
+ return this;
+ }
+
+ @Override
+ public D8TestRunResult run(TestRuntime runtime, String mainClass, String... args)
+ throws CompilationFailedException, ExecutionException, IOException {
+ return cf2dex.addProgramFiles(cf2cf.compile().writeToZip()).run(runtime, mainClass, args);
+ }
+
+ @Override
+ public DebugTestConfig debugConfig() {
+ throw new Unimplemented("Unsupported debug config as of now...");
+ }
+
+ @Override
+ public IntermediateCfD8TestBuilder addProgramFiles(Collection<Path> files) {
+ cf2cf.addProgramFiles(files);
+ return self();
+ }
+
+ @Override
+ public IntermediateCfD8TestBuilder addProgramClassFileData(Collection<byte[]> classes) {
+ cf2cf.addProgramClassFileData(classes);
+ return self();
+ }
+
+ @Override
+ public IntermediateCfD8TestBuilder addProgramDexFileData(Collection<byte[]> data) {
+ cf2cf.addProgramDexFileData(data);
+ return self();
+ }
+
+ @Override
+ public IntermediateCfD8TestBuilder addLibraryFiles(Collection<Path> files) {
+ cf2cf.addLibraryFiles(files);
+ cf2dex.addLibraryFiles(files);
+ return self();
+ }
+
+ @Override
+ public IntermediateCfD8TestBuilder addLibraryClasses(Collection<Class<?>> classes) {
+ cf2cf.addLibraryClasses(classes);
+ cf2dex.addLibraryClasses(classes);
+ return self();
+ }
+
+ @Override
+ public IntermediateCfD8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
+ cf2cf.addClasspathClasses(classes);
+ cf2dex.addClasspathClasses(classes);
+ return self();
+ }
+
+ @Override
+ public IntermediateCfD8TestBuilder addClasspathFiles(Collection<Path> files) {
+ cf2cf.addClasspathFiles(files);
+ cf2dex.addClasspathFiles(files);
+ return self();
+ }
+
+ @Override
+ public IntermediateCfD8TestBuilder addRunClasspathFiles(Collection<Path> files) {
+ cf2dex.addRunClasspathFiles(files);
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JvmTestRunResult.java b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
index a94fd38..fdd2580 100644
--- a/src/test/java/com/android/tools/r8/JvmTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
-public class JvmTestRunResult extends TestRunResult<JvmTestRunResult> {
+public class JvmTestRunResult extends SingleTestRunResult<JvmTestRunResult> {
public JvmTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
super(app, runtime, result);
diff --git a/src/test/java/com/android/tools/r8/MarkerMatcher.java b/src/test/java/com/android/tools/r8/MarkerMatcher.java
index 13065b7..ea6ea4a 100644
--- a/src/test/java/com/android/tools/r8/MarkerMatcher.java
+++ b/src/test/java/com/android/tools/r8/MarkerMatcher.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -19,6 +20,10 @@
public abstract class MarkerMatcher extends TypeSafeMatcher<Marker> {
+ public static void assertMarkersMatch(Iterable<Marker> markers, Matcher<Marker> matcher) {
+ assertMarkersMatch(markers, ImmutableList.of(matcher));
+ }
+
public static void assertMarkersMatch(
Iterable<Marker> markers, Collection<Matcher<Marker>> matchers) {
// Match is unordered, but we make no attempts to find the maximum match.
@@ -103,6 +108,20 @@
};
}
+ public static Matcher<Marker> markerIsDesugared() {
+ return new MarkerMatcher() {
+ @Override
+ protected boolean eval(Marker marker) {
+ return marker.isDesugared();
+ }
+
+ @Override
+ protected void explain(Description description) {
+ description.appendText("desugared ");
+ }
+ };
+ }
+
public static Matcher<Marker> markerMinApi(AndroidApiLevel level) {
return new MarkerMatcher() {
@Override
diff --git a/src/test/java/com/android/tools/r8/MarkersTest.java b/src/test/java/com/android/tools/r8/MarkersTest.java
index f2e539b..650cf39 100644
--- a/src/test/java/com/android/tools/r8/MarkersTest.java
+++ b/src/test/java/com/android/tools/r8/MarkersTest.java
@@ -7,10 +7,15 @@
import static com.android.tools.r8.MarkerMatcher.markerCompilationMode;
import static com.android.tools.r8.MarkerMatcher.markerDesugaredLibraryIdentifier;
import static com.android.tools.r8.MarkerMatcher.markerHasChecksums;
+import static com.android.tools.r8.MarkerMatcher.markerHasDesugaredLibraryIdentifier;
+import static com.android.tools.r8.MarkerMatcher.markerHasMinApi;
+import static com.android.tools.r8.MarkerMatcher.markerIsDesugared;
import static com.android.tools.r8.MarkerMatcher.markerMinApi;
import static com.android.tools.r8.MarkerMatcher.markerR8Mode;
import static com.android.tools.r8.MarkerMatcher.markerTool;
import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
@@ -95,4 +100,157 @@
assertMarkersMatch(markers, ImmutableList.of(l8Matcher, r8Matcher));
}
}
+
+ @Test
+ public void testD8MarkerInDex() throws Throwable {
+ AndroidApiLevel apiLevel = AndroidApiLevel.L;
+ Path output = temp.newFolder().toPath().resolve("output.zip");
+ D8Command.Builder builder =
+ D8Command.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+ .setMode(compilationMode)
+ .setMinApiLevel(apiLevel.getLevel())
+ .setOutput(output, OutputMode.DexIndexed);
+ D8.run(builder.build());
+ Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+ Matcher<Marker> matcher =
+ allOf(
+ markerTool(Tool.D8),
+ markerCompilationMode(compilationMode),
+ markerIsDesugared(),
+ markerMinApi(apiLevel),
+ not(markerHasDesugaredLibraryIdentifier()));
+ assertMarkersMatch(markers, matcher);
+ }
+
+ @Test
+ public void testD8MarkerInCf() throws Throwable {
+ // Shrinking of desugared library is not affecting this test.
+ assumeTrue(shrinkDesugaredLibrary);
+
+ AndroidApiLevel apiLevel = AndroidApiLevel.L;
+ Path output = temp.newFolder().toPath().resolve("output.zip");
+ D8Command.Builder builder =
+ D8Command.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+ .setMode(compilationMode)
+ .setMinApiLevel(apiLevel.getLevel())
+ .setOutput(output, OutputMode.ClassFile);
+ D8.run(builder.build());
+ Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+ Matcher<Marker> matcher =
+ allOf(
+ markerTool(Tool.D8),
+ markerCompilationMode(compilationMode),
+ markerIsDesugared(),
+ markerMinApi(apiLevel),
+ not(markerHasDesugaredLibraryIdentifier()));
+ assertMarkersMatch(markers, matcher);
+ }
+
+ @Test
+ public void testR8MarkerInDex() throws Throwable {
+ // Shrinking of desugared library is not affecting this test.
+ assumeTrue(shrinkDesugaredLibrary);
+
+ AndroidApiLevel apiLevel = AndroidApiLevel.L;
+ Path output = temp.newFolder().toPath().resolve("output.zip");
+ R8Command.Builder builder =
+ R8Command.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+ .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
+ .setMode(compilationMode)
+ .setMinApiLevel(apiLevel.getLevel())
+ .setOutput(output, OutputMode.DexIndexed);
+ R8.run(builder.build());
+ Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+ Matcher<Marker> matcher =
+ allOf(
+ markerTool(Tool.R8),
+ markerCompilationMode(compilationMode),
+ markerIsDesugared(),
+ markerMinApi(apiLevel),
+ not(markerHasDesugaredLibraryIdentifier()));
+ assertMarkersMatch(markers, matcher);
+ }
+
+ @Test
+ public void testR8MarkerInCf() throws Throwable {
+ // Shrinking of desugared library is not affecting this test.
+ assumeTrue(shrinkDesugaredLibrary);
+
+ Path output = temp.newFolder().toPath().resolve("output.zip");
+ R8Command.Builder builder =
+ R8Command.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+ .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
+ .setMode(compilationMode)
+ .setOutput(output, OutputMode.ClassFile);
+ R8.run(builder.build());
+ Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+ Matcher<Marker> matcher =
+ allOf(
+ markerTool(Tool.R8),
+ markerCompilationMode(compilationMode),
+ not(markerIsDesugared()),
+ not(markerHasMinApi()),
+ not(markerHasDesugaredLibraryIdentifier()));
+ assertMarkersMatch(markers, matcher);
+ }
+
+ @Test
+ public void testR8MarkerInCfAfterD8CfDesugar() throws Throwable {
+ // Shrinking of desugared library is not affecting this test.
+ assumeTrue(shrinkDesugaredLibrary);
+
+ AndroidApiLevel apiLevel = AndroidApiLevel.L;
+ Path d8DesugaredOutput = temp.newFolder().toPath().resolve("output.zip");
+ D8.run(
+ D8Command.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+ .setMode(compilationMode)
+ .setMinApiLevel(apiLevel.getLevel())
+ .setOutput(d8DesugaredOutput, OutputMode.ClassFile)
+ .build());
+ assertMarkersMatch(
+ ExtractMarker.extractMarkerFromDexFile(d8DesugaredOutput),
+ allOf(
+ markerTool(Tool.D8),
+ markerCompilationMode(compilationMode),
+ markerIsDesugared(),
+ markerMinApi(apiLevel),
+ not(markerHasDesugaredLibraryIdentifier())));
+
+ // Running R8 on desugared input will clear that information and leave no markers with
+ // that information.
+ Path output = temp.newFolder().toPath().resolve("output.zip");
+ R8.run(
+ R8Command.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+ .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
+ .setMode(compilationMode)
+ .setOutput(output, OutputMode.ClassFile)
+ .build());
+ assertMarkersMatch(
+ ExtractMarker.extractMarkerFromDexFile(output),
+ allOf(
+ markerTool(Tool.R8),
+ markerCompilationMode(compilationMode),
+ not(markerIsDesugared()),
+ not(markerHasMinApi()),
+ not(markerHasDesugaredLibraryIdentifier())));
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
index 5cfce33..3c2bbfc 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
@@ -14,7 +14,7 @@
import java.io.IOException;
import java.util.concurrent.ExecutionException;
-public class ProguardTestRunResult extends TestRunResult<ProguardTestRunResult> {
+public class ProguardTestRunResult extends SingleTestRunResult<ProguardTestRunResult> {
private final String proguardMap;
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 4fc59c6..935de02 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -3,11 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.dexsplitter.SplitterTestBase.simpleSplitProvider;
import static org.hamcrest.CoreMatchers.containsString;
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
@@ -64,6 +67,7 @@
private List<String> keepRules = new ArrayList<>();
private List<Path> mainDexRulesFiles = new ArrayList<>();
private List<String> applyMappingMaps = new ArrayList<>();
+ private final List<Path> features = new ArrayList<>();
@Override
R8TestCompileResult internalCompile(
@@ -137,7 +141,8 @@
box.syntheticProguardRules,
proguardMapBuilder.toString(),
graphConsumer,
- builder.getMinApiLevel());
+ builder.getMinApiLevel(),
+ features);
switch (allowedDiagnosticMessages) {
case ALL:
compileResult.assertDiagnosticThatMatches(new IsAnything<>());
@@ -569,8 +574,22 @@
return self();
}
+ public T addFeatureSplitRuntime() {
+ addProgramClasses(SplitRunner.class, RunInterface.class);
+ addKeepClassAndMembersRules(SplitRunner.class, RunInterface.class);
+ return self();
+ }
+
public T addFeatureSplit(Function<FeatureSplit.Builder, FeatureSplit> featureSplitBuilder) {
builder.addFeatureSplit(featureSplitBuilder);
return self();
}
+
+ public T addFeatureSplit(Class<?>... classes) throws IOException {
+ Path path = getState().getNewTempFile("feature.zip");
+ builder.addFeatureSplit(
+ builder -> simpleSplitProvider(builder, path, getState().getTempFolder(), classes));
+ features.add(path);
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 5dac954..3fa1091 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -3,12 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import java.io.IOException;
@@ -23,6 +30,7 @@
private final String proguardMap;
private final CollectingGraphConsumer graphConsumer;
private final int minApiLevel;
+ private final List<Path> features;
R8TestCompileResult(
TestState state,
@@ -32,13 +40,15 @@
List<ProguardConfigurationRule> syntheticProguardRules,
String proguardMap,
CollectingGraphConsumer graphConsumer,
- int minApiLevel) {
+ int minApiLevel,
+ List<Path> features) {
super(state, app, outputMode);
this.proguardConfiguration = proguardConfiguration;
this.syntheticProguardRules = syntheticProguardRules;
this.proguardMap = proguardMap;
this.graphConsumer = graphConsumer;
this.minApiLevel = minApiLevel;
+ this.features = features;
}
@Override
@@ -51,11 +61,16 @@
return state.getDiagnosticsMessages();
}
+ @Override
public R8TestCompileResult inspectDiagnosticMessages(Consumer<TestDiagnosticMessages> consumer) {
consumer.accept(state.getDiagnosticsMessages());
return self();
}
+ public Path getFeature(int index) {
+ return features.get(index);
+ }
+
@Override
public String getStdout() {
return state.getStdout();
@@ -71,6 +86,22 @@
return new CodeInspector(app, proguardMap);
}
+ private CodeInspector featureInspector(Path feature) throws IOException {
+ return new CodeInspector(
+ AndroidApp.builder().addProgramFile(feature).setProguardMapOutputData(proguardMap).build());
+ }
+
+ @SafeVarargs
+ public final <E extends Throwable> R8TestCompileResult inspect(
+ ThrowingConsumer<CodeInspector, E>... consumers) throws IOException, E {
+ assertEquals(1 + features.size(), consumers.length);
+ consumers[0].accept(inspector());
+ for (int i = 0; i < features.size(); i++) {
+ consumers[i + 1].accept(featureInspector(features.get(i)));
+ }
+ return self();
+ }
+
public GraphInspector graphInspector() throws IOException {
assert graphConsumer != null;
return new GraphInspector(graphConsumer, inspector());
@@ -101,6 +132,37 @@
return new R8TestRunResult(app, runtime, result, proguardMap, this::graphInspector);
}
+ public R8TestRunResult runFeature(TestRuntime runtime, Class<?> mainFeatureClass)
+ throws IOException {
+ return runFeature(runtime, mainFeatureClass, features.get(0));
+ }
+
+ public R8TestRunResult runFeature(
+ TestRuntime runtime, Class<?> mainFeatureClass, Path feature, Path... featureDependencies)
+ throws IOException {
+ assert getBackend() == runtime.getBackend();
+ ClassSubject mainClassSubject = inspector().clazz(SplitRunner.class);
+ assertThat("Did you forget a keep rule for the main method?", mainClassSubject, isPresent());
+ assertThat(
+ "Did you forget a keep rule for the main method?",
+ mainClassSubject.mainMethod(),
+ isPresent());
+ ClassSubject mainFeatureClassSubject = featureInspector(feature).clazz(mainFeatureClass);
+ assertThat(
+ "Did you forget a keep rule for the run method?", mainFeatureClassSubject, isPresent());
+ assertThat(
+ "Did you forget a keep rule for the run method?",
+ mainFeatureClassSubject.uniqueMethodWithName("run"),
+ isPresent());
+ String[] args = new String[2 + featureDependencies.length];
+ args[0] = mainFeatureClassSubject.getFinalName();
+ args[1] = feature.toString();
+ for (int i = 2; i < args.length; i++) {
+ args[i] = featureDependencies[i - 2].toString();
+ }
+ return runArt(runtime, additionalRunClassPath, mainClassSubject.getFinalName(), args);
+ }
+
public String getProguardMap() {
return proguardMap;
}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 9fe91d1..0afdccf 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -17,7 +17,7 @@
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
-public class R8TestRunResult extends TestRunResult<R8TestRunResult> {
+public class R8TestRunResult extends SingleTestRunResult<R8TestRunResult> {
public interface GraphInspectorSupplier {
GraphInspector get() throws IOException, ExecutionException;
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
new file mode 100644
index 0000000..4d72d22
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -0,0 +1,171 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.concurrent.ExecutionException;
+import org.hamcrest.Matcher;
+
+public abstract class SingleTestRunResult<RR extends SingleTestRunResult<RR>>
+ extends TestRunResult<RR> {
+ protected final AndroidApp app;
+ private final TestRuntime runtime;
+ private final ProcessResult result;
+
+ public SingleTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
+ this.app = app;
+ this.runtime = runtime;
+ this.result = result;
+ }
+
+ public AndroidApp app() {
+ return app;
+ }
+
+ public ProcessResult getResult() {
+ return result;
+ }
+
+ public String getStdOut() {
+ return result.stdout;
+ }
+
+ public String getStdErr() {
+ return result.stderr;
+ }
+
+ public StackTrace getStackTrace() {
+ if (runtime.isDex()) {
+ return StackTrace.extractFromArt(getStdErr(), runtime.asDex().getVm());
+ } else {
+ return StackTrace.extractFromJvm(getStdErr());
+ }
+ }
+
+ public int getExitCode() {
+ return result.exitCode;
+ }
+
+ @Override
+ public RR assertSuccess() {
+ assertEquals(errorMessage("Expected run to succeed."), 0, result.exitCode);
+ return self();
+ }
+
+ @Override
+ public RR assertStdoutMatches(Matcher<String> matcher) {
+ assertThat(errorMessage("Run stdout incorrect.", matcher.toString()), result.stdout, matcher);
+ return self();
+ }
+
+ @Override
+ public RR assertFailure() {
+ assertNotEquals(errorMessage("Expected run to fail."), 0, result.exitCode);
+ return self();
+ }
+
+ @Override
+ public RR assertStderrMatches(Matcher<String> matcher) {
+ assertThat(errorMessage("Run stderr incorrect.", matcher.toString()), result.stderr, matcher);
+ return self();
+ }
+
+ public CodeInspector inspector() throws IOException, ExecutionException {
+ // Inspection post run implies success. If inspection of an invalid program is needed it should
+ // be done on the compilation result or on the input.
+ assertSuccess();
+ assertNotNull(app);
+ return new CodeInspector(app);
+ }
+
+ @Override
+ public <E extends Throwable> RR inspect(ThrowingConsumer<CodeInspector, E> consumer)
+ throws IOException, ExecutionException, E {
+ CodeInspector inspector = inspector();
+ consumer.accept(inspector);
+ return self();
+ }
+
+ public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer)
+ throws IOException, ExecutionException, E {
+ assertFailure();
+ assertNotNull(app);
+ CodeInspector inspector = new CodeInspector(app);
+ consumer.accept(inspector);
+ return self();
+ }
+
+ public <E extends Throwable> RR inspectStackTrace(ThrowingConsumer<StackTrace, E> consumer)
+ throws E {
+ consumer.accept(getStackTrace());
+ return self();
+ }
+
+ public RR disassemble(PrintStream ps) throws IOException, ExecutionException {
+ ToolHelper.disassemble(app, ps);
+ return self();
+ }
+
+ public RR disassemble() throws IOException, ExecutionException {
+ return disassemble(System.out);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ appendInfo(builder);
+ return builder.toString();
+ }
+
+ String errorMessage(String message) {
+ return errorMessage(message, null);
+ }
+
+ String errorMessage(String message, String expected) {
+ StringBuilder builder = new StringBuilder(message).append('\n');
+ if (expected != null) {
+ if (expected.contains(System.lineSeparator())) {
+ builder.append("EXPECTED:").append(System.lineSeparator()).append(expected);
+ } else {
+ builder.append("EXPECTED: ").append(expected);
+ }
+ builder.append(System.lineSeparator());
+ }
+ appendInfo(builder);
+ return builder.toString();
+ }
+
+ private void appendInfo(StringBuilder builder) {
+ builder.append("APPLICATION: ");
+ appendApplication(builder);
+ builder.append('\n');
+ appendProcessResult(builder);
+ }
+
+ private void appendApplication(StringBuilder builder) {
+ builder.append(app == null ? "<default>" : app.toString());
+ }
+
+ private void appendProcessResult(StringBuilder builder) {
+ builder.append("COMMAND: ").append(result.command).append('\n').append(result);
+ }
+
+ public RR writeProcessResult(PrintStream ps) {
+ StringBuilder sb = new StringBuilder();
+ appendProcessResult(sb);
+ ps.println(sb.toString());
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 545ab13..35e1eec 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -65,6 +65,7 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.PreloadedClassFileProvider;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
@@ -191,7 +192,7 @@
return testForJvm(temp);
}
- public TestBuilder<? extends TestRunResult<?>, ?> testForRuntime(
+ public TestBuilder<? extends SingleTestRunResult<?>, ?> testForRuntime(
TestRuntime runtime, Consumer<D8TestBuilder> d8TestBuilderConsumer) {
if (runtime.isCf()) {
return testForJvm();
@@ -203,15 +204,40 @@
}
}
- public TestBuilder<? extends TestRunResult<?>, ?> testForRuntime(
+ public TestBuilder<? extends SingleTestRunResult<?>, ?> testForRuntime(
TestRuntime runtime, AndroidApiLevel apiLevel) {
return testForRuntime(runtime, d8TestBuilder -> d8TestBuilder.setMinApi(apiLevel));
}
- public TestBuilder<? extends TestRunResult<?>, ?> testForRuntime(TestParameters parameters) {
+ public TestBuilder<? extends SingleTestRunResult<?>, ?> testForRuntime(
+ TestParameters parameters) {
return testForRuntime(parameters.getRuntime(), parameters.getApiLevel());
}
+ public TestBuilder<? extends TestRunResult<?>, ?> testForDesugaring(TestParameters parameters) {
+ return testForDesugaring(parameters.getRuntime().getBackend(), parameters.getApiLevel());
+ }
+
+ private TestBuilder<? extends TestRunResult<?>, ?> testForDesugaring(
+ Backend backend, AndroidApiLevel apiLevel) {
+ assert apiLevel != null : "No API level. Add .withAllApiLevelsAlsoForCf() to test parameters?";
+ TestState state = new TestState(temp);
+ List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> builders;
+ if (backend == Backend.CF) {
+ builders =
+ ImmutableList.of(
+ new Pair<>("JAVAC", JvmTestBuilder.create(state)),
+ new Pair<>("D8/CF", D8TestBuilder.create(state, Backend.CF).setMinApi(apiLevel)));
+ } else {
+ assert backend == Backend.DEX;
+ builders =
+ ImmutableList.of(
+ new Pair<>("D8/DEX", D8TestBuilder.create(state, Backend.DEX).setMinApi(apiLevel)),
+ new Pair<>("D8/DEX o D8/CF", IntermediateCfD8TestBuilder.create(state, apiLevel)));
+ }
+ return TestBuilderCollection.create(state, builders);
+ }
+
public ProguardTestBuilder testForProguard() {
return testForProguard(temp);
}
@@ -588,13 +614,15 @@
}
protected static AppView<AppInfo> computeAppView(AndroidApp app) throws Exception {
- AppInfo appInfo = new AppInfo(readApplicationForDexOutput(app, new InternalOptions()));
+ AppInfo appInfo =
+ AppInfo.createInitialAppInfo(readApplicationForDexOutput(app, new InternalOptions()));
return AppView.createForD8(appInfo);
}
protected static AppInfoWithClassHierarchy computeAppInfoWithClassHierarchy(AndroidApp app)
throws Exception {
- return new AppInfoWithClassHierarchy(readApplicationForDexOutput(app, new InternalOptions()));
+ return AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+ readApplicationForDexOutput(app, new InternalOptions()));
}
protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithSubtyping(AndroidApp app)
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index d56f4d9..9a874c2 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -58,8 +59,10 @@
}
@Deprecated
- public abstract RR run(String mainClass)
- throws CompilationFailedException, ExecutionException, IOException;
+ public RR run(String mainClass)
+ throws CompilationFailedException, ExecutionException, IOException {
+ throw new Unimplemented("Deprecated");
+ }
public abstract RR run(TestRuntime runtime, String mainClass, String... args)
throws CompilationFailedException, ExecutionException, IOException;
diff --git a/src/test/java/com/android/tools/r8/TestBuilderCollection.java b/src/test/java/com/android/tools/r8/TestBuilderCollection.java
new file mode 100644
index 0000000..a537aea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestBuilderCollection.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.Pair;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+
+/** Abstraction to allow setup and execution of multiple test builders. */
+public class TestBuilderCollection
+ extends TestBuilder<TestRunResultCollection, TestBuilderCollection> {
+
+ public static TestBuilderCollection create(
+ TestState state,
+ List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> testBuilders) {
+ return new TestBuilderCollection(state, testBuilders);
+ }
+
+ private final List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> builders;
+
+ private TestBuilderCollection(
+ TestState state, List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> builders) {
+ super(state);
+ assert !builders.isEmpty();
+ this.builders = builders;
+ }
+
+ @Override
+ TestBuilderCollection self() {
+ return this;
+ }
+
+ @Override
+ public TestRunResultCollection run(TestRuntime runtime, String mainClass, String... args)
+ throws CompilationFailedException, ExecutionException, IOException {
+ List<Pair<String, TestRunResult<?>>> runs = new ArrayList<>(builders.size());
+ for (Pair<String, TestBuilder<? extends TestRunResult<?>, ?>> builder : builders) {
+ runs.add(new Pair<>(builder.getFirst(), builder.getSecond().run(runtime, mainClass, args)));
+ }
+ return TestRunResultCollection.create(runs);
+ }
+
+ private TestBuilderCollection forEach(Consumer<TestBuilder<? extends TestRunResult<?>, ?>> fn) {
+ builders.forEach(b -> fn.accept(b.getSecond()));
+ return self();
+ }
+
+ @Override
+ public DebugTestConfig debugConfig() {
+ throw new Unimplemented("Unsupported debug config as of now...");
+ }
+
+ @Override
+ public TestBuilderCollection addProgramFiles(Collection<Path> files) {
+ return forEach(b -> b.addProgramFiles(files));
+ }
+
+ @Override
+ public TestBuilderCollection addProgramClassFileData(Collection<byte[]> classes) {
+ return forEach(b -> b.addProgramClassFileData(classes));
+ }
+
+ @Override
+ public TestBuilderCollection addProgramDexFileData(Collection<byte[]> data) {
+ return forEach(b -> b.addProgramDexFileData(data));
+ }
+
+ @Override
+ public TestBuilderCollection addLibraryFiles(Collection<Path> files) {
+ return forEach(b -> b.addLibraryFiles(files));
+ }
+
+ @Override
+ public TestBuilderCollection addLibraryClasses(Collection<Class<?>> classes) {
+ return forEach(b -> b.addLibraryClasses(classes));
+ }
+
+ @Override
+ public TestBuilderCollection addClasspathClasses(Collection<Class<?>> classes) {
+ return forEach(b -> b.addClasspathClasses(classes));
+ }
+
+ @Override
+ public TestBuilderCollection addClasspathFiles(Collection<Path> files) {
+ return forEach(b -> b.addClasspathFiles(files));
+ }
+
+ @Override
+ public TestBuilderCollection addRunClasspathFiles(Collection<Path> files) {
+ return forEach(b -> b.addRunClasspathFiles(files));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 0816496..3294828 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -419,7 +419,7 @@
return createRunResult(runtime, result);
}
- private RR runArt(
+ RR runArt(
TestRuntime runtime, List<Path> additionalClassPath, String mainClass, String... arguments)
throws IOException {
DexVm vm = runtime.asDex().getVm();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 9d6b1c8..da1bc91 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -229,20 +229,15 @@
}
public T setMinApi(AndroidApiLevel minApiLevel) {
- if (backend == Backend.DEX) {
- return setMinApi(minApiLevel.getLevel());
- }
- return self();
+ return setMinApi(minApiLevel.getLevel());
}
public T setMinApi(int minApiLevel) {
assert builder.getMinApiLevel() > 0 || this.defaultMinApiLevel != null
: "Tests must use this method to set min API level, and not"
+ " BaseCompilerCommand.Builder.setMinApiLevel()";
- if (backend == Backend.DEX) {
- this.defaultMinApiLevel = null;
- builder.setMinApiLevel(minApiLevel);
- }
+ this.defaultMinApiLevel = null;
+ builder.setMinApiLevel(minApiLevel);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 16fd725..85214af 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -1,22 +1,15 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.hamcrest.CoreMatchers.is;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
-import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -24,93 +17,45 @@
import java.util.function.Function;
import org.hamcrest.Matcher;
+/**
+ * Abstract base for checking the result of executing (test) program(s).
+ *
+ * <p>All methods defined on this base must generalize to allow checking of multiple run results
+ * simultaneously. For methods on single runs see the SingleTestRunResult subclass.
+ */
public abstract class TestRunResult<RR extends TestRunResult<RR>> {
- protected final AndroidApp app;
- private final TestRuntime runtime;
- private final ProcessResult result;
-
- public TestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
- this.app = app;
- this.runtime = runtime;
- this.result = result;
- }
abstract RR self();
- public <S> S map(Function<RR, S> fn) {
- return fn.apply(self());
- }
+ public abstract RR assertSuccess();
+
+ public abstract RR assertStdoutMatches(Matcher<String> matcher);
+
+ public abstract RR assertFailure();
+
+ public abstract RR assertStderrMatches(Matcher<String> matcher);
+
+ public abstract <E extends Throwable> RR inspect(ThrowingConsumer<CodeInspector, E> consumer)
+ throws IOException, ExecutionException, E;
+
+ public abstract RR disassemble() throws IOException, ExecutionException;
public RR apply(Consumer<RR> fn) {
fn.accept(self());
return self();
}
- public AndroidApp app() {
- return app;
+ public <S> S map(Function<RR, S> fn) {
+ return fn.apply(self());
}
- public String getStdOut() {
- return result.stdout;
- }
-
- public String getStdErr() {
- return result.stderr;
- }
-
- public StackTrace getStackTrace() {
- if (runtime.isDex()) {
- return StackTrace.extractFromArt(getStdErr(), runtime.asDex().getVm());
- } else {
- return StackTrace.extractFromJvm(getStdErr());
- }
- }
-
- public int getExitCode() {
- return result.exitCode;
- }
-
- public RR assertSuccess() {
- assertEquals(errorMessage("Expected run to succeed."), 0, result.exitCode);
- return self();
- }
-
- public RR assertFailure() {
- assertNotEquals(errorMessage("Expected run to fail."), 0, result.exitCode);
- return self();
- }
-
- public RR assertFailureWithOutput(String expected) {
- assertFailure();
- assertEquals(errorMessage("Run stdout incorrect.", expected), expected, result.stdout);
- return self();
- }
-
- public RR assertFailureWithErrorThatMatches(Matcher<String> matcher) {
- assertFailure();
- assertThat(
- errorMessage("Run stderr incorrect.", matcher.toString()), result.stderr, matcher);
- return self();
- }
-
- public RR assertFailureWithErrorThatThrows(Class<? extends Throwable> expectedError) {
- assertFailure();
- assertThat(
- errorMessage("Run stderr incorrect.", expectedError.getName()),
- result.stderr,
- containsString(expectedError.getName()));
- return self();
- }
-
- public RR assertStderrMatches(Matcher<String> matcher) {
- assertThat(errorMessage("Run stderr incorrect.", matcher.toString()), result.stderr, matcher);
- return self();
+ public RR assertSuccessWithOutputThatMatches(Matcher<String> matcher) {
+ assertStdoutMatches(matcher);
+ return assertSuccess();
}
public RR assertSuccessWithOutput(String expected) {
- assertSuccess();
- assertEquals(errorMessage("Run stdout incorrect.", expected), expected, result.stdout);
- return self();
+ return assertSuccessWithOutputThatMatches(is(expected));
}
public RR assertSuccessWithEmptyOutput() {
@@ -125,95 +70,17 @@
return assertSuccessWithOutput(StringUtils.lines(expected));
}
- public RR assertSuccessWithOutputThatMatches(Matcher<String> matcher) {
- assertSuccess();
- assertThat(errorMessage("Run stdout incorrect.", matcher.toString()), result.stdout, matcher);
- return self();
+ public RR assertFailureWithErrorThatMatches(Matcher<String> matcher) {
+ assertStderrMatches(matcher);
+ return assertFailure();
}
- public CodeInspector inspector() throws IOException, ExecutionException {
- // Inspection post run implies success. If inspection of an invalid program is needed it should
- // be done on the compilation result or on the input.
- assertSuccess();
- assertNotNull(app);
- return new CodeInspector(app);
+ public RR assertFailureWithOutput(String expected) {
+ assertStdoutMatches(is(expected));
+ return assertFailure();
}
- public <E extends Throwable> RR inspect(ThrowingConsumer<CodeInspector, E> consumer)
- throws IOException, ExecutionException, E {
- CodeInspector inspector = inspector();
- consumer.accept(inspector);
- return self();
- }
-
- public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer)
- throws IOException, ExecutionException, E {
- assertFailure();
- assertNotNull(app);
- CodeInspector inspector = new CodeInspector(app);
- consumer.accept(inspector);
- return self();
- }
-
- public <E extends Throwable> RR inspectStackTrace(ThrowingConsumer<StackTrace, E> consumer)
- throws E {
- consumer.accept(getStackTrace());
- return self();
- }
-
- public RR disassemble(PrintStream ps) throws IOException, ExecutionException {
- ToolHelper.disassemble(app, ps);
- return self();
- }
-
- public RR disassemble() throws IOException, ExecutionException {
- return disassemble(System.out);
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- appendInfo(builder);
- return builder.toString();
- }
-
- String errorMessage(String message) {
- return errorMessage(message, null);
- }
-
- String errorMessage(String message, String expected) {
- StringBuilder builder = new StringBuilder(message).append('\n');
- if (expected != null) {
- if (expected.contains(System.lineSeparator())) {
- builder.append("EXPECTED:").append(System.lineSeparator()).append(expected);
- } else {
- builder.append("EXPECTED: ").append(expected);
- }
- builder.append(System.lineSeparator());
- }
- appendInfo(builder);
- return builder.toString();
- }
-
- private void appendInfo(StringBuilder builder) {
- builder.append("APPLICATION: ");
- appendApplication(builder);
- builder.append('\n');
- appendProcessResult(builder);
- }
-
- private void appendApplication(StringBuilder builder) {
- builder.append(app == null ? "<default>" : app.toString());
- }
-
- private void appendProcessResult(StringBuilder builder) {
- builder.append("COMMAND: ").append(result.command).append('\n').append(result);
- }
-
- public RR writeProcessResult(PrintStream ps) {
- StringBuilder sb = new StringBuilder();
- appendProcessResult(sb);
- ps.println(sb.toString());
- return self();
+ public RR assertFailureWithErrorThatThrows(Class<? extends Throwable> expectedError) {
+ return assertFailureWithErrorThatMatches(containsString(expectedError.getName()));
}
}
diff --git a/src/test/java/com/android/tools/r8/TestRunResultCollection.java b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
new file mode 100644
index 0000000..af4256a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import org.hamcrest.Matcher;
+
+/** Checking container for checking the same properties of multiple run results. */
+public class TestRunResultCollection extends TestRunResult<TestRunResultCollection> {
+
+ public static TestRunResultCollection create(List<Pair<String, TestRunResult<?>>> runs) {
+ assert !runs.isEmpty();
+ return new TestRunResultCollection(runs);
+ }
+
+ private final List<Pair<String, TestRunResult<?>>> runs;
+
+ public TestRunResultCollection(List<Pair<String, TestRunResult<?>>> runs) {
+ this.runs = runs;
+ }
+
+ @Override
+ TestRunResultCollection self() {
+ return this;
+ }
+
+ private TestRunResultCollection forEach(Consumer<TestRunResult<?>> fn) {
+ runs.forEach(r -> fn.accept(r.getSecond()));
+ return self();
+ }
+
+ @Override
+ public TestRunResultCollection assertSuccess() {
+ return forEach(TestRunResult::assertSuccess);
+ }
+
+ @Override
+ public TestRunResultCollection assertFailure() {
+ return forEach(TestRunResult::assertFailure);
+ }
+
+ @Override
+ public TestRunResultCollection assertStdoutMatches(Matcher<String> matcher) {
+ return forEach(r -> r.assertStdoutMatches(matcher));
+ }
+
+ @Override
+ public TestRunResultCollection assertStderrMatches(Matcher<String> matcher) {
+ return forEach(r -> r.assertStderrMatches(matcher));
+ }
+
+ @Override
+ public <E extends Throwable> TestRunResultCollection inspect(
+ ThrowingConsumer<CodeInspector, E> consumer) throws IOException, ExecutionException, E {
+ for (Pair<String, TestRunResult<?>> run : runs) {
+ run.getSecond().inspect(consumer);
+ }
+ return self();
+ }
+
+ @Override
+ public TestRunResultCollection disassemble() throws IOException, ExecutionException {
+ for (Pair<String, TestRunResult<?>> run : runs) {
+ String name = run.getFirst();
+ System.out.println(name + " " + Strings.repeat("=", 80 - name.length() - 1));
+ run.getSecond().disassemble();
+ }
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index f53e8b3..3069351 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -5,9 +5,11 @@
package com.android.tools.r8;
import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
@@ -30,6 +32,22 @@
super(state, builder, backend);
}
+ @Override
+ public T setMinApi(AndroidApiLevel minApiLevel) {
+ if (backend == Backend.DEX) {
+ return super.setMinApi(minApiLevel.getLevel());
+ }
+ return self();
+ }
+
+ @Override
+ public T setMinApi(int minApiLevel) {
+ if (backend == Backend.DEX) {
+ return super.setMinApi(minApiLevel);
+ }
+ return self();
+ }
+
public T treeShaking(boolean enable) {
enableTreeShaking = enable;
return self();
@@ -172,6 +190,31 @@
return self();
}
+ public T addKeepFeatureMainRule(Class<?> mainClass) {
+ return addKeepFeatureMainRule(mainClass.getTypeName());
+ }
+
+ public T addKeepFeatureMainRules(Class<?>... mainClasses) {
+ for (Class<?> mainClass : mainClasses) {
+ this.addKeepFeatureMainRule(mainClass);
+ }
+ return self();
+ }
+
+ public T addKeepFeatureMainRule(String mainClass) {
+ return addKeepRules(
+ "-keep public class " + mainClass,
+ " implements " + RunInterface.class.getTypeName() + " {",
+ " public void <init>();",
+ " public void run();",
+ "}");
+ }
+
+ public T addKeepFeatureMainRules(List<String> mainClasses) {
+ mainClasses.forEach(this::addKeepFeatureMainRule);
+ return self();
+ }
+
public T addKeepMethodRules(Class<?> clazz, String... methodSignatures) {
StringBuilder sb = new StringBuilder();
sb.append("-keep class " + clazz.getTypeName() + " {\n");
diff --git a/src/test/java/com/android/tools/r8/TestState.java b/src/test/java/com/android/tools/r8/TestState.java
index 78388bc..6ba75d7 100644
--- a/src/test/java/com/android/tools/r8/TestState.java
+++ b/src/test/java/com/android/tools/r8/TestState.java
@@ -19,10 +19,18 @@
this.temp = temp;
}
+ public TemporaryFolder getTempFolder() {
+ return temp;
+ }
+
public Path getNewTempFolder() throws IOException {
return temp.newFolder().toPath();
}
+ public Path getNewTempFile(String name) throws IOException {
+ return getNewTempFolder().resolve(name);
+ }
+
DiagnosticsHandler getDiagnosticsHandler() {
return messages;
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
index 7007821..402d279 100644
--- a/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.horizontalstatic;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
diff --git a/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerSynchronizedMethodTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
index a811257..8ddfef4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.horizontalstatic;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/InliningAfterStaticClassMergerTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontalstatic/InliningAfterStaticClassMergerTest.java
index e2ee52f..75e26f1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/InliningAfterStaticClassMergerTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.horizontalstatic;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
similarity index 95%
rename from src/test/java/com/android/tools/r8/classmerging/StaticClassMergerInterfaceTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
index b0557d3..3b0b12f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.horizontalstatic;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -58,7 +58,6 @@
assertThat(inspector.clazz(A.class.getTypeName() + "$-CC"), isPresent());
}
-
// By the time B is processed, there is no merge representative, so it should be present.
assertThat(inspector.clazz(B.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/classmerging/StaticClassMergerTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
index 3fa47cb..7d6aa4a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.horizontalstatic;
import static org.junit.Assert.assertEquals;
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/classmerging/StaticClassMergerVisibilityTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
index 46ccfd2..9d545e5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerVisibilityTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.horizontalstatic;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/classmerging/B141942381.java b/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/classmerging/B141942381.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
index 36f320b..97f84ef 100644
--- a/src/test/java/com/android/tools/r8/classmerging/B141942381.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
@@ -1,7 +1,7 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/classmerging/ForceInlineConstructorWithMultiPackageAccessesTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithMultiPackageAccessesTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/classmerging/ForceInlineConstructorWithMultiPackageAccessesTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithMultiPackageAccessesTest.java
index 06344db..a96f8d3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ForceInlineConstructorWithMultiPackageAccessesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithMultiPackageAccessesTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
@@ -11,7 +11,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.classmerging.testclasses.ForceInlineConstructorWithMultiPackageAccessesTestClasses;
+import com.android.tools.r8.classmerging.vertical.testclasses.ForceInlineConstructorWithMultiPackageAccessesTestClasses;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInliningWithStaticInterfaceMethodTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/ForceInliningWithStaticInterfaceMethodTest.java
index 7c86d1a..ff5d1a8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInliningWithStaticInterfaceMethodTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
diff --git a/src/test/java/com/android/tools/r8/classmerging/IncorrectRewritingOfInvokeSuperTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/classmerging/IncorrectRewritingOfInvokeSuperTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
index 25ddfcd..505aa07 100644
--- a/src/test/java/com/android/tools/r8/classmerging/IncorrectRewritingOfInvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
diff --git a/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
index ba16ddc..b9e0066 100644
--- a/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
diff --git a/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NestedDefaultInterfaceMethodsTestDump.java
similarity index 95%
rename from src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/NestedDefaultInterfaceMethodsTestDump.java
index e55802d..e2bd923 100644
--- a/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NestedDefaultInterfaceMethodsTestDump.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/StaticInitializerTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/StaticInitializerTest.java
index 72c9e57..aa6a4f7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/StaticInitializerTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
index b95a89a..6e93ac6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
similarity index 95%
rename from src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
index 5f6e255..f9c51ce 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
index cab2a18..9de435b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
@@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedBlockTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedBlockTest.java
index 2828ec4..327be46 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedBlockTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedBlockWithArraysTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedBlockWithArraysTest.java
index b6039db..6c3f55d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedBlockWithArraysTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedMethodTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedMethodTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedMethodTest.java
index e77ee05..deff6a8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedMethodTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
similarity index 99%
rename from src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 26e5a16..2411076 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -1,7 +1,7 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging;
+package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
import static com.android.tools.r8.smali.SmaliBuilder.buildCode;
diff --git a/src/test/java/com/android/tools/r8/classmerging/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java
similarity index 85%
rename from src/test/java/com/android/tools/r8/classmerging/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java
index 80ab1d6..2b3967a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java
@@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.classmerging.testclasses;
+package com.android.tools.r8.classmerging.vertical.testclasses;
import com.android.tools.r8.NeverMerge;
diff --git a/src/test/java/com/android/tools/r8/desugar/ConcurrentHashMapKeySetTest.java b/src/test/java/com/android/tools/r8/desugar/ConcurrentHashMapKeySetTest.java
new file mode 100644
index 0000000..dda9269
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/ConcurrentHashMapKeySetTest.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.concurrent.ConcurrentHashMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConcurrentHashMapKeySetTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public ConcurrentHashMapKeySetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ TestRunResult<?> result =
+ testForDesugaring(parameters)
+ .addInnerClasses(ConcurrentHashMapKeySetTest.class)
+ .run(parameters.getRuntime(), TestClass.class);
+ if (parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getMinApiLevel().isLessThan(AndroidApiLevel.Q)) {
+ // TODO(b/123160897): Support desugaring of the Java 8 change to ConcurrentHashMap::keySet.
+ result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ } else {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+ // TODO(b/123160897): Inspect that keySet has changed on API level < Q / JDK8.
+ }
+
+ static class TestClass {
+
+ static ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
+
+ public static void main(String[] args) {
+ map.put("Hello", "world");
+ for (String key : map.keySet()) {
+ System.out.println(key + ", " + map.get(key));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
index 39f0ef1..17e482f 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
@@ -29,10 +29,20 @@
this.parameters = parameters;
}
- private void checkHasCompanionClass(CodeInspector inspector) {
- assertTrue(
- inspector.allClasses().stream()
- .anyMatch(subject -> subject.getOriginalName().endsWith("$-CC")));
+ private void checkHasCompanionClassIfRequired(CodeInspector inspector) {
+ boolean canUseDefaultAndStaticInterfaceMethods =
+ parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport());
+ if (canUseDefaultAndStaticInterfaceMethods) {
+ assertTrue(
+ inspector.allClasses().stream()
+ .noneMatch(subject -> subject.getOriginalName().endsWith("$-CC")));
+ } else {
+ assertTrue(
+ inspector.allClasses().stream()
+ .anyMatch(subject -> subject.getOriginalName().endsWith("$-CC")));
+ }
}
private void checkHasLambdaClass(CodeInspector inspector) {
@@ -49,7 +59,7 @@
.addInnerClasses(DesugarToClassFile.class)
.setMinApi(parameters.getApiLevel())
.compile()
- .inspect(this::checkHasCompanionClass)
+ .inspect(this::checkHasCompanionClassIfRequired)
.inspect(this::checkHasLambdaClass)
.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index f485a25..d53e0e6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -7,7 +7,8 @@
import static com.android.tools.r8.MarkerMatcher.assertMarkersMatch;
import static com.android.tools.r8.MarkerMatcher.markerCompilationMode;
import static com.android.tools.r8.MarkerMatcher.markerHasDesugaredLibraryIdentifier;
-import static com.android.tools.r8.MarkerMatcher.markerHasMinApi;
+import static com.android.tools.r8.MarkerMatcher.markerIsDesugared;
+import static com.android.tools.r8.MarkerMatcher.markerMinApi;
import static com.android.tools.r8.MarkerMatcher.markerTool;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.not;
@@ -97,10 +98,9 @@
allOf(
markerTool(Tool.R8),
markerCompilationMode(CompilationMode.RELEASE),
- not(markerHasMinApi()),
+ not(markerIsDesugared()),
not(markerHasDesugaredLibraryIdentifier()));
- assertMarkersMatch(
- ExtractMarker.extractMarkerFromJarFile(shrunkenLib), ImmutableList.of(libraryMatcher));
+ assertMarkersMatch(ExtractMarker.extractMarkerFromJarFile(shrunkenLib), libraryMatcher);
// Build an app with the R8 compiled library.
Path app =
@@ -116,13 +116,62 @@
Matcher<Marker> d8Matcher =
allOf(
markerTool(Tool.D8),
- markerHasMinApi(),
+ markerIsDesugared(),
markerHasDesugaredLibraryIdentifier(
parameters.getApiLevel().isLessThan(AndroidApiLevel.O)));
assertMarkersMatch(
ExtractMarker.extractMarkerFromDexFile(app), ImmutableList.of(libraryMatcher, d8Matcher));
}
+ @Test
+ public void testMergeDesugaredWithDesugaredLib() throws Exception {
+ // Compile a library with D8 to CF.
+ Path desugaredLibCf =
+ testForD8(Backend.CF)
+ .addProgramClasses(Part2.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+
+ // D8 class file output marker has desugaring but no library desugaring.
+ Matcher<Marker> markerMatcher =
+ allOf(
+ markerTool(Tool.D8),
+ markerCompilationMode(CompilationMode.DEBUG),
+ markerIsDesugared(),
+ markerMinApi(parameters.getApiLevel()),
+ not(markerHasDesugaredLibraryIdentifier()));
+ assertMarkersMatch(ExtractMarker.extractMarkerFromJarFile(desugaredLibCf), markerMatcher);
+
+ Path desugaredLibDex =
+ testForD8()
+ .addProgramFiles(desugaredLibCf)
+ .disableDesugaring()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+
+ // D8 dex file output marker has the same marker as the D8 class file output.
+ assertMarkersMatch(ExtractMarker.extractMarkerFromJarFile(desugaredLibDex), markerMatcher);
+
+ // Build an app using library desugaring merging with library not using library desugaring.
+ Path app;
+ try {
+ app =
+ testForD8()
+ .addProgramFiles(buildPart1DesugaredLibrary(), desugaredLibDex)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+
+ assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(app), markerMatcher);
+ } catch (CompilationFailedException e) {
+ assertTrue(someLibraryDesugaringRequired());
+ return;
+ }
+ assert !someLibraryDesugaringRequired();
+ }
+
private void assertError(TestDiagnosticMessages m) {
List<Diagnostic> errors = m.getErrors();
if (expectError()) {
@@ -141,6 +190,10 @@
}
private boolean expectError() {
+ return someLibraryDesugaringRequired();
+ }
+
+ private boolean someLibraryDesugaringRequired() {
return parameters.getApiLevel().getLevel() <= AndroidApiLevel.N.getLevel();
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
new file mode 100644
index 0000000..9004df6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -0,0 +1,182 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.kotlin;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinMetadataTest extends DesugaredLibraryTestBase {
+
+ private static final String PKG = KotlinMetadataTest.class.getPackage().getName();
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private final KotlinTargetVersion targetVersion;
+ private static final String EXPECTED_OUTPUT = "Wuhuu, my special day is: 1997-8-29-2-14";
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}, target: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ KotlinTargetVersion.values());
+ }
+
+ public KotlinMetadataTest(
+ boolean shrinkDesugaredLibrary,
+ TestParameters parameters,
+ KotlinTargetVersion targetVersion) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ this.targetVersion = targetVersion;
+ }
+
+ private static Map<KotlinTargetVersion, Path> compiledJars = new HashMap<>();
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ compiledJars.put(
+ targetVersion,
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ Paths.get(
+ ToolHelper.TESTS_DIR,
+ "java",
+ DescriptorUtils.getBinaryNameFromJavaType(PKG),
+ "Main" + FileUtils.KT_EXTENSION))
+ .compile());
+ }
+ }
+
+ @Test
+ public void testCf() throws Exception {
+ assumeTrue(parameters.getRuntime().isCf());
+ testForRuntime(parameters)
+ .addProgramFiles(compiledJars.get(targetVersion))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .run(parameters.getRuntime(), PKG + ".MainKt")
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testTimeD8() throws Exception {
+ assumeTrue(parameters.getRuntime().isDex());
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ final File output = temp.newFile("output.zip");
+ final D8TestRunResult d8TestRunResult =
+ testForD8()
+ .addProgramFiles(compiledJars.get(targetVersion))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .setProgramConsumer(new ArchiveConsumer(output.toPath(), true))
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .addOptionsModification(
+ options -> {
+ options.testing.enableD8ResourcesPassThrough = true;
+ options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+ })
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ false)
+ .run(parameters.getRuntime(), PKG + ".MainKt")
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ if (requiresAnyCoreLibDesugaring(parameters)) {
+ d8TestRunResult.inspect(this::inspectRewrittenMetadata);
+ }
+ }
+
+ @Test
+ public void testTimeR8() throws Exception {
+ boolean desugarLibrary = parameters.isDexRuntime() && requiresAnyCoreLibDesugaring(parameters);
+ final R8FullTestBuilder testBuilder =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(compiledJars.get(targetVersion))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .addKeepMainRule(PKG + ".MainKt")
+ .addKeepAllClassesRule()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages();
+ KeepRuleConsumer keepRuleConsumer = null;
+ if (desugarLibrary) {
+ keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testBuilder.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer);
+ }
+ final R8TestCompileResult compileResult =
+ testBuilder
+ .compile()
+ .assertAllWarningMessagesMatch(
+ equalTo("Resource 'META-INF/MANIFEST.MF' already exists."));
+ if (desugarLibrary) {
+ assertNotNull(keepRuleConsumer);
+ compileResult.addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary);
+ }
+ final R8TestRunResult r8TestRunResult =
+ compileResult
+ .run(parameters.getRuntime(), PKG + ".MainKt")
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ if (desugarLibrary) {
+ r8TestRunResult.inspect(this::inspectRewrittenMetadata);
+ }
+ }
+
+ private void inspectRewrittenMetadata(CodeInspector inspector) {
+ final ClassSubject clazz =
+ inspector.clazz("com.android.tools.r8.desugar.desugaredlibrary.kotlin.Skynet");
+ assertThat(clazz, isPresent());
+ final KotlinClassMetadata kotlinClassMetadata = clazz.getKotlinClassMetadata();
+ assertNotNull(kotlinClassMetadata);
+ String metadata = KotlinMetadataWriter.kotlinMetadataToString("", kotlinClassMetadata);
+ assertThat(metadata, containsString("specialDay:Lj$/time/LocalDateTime;"));
+ assertThat(metadata, containsString("Class(name=j$/time/LocalDateTime)"));
+ assertThat(metadata, not(containsString("java.time.LocalDateTime")));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/Main.kt b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/Main.kt
new file mode 100644
index 0000000..f7e56bc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/Main.kt
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.kotlin
+
+import kotlin.reflect.full.primaryConstructor
+
+class Skynet(val specialDay: java.time.LocalDateTime)
+
+fun main() {
+ // Create Skynet reflectively and call it in a 'self-aware' fashion.
+ val primaryConstructor = Skynet::class.primaryConstructor
+ val skynet = primaryConstructor?.call(java.time.LocalDateTime.of(1997, 8, 29, 2, 14, 0))
+ val sd = skynet?.specialDay
+ println("Wuhuu, my special day is: " +
+ "${sd?.year}-${sd?.monthValue}-${sd?.dayOfMonth}-${sd?.hour}-${sd?.minute}")
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
index 3317df1..33e6587 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/ReturnTest.java
@@ -20,7 +20,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
}
public ReturnTest(TestParameters parameters) {
@@ -29,7 +29,7 @@
@Test
public void testReturn() throws Exception {
- testForRuntime(parameters)
+ testForDesugaring(parameters)
.addInnerClasses(ReturnTest.class)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java
new file mode 100644
index 0000000..538f764
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DevirtualizationAcrossFeatureSplitTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DevirtualizationAcrossFeatureSplitTest extends SplitterTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public DevirtualizationAcrossFeatureSplitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseClass.class, BaseInterface.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(FeatureMain.class, BaseInterfaceImpl.class)
+ .addKeepFeatureMainRules(FeatureMain.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .runFeature(parameters.getRuntime(), FeatureMain.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ // Base.
+
+ public static class BaseClass {
+
+ @NeverInline
+ public static void run(BaseInterface instance) {
+ instance.greet();
+ }
+ }
+
+ public interface BaseInterface {
+
+ void greet();
+ }
+
+ // Feature.
+
+ public static class FeatureMain implements RunInterface {
+
+ @Override
+ public void run() {
+ BaseClass.run(new BaseInterfaceImpl());
+ }
+ }
+
+ public static class BaseInterfaceImpl implements BaseInterface {
+
+ @Override
+ public void greet() {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 536d19d..816ed12 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -274,27 +274,30 @@
void run();
}
- static class SplitRunner {
+ public static class SplitRunner {
/* We support two different modes:
* - One argument to main:
* Pass in the class to be loaded, must implement RunInterface, run will be called
- * - Two arguments to main:
+ * - Two or more arguments to main:
* Pass in the class to be loaded, must implement RunInterface, run will be called
- * Pass in the feature split that we class load
- *
+ * Pass in the feature split that we class load, and an optional list of other feature
+ * splits that must be loaded before the given feature split.
*/
public static void main(String[] args) {
- if (args.length < 1 || args.length > 2) {
+ if (args.length < 1) {
throw new RuntimeException("Unsupported number of arguments");
}
String classToRun = args[0];
ClassLoader loader = SplitRunner.class.getClassLoader();
- // In the case where we simulate splits, we pass in the feature as the second argument
- if (args.length == 2) {
- try {
- loader = new PathClassLoader(args[1], SplitRunner.class.getClassLoader());
- } catch (MalformedURLException e) {
- throw new RuntimeException("Failed reading input URL");
+ // In the case where we simulate splits, the second argument is the feature to load, followed
+ // by all the other features that it depends on.
+ if (args.length >= 2) {
+ for (int i = args.length - 1; i >= 1; i--) {
+ try {
+ loader = new PathClassLoader(args[i], loader);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Failed reading input URL");
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/StaticClassMergingInFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/StaticClassMergingInFeatureSplitTest.java
new file mode 100644
index 0000000..a1c961e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/StaticClassMergingInFeatureSplitTest.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StaticClassMergingInFeatureSplitTest extends SplitterTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public StaticClassMergingInFeatureSplitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseClassA.class, BaseClassB.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(Feature1Main.class, Feature1ClassA.class, Feature1ClassB.class)
+ .addFeatureSplit(Feature2Main.class, Feature2ClassA.class, Feature2ClassB.class)
+ .addKeepFeatureMainRules(Feature1Main.class, Feature2Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspectBase, this::inspectFeature1, this::inspectFeature2);
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature1Main.class, compileResult.getFeature(0))
+ .assertSuccessWithOutputLines("Hello from feature 1!");
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature2Main.class, compileResult.getFeature(1))
+ .assertSuccessWithOutputLines("Hello from feature 2!");
+ }
+
+ private void inspectBase(CodeInspector inspector) {
+ assertThat(inspector.clazz(BaseClassA.class), isPresent());
+ assertThat(inspector.clazz(BaseClassB.class), not(isPresent()));
+ }
+
+ private void inspectFeature1(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature1ClassA.class), isPresent());
+ assertThat(inspector.clazz(Feature1ClassB.class), not(isPresent()));
+ }
+
+ private void inspectFeature2(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature2ClassA.class), isPresent());
+ assertThat(inspector.clazz(Feature2ClassB.class), not(isPresent()));
+ }
+
+ // Base.
+
+ public static class BaseClassA {
+
+ @NeverInline
+ public static void greet() {
+ System.out.print("Hello");
+ }
+ }
+
+ @NeverClassInline
+ public static class BaseClassB {
+
+ @NeverInline
+ public static void greet() {
+ System.out.print(" ");
+ }
+ }
+
+ // Feature 1.
+
+ public static class Feature1Main implements RunInterface {
+
+ public void run() {
+ BaseClassA.greet();
+ BaseClassB.greet();
+ Feature1ClassA.greet();
+ Feature1ClassB.greet();
+ }
+ }
+
+ static class Feature1ClassA {
+
+ @NeverInline
+ public static void greet() {
+ System.out.print("from feature 1");
+ }
+ }
+
+ @NeverClassInline
+ static class Feature1ClassB {
+
+ @NeverInline
+ public static void greet() {
+ System.out.println("!");
+ }
+ }
+
+ // Feature 2.
+
+ public static class Feature2Main implements RunInterface {
+
+ @NeverInline
+ public void run() {
+ BaseClassA.greet();
+ BaseClassB.greet();
+ Feature2ClassA.greet();
+ Feature2ClassB.greet();
+ }
+ }
+
+ static class Feature2ClassA {
+
+ @NeverInline
+ public static void greet() {
+ System.out.print("from feature 2");
+ }
+ }
+
+ @NeverClassInline
+ static class Feature2ClassB {
+
+ @NeverInline
+ public static void greet() {
+ System.out.println("!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java
new file mode 100644
index 0000000..4ff8f4b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingAcrossFeatureSplitTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.BaseClass;
+import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.Feature1Class;
+import com.android.tools.r8.dexsplitter.VerticalClassMergingInFeatureSplitTest.Feature2Class;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergingAcrossFeatureSplitTest extends SplitterTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public VerticalClassMergingAcrossFeatureSplitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseClass.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(Feature1Class.class)
+ .addFeatureSplit(Feature2Main.class, Feature2Class.class)
+ .addKeepFeatureMainRule(Feature2Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspectBase, this::inspectFeature1, this::inspectFeature2);
+
+ // Run feature 2 on top of feature 1.
+ compileResult
+ .runFeature(
+ parameters.getRuntime(),
+ Feature2Main.class,
+ compileResult.getFeature(1),
+ compileResult.getFeature(0))
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspectBase(CodeInspector inspector) {
+ assertThat(inspector.clazz(BaseClass.class), isPresent());
+ }
+
+ private void inspectFeature1(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature1Class.class), isPresent());
+ }
+
+ private void inspectFeature2(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature2Class.class), isPresent());
+ }
+
+ // Base.
+
+ public static class BaseClass {
+
+ @NeverInline
+ public void greet() {
+ System.out.println("world!");
+ }
+ }
+
+ // Feature 1.
+
+ public static class Feature1Class extends BaseClass {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print(" ");
+ super.greet();
+ }
+ }
+
+ // Feature 2.
+
+ public static class Feature2Main implements RunInterface {
+
+ @Override
+ public void run() {
+ new Feature2Class().greet();
+ }
+ }
+
+ @NeverClassInline
+ static class Feature2Class extends Feature1Class {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print("Hello");
+ super.greet();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java
new file mode 100644
index 0000000..13234ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/VerticalClassMergingInFeatureSplitTest.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergingInFeatureSplitTest extends SplitterTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public VerticalClassMergingInFeatureSplitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(BaseClass.class, BaseClassWithBaseSubclass.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(
+ Feature1Main.class, Feature1Class.class, Feature1ClassWithSameFeatureSubclass.class)
+ .addFeatureSplit(
+ Feature2Main.class, Feature2Class.class, Feature2ClassWithSameFeatureSubclass.class)
+ .addKeepFeatureMainRules(Feature1Main.class, Feature2Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspectBase, this::inspectFeature1, this::inspectFeature2);
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature1Main.class, compileResult.getFeature(0))
+ .assertSuccessWithOutputLines("Hello world!");
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature2Main.class, compileResult.getFeature(1))
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspectBase(CodeInspector inspector) {
+ assertThat(inspector.clazz(BaseClass.class), isPresent());
+ assertThat(inspector.clazz(BaseClassWithBaseSubclass.class), not(isPresent()));
+ }
+
+ private void inspectFeature1(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature1Class.class), isPresent());
+ assertThat(inspector.clazz(Feature1ClassWithSameFeatureSubclass.class), not(isPresent()));
+ }
+
+ private void inspectFeature2(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature2Class.class), isPresent());
+ assertThat(inspector.clazz(Feature2ClassWithSameFeatureSubclass.class), not(isPresent()));
+ }
+
+ // Base.
+
+ static class BaseClassWithBaseSubclass {
+
+ @NeverInline
+ public void greet() {
+ System.out.print(" ");
+ }
+ }
+
+ @NeverClassInline
+ public static class BaseClass extends BaseClassWithBaseSubclass {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print("Hello");
+ super.greet();
+ }
+ }
+
+ // Feature 1.
+
+ public static class Feature1Main implements RunInterface {
+
+ @Override
+ public void run() {
+ new BaseClass().greet();
+ new Feature1Class().greet();
+ }
+ }
+
+ static class Feature1ClassWithSameFeatureSubclass {
+
+ @NeverInline
+ public void greet() {
+ System.out.println("!");
+ }
+ }
+
+ @NeverClassInline
+ static class Feature1Class extends Feature1ClassWithSameFeatureSubclass {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print("world");
+ super.greet();
+ }
+ }
+
+ // Feature 2.
+
+ public static class Feature2Main implements RunInterface {
+
+ @NeverInline
+ @Override
+ public void run() {
+ new BaseClass().greet();
+ new Feature2Class().greet();
+ }
+ }
+
+ static class Feature2ClassWithSameFeatureSubclass {
+
+ @NeverInline
+ public void greet() {
+ System.out.println("!");
+ }
+ }
+
+ @NeverClassInline
+ static class Feature2Class extends Feature2ClassWithSameFeatureSubclass {
+
+ @NeverInline
+ @Override
+ public void greet() {
+ System.out.print("world");
+ super.greet();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
new file mode 100644
index 0000000..d6dda57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClInitSideEffectEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public ClInitSideEffectEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<Switch> classToTest = Switch.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClInitSideEffectEnumUnboxingTest.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ void print() {
+ Switch.packagePrivatePrint();
+ }
+ }
+
+ @NeverClassInline
+ @NeverMerge
+ static class OtherClass {
+ static {
+ Switch.otherClassInit = true;
+ }
+ }
+
+ static class Switch {
+
+ static boolean otherClassInit = false;
+
+ public static void main(String[] args) {
+ System.out.println(MyEnum.A.ordinal());
+ System.out.println(0);
+ System.out.println(MyEnum.B.ordinal());
+ System.out.println(1);
+
+ MyEnum.A.print();
+ packagePrivatePrint();
+
+ System.out.println(otherClassInit);
+ System.out.println(false);
+
+ initializeOtherClass();
+
+ System.out.println(otherClassInit);
+ System.out.println(true);
+ }
+
+ @NeverInline
+ private static void initializeOtherClass() {
+ new OtherClass();
+ }
+
+ @NeverInline
+ static void packagePrivatePrint() {
+ System.out.println("package private dependency");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index 1a440cb..bdf46e3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -133,13 +133,8 @@
Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("directBean")),
Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("directSugar")))
.addKeepClassRules(ToStringLib.LibEnum.class)
- .allowDiagnosticMessages()
.setMinApi(parameters.getApiLevel())
- .compile()
- .inspectDiagnosticMessages(
- msg ->
- assertEnumIsBoxed(
- ToStringLib.LibEnum.class, ToStringLib.LibEnum.class.getSimpleName(), msg));
+ .compile();
}
// This class emulates a library with the three public methods getEnumXXX.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
index 758ed36..4f724d3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
@@ -71,11 +70,7 @@
assertThat(codeInspector.clazz(Companion.class).uniqueMethodWithName("method"), isPresent());
return;
}
- MethodSubject method =
- codeInspector
- .clazz(CompanionHost.class)
- .uniqueMethodWithName(
- EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "1$method");
+ MethodSubject method = codeInspector.clazz(CompanionHost.class).uniqueMethodWithName("method");
assertThat(method, isPresent());
assertEquals("int", method.getMethod().method.proto.parameters.toString());
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index 02b5a5f..f26a6be 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -66,23 +66,26 @@
}
}
- void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) {
- // TODO(b/160854837): re-enable enum unboxing.
- options.enableEnumUnboxing = true;
+ protected void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) {
options.enableEnumValueOptimization = enumValueOptimization;
options.enableEnumSwitchMapRemoval = enumValueOptimization;
options.testing.enableEnumUnboxingDebugLogs = true;
}
- void assertEnumIsUnboxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+ protected void assertEnumIsUnboxed(
+ Class<?> enumClass, String testName, TestDiagnosticMessages m) {
assertTrue(enumClass.isEnum());
+ assertEnumIsUnboxed(enumClass.getSimpleName(), testName, m);
+ }
+
+ protected void assertEnumIsUnboxed(String enumClass, String testName, TestDiagnosticMessages m) {
Diagnostic diagnostic = m.getInfos().get(0);
assertTrue(diagnostic.getDiagnosticMessage().startsWith("Unboxed enums"));
assertTrue(
StringUtils.joinLines(
"Expected enum to be removed (" + testName + "):",
m.getInfos().get(1).getDiagnosticMessage()),
- diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
+ diagnostic.getDiagnosticMessage().contains(enumClass));
}
void assertEnumIsBoxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
@@ -101,7 +104,7 @@
getAllEnumKeepRules());
}
- static EnumKeepRules[] getAllEnumKeepRules() {
+ protected static EnumKeepRules[] getAllEnumKeepRules() {
return EnumKeepRules.values();
}
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingVerticalClassMergeTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingVerticalClassMergeTest.java
new file mode 100644
index 0000000..d8396cb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingVerticalClassMergeTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumUnboxingVerticalClassMergeTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EnumUnboxingVerticalClassMergeTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumUnboxingVerticalClassMergeTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .noMinification() // For assertions.
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertVerticalClassMerged)
+ .inspectDiagnosticMessages(
+ m ->
+ assertEnumIsUnboxed(
+ UnboxableEnum.class,
+ EnumUnboxingVerticalClassMergeTest.class.getSimpleName(),
+ m))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ private void assertVerticalClassMerged(CodeInspector codeInspector) {
+ assertFalse(codeInspector.clazz(Merge1.class).isPresent());
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ Merge1 merge = new Merge2();
+ merge.print();
+ System.out.println("print");
+ merge.printDefault();
+ System.out.println("print default");
+ UnboxableEnum.A.enumPrint();
+ System.out.println("0");
+ UnboxableEnum.B.enumPrint();
+ System.out.println("1");
+ }
+
+ @NeverInline
+ static void test(UnboxableEnum e) {
+ System.out.println(e.ordinal());
+ }
+ }
+
+ static class Merge2 extends Merge1 {
+ @Override
+ public void print() {
+ System.out.println("print");
+ }
+ }
+
+ abstract static class Merge1 {
+ abstract void print();
+
+ void printDefault() {
+ System.out.println("print default");
+ }
+ }
+
+ @NeverClassInline
+ enum UnboxableEnum {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ void enumPrint() {
+ Main.test(this);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
index 8e206b8..bf038fa 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
@@ -72,11 +72,37 @@
C;
}
+ @NeverClassInline
+ enum MyEnum3 {
+ A,
+ B,
+ C;
+ }
+
static class TestClass {
public static void main(String[] args) {
virtualTest();
staticTest();
+ constructorTest();
+ }
+
+ @NeverInline
+ private static void constructorTest() {
+ new TestClass(42);
+ System.out.println("42");
+ new TestClass(MyEnum1.A);
+ System.out.println("0");
+ new TestClass(MyEnum1.B);
+ System.out.println("1");
+ new TestClass(MyEnum2.A);
+ System.out.println("0");
+ new TestClass(MyEnum2.B);
+ System.out.println("1");
+ new TestClass(MyEnum3.A);
+ System.out.println("0");
+ new TestClass(MyEnum3.B);
+ System.out.println("1");
}
@NeverInline
@@ -91,6 +117,10 @@
System.out.println("0");
staticMethod(MyEnum2.B);
System.out.println("1");
+ staticMethod(MyEnum3.A);
+ System.out.println("0");
+ staticMethod(MyEnum3.B);
+ System.out.println("1");
}
@NeverInline
@@ -106,6 +136,32 @@
System.out.println("0");
testClass.virtualMethod(MyEnum2.B);
System.out.println("1");
+ testClass.virtualMethod(MyEnum3.A);
+ System.out.println("0");
+ testClass.virtualMethod(MyEnum3.B);
+ System.out.println("1");
+ }
+
+ public TestClass() {}
+
+ @NeverInline
+ public TestClass(MyEnum1 e1) {
+ System.out.println(e1.ordinal());
+ }
+
+ @NeverInline
+ public TestClass(MyEnum2 e2) {
+ System.out.println(e2.ordinal());
+ }
+
+ @NeverInline
+ public TestClass(MyEnum3 e3) {
+ System.out.println(e3.ordinal());
+ }
+
+ @NeverInline
+ public TestClass(int i) {
+ System.out.println(i);
}
@NeverInline
@@ -114,8 +170,13 @@
}
@NeverInline
- public void virtualMethod(MyEnum2 e1) {
- System.out.println(e1.ordinal());
+ public void virtualMethod(MyEnum2 e2) {
+ System.out.println(e2.ordinal());
+ }
+
+ @NeverInline
+ public void virtualMethod(MyEnum3 e3) {
+ System.out.println(e3.ordinal());
}
@NeverInline
@@ -129,8 +190,13 @@
}
@NeverInline
- public static void staticMethod(MyEnum2 e1) {
- System.out.println(e1.ordinal());
+ public static void staticMethod(MyEnum2 e2) {
+ System.out.println(e2.ordinal());
+ }
+
+ @NeverInline
+ public static void staticMethod(MyEnum3 e3) {
+ System.out.println(e3.ordinal());
}
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index 76c06cb..f58a069 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -4,10 +4,15 @@
package com.android.tools.r8.enumunboxing;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -17,7 +22,7 @@
@RunWith(Parameterized.class)
public class SwitchEnumUnboxingTest extends EnumUnboxingTestBase {
- private static final Class<?> ENUM_CLASS = MyEnum.class;
+ private static final Class<?> ENUM_CLASS = MyEnumFewCases.class;
private final TestParameters parameters;
private final boolean enumValueOptimization;
@@ -45,10 +50,12 @@
.addKeepRules(enumKeepRules.getKeepRules())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .noMinification() // For assertions.
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
.compile()
+ .inspect(this::assertSwitchPresentButSwitchMapRemoved)
.inspectDiagnosticMessages(
m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
.run(parameters.getRuntime(), classToTest)
@@ -56,25 +63,87 @@
assertLines2By2Correct(run.getStdOut());
}
+ private void assertSwitchPresentButSwitchMapRemoved(CodeInspector i) {
+ if (enumValueOptimization) {
+ assertFalse(
+ i.clazz("com.android.tools.r8.enumunboxing.SwitchEnumUnboxingTest$1").isPresent());
+ }
+ assertTrue(
+ i.clazz(Switch.class)
+ .uniqueMethodWithName("switchOnEnumManyCases")
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isSwitch));
+ }
+
@NeverClassInline
- enum MyEnum {
+ enum MyEnumFewCases {
A,
B,
- C
+ C;
+
+ @NeverInline
+ void print() {
+ Switch.packagePrivatePrint();
+ }
+ }
+
+ @NeverClassInline
+ enum MyEnumManyCases {
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I;
+
+ @NeverInline
+ void print() {
+ Switch.packagePrivatePrint();
+ }
}
static class Switch {
public static void main(String[] args) {
- System.out.println(switchOnEnum(MyEnum.A));
+ System.out.println(switchOnEnumFewCases(MyEnumFewCases.A));
System.out.println(0xC0FFEE);
- System.out.println(switchOnEnum(MyEnum.B));
+ System.out.println(switchOnEnumFewCases(MyEnumFewCases.B));
System.out.println(0xBABE);
+
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.A));
+ System.out.println(0xACE);
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.B));
+ System.out.println(0xBABE);
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.C));
+ System.out.println(0xC0FFEE);
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.D));
+ System.out.println(0xDEC0DE);
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.E));
+ System.out.println(0xEFFACE);
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.F));
+ System.out.println(0xF00D);
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.G));
+ System.out.println(0x0);
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.H));
+ System.out.println(0x1);
+ System.out.println(switchOnEnumManyCases(MyEnumManyCases.I));
+ System.out.println(0x2);
+
+ MyEnumFewCases.A.print();
+ MyEnumManyCases.A.print();
}
- // Avoid removing the switch entirely.
@NeverInline
- static int switchOnEnum(MyEnum e) {
+ static void packagePrivatePrint() {
+ System.out.println("package private dependency");
+ }
+
+ // This switch will be converted into branches.
+ @NeverInline
+ static int switchOnEnumFewCases(MyEnumFewCases e) {
switch (e) {
case A:
return 0xC0FFEE;
@@ -84,5 +153,32 @@
return 0xDEADBEEF;
}
}
+
+ // This switch will remain a switch.
+ @NeverInline
+ static int switchOnEnumManyCases(MyEnumManyCases e) {
+ switch (e) {
+ case A:
+ return 0xACE;
+ case B:
+ return 0xBABE;
+ case C:
+ return 0xC0FFEE;
+ case D:
+ return 0xDEC0DE;
+ case E:
+ return 0xEFFACE;
+ case F:
+ return 0xF00D;
+ case G:
+ return 0x0;
+ case H:
+ return 0x1;
+ case I:
+ return 0x2;
+ default:
+ return 0xDEADBEEF;
+ }
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/Main.kt b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/Main.kt
new file mode 100644
index 0000000..3e5e636
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/Main.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing.kotlin
+
+enum class Color {
+ RED,
+ GREEN,
+ BLUE
+}
+
+fun main() {
+ enumValues<Color>().forEach { println(it.name) }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
new file mode 100644
index 0000000..1bb9c1e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing.kotlin;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimpleKotlinEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+ private final KotlinTargetVersion targetVersion;
+
+ private static final String PKG = SimpleKotlinEnumUnboxingTest.class.getPackage().getName();
+ private static Map<KotlinTargetVersion, Path> jars = new HashMap<>();
+
+ @Parameters(name = "{0}, valueOpt: {1}, keep: {2}, kotlin targetVersion: {3}")
+ public static List<Object[]> enumUnboxingTestParameters() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ BooleanUtils.values(),
+ getAllEnumKeepRules(),
+ KotlinTargetVersion.values());
+ }
+
+ public SimpleKotlinEnumUnboxingTest(
+ TestParameters parameters,
+ boolean enumValueOptimization,
+ EnumKeepRules enumKeepRules,
+ KotlinTargetVersion targetVersion) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ this.targetVersion = targetVersion;
+ }
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ jars.put(
+ targetVersion,
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ Paths.get(
+ ToolHelper.TESTS_DIR,
+ "java",
+ DescriptorUtils.getBinaryNameFromJavaType(PKG),
+ "Main.kt"))
+ .compile());
+ }
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForR8(parameters.getBackend())
+ .addProgramFiles(jars.get(targetVersion))
+ .addKeepMainRule(PKG + ".MainKt")
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addKeepRuntimeVisibleAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> {
+ assertEnumIsUnboxed(
+ PKG + ".Color", SimpleKotlinEnumUnboxingTest.class.getSimpleName(), m);
+ })
+ .run(parameters.getRuntime(), PKG + ".MainKt")
+ .assertSuccessWithOutputLines("RED", "GREEN", "BLUE");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index 04c8788..96d5012 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -71,7 +71,7 @@
InternalOptions options = new InternalOptions();
DexApplication dexApplication =
new ApplicationReader(application, options, Timing.empty()).read();
- AppView<?> appView = AppView.createForD8(new AppInfo(dexApplication));
+ AppView<?> appView = AppView.createForD8(AppInfo.createInitialAppInfo(dexApplication));
// Build the code, and split the code into three blocks.
MethodSubject methodSubject = getMethodSubject(application, signature);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
index 3ce1ee1..0eb5fab 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
@@ -14,10 +14,10 @@
import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.NeverPropagateValue;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -184,7 +184,7 @@
.assertSuccessWithOutput(JAVA_OUTPUT);
}
- private void test(TestRunResult result, boolean hasLiveness) throws Exception {
+ private void test(SingleTestRunResult<?> result, boolean hasLiveness) throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index cdb9264..2485f20 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -134,7 +134,7 @@
InternalOptions options = new InternalOptions();
options.debug = true;
- AppInfo appInfo = new AppInfo(DexApplication.builder(options, null).build());
+ AppInfo appInfo = AppInfo.createInitialAppInfo(DexApplication.builder(options, null).build());
AppView<?> appView = AppView.createForD8(appInfo);
IRCode code =
new IRCode(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
index 83c5243..ca64a9f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -12,10 +12,10 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -81,9 +81,8 @@
}
private void test(
- TestRunResult result,
- int expectedCountInMain,
- int expectedCountInConsumer) throws Exception {
+ SingleTestRunResult<?> result, int expectedCountInMain, int expectedCountInConsumer)
+ throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
index 1867272..43ea1b7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ir.optimize.canonicalization.ConstClassMain.Outer;
import com.android.tools.r8.ir.optimize.canonicalization.ConstClassMain.Outer.Inner;
import com.android.tools.r8.utils.InternalOptions;
@@ -144,8 +144,8 @@
});
}
- private void test(
- TestRunResult result, int mainCount, int outerCount, int innerCount) throws Exception {
+ private void test(SingleTestRunResult result, int mainCount, int outerCount, int innerCount)
+ throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/DexItemBasedConstStringCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/DexItemBasedConstStringCanonicalizationTest.java
index 5f8e918..5ef0d45 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/DexItemBasedConstStringCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/DexItemBasedConstStringCanonicalizationTest.java
@@ -10,8 +10,8 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ir.optimize.reflection.GetNameTestBase;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -69,8 +69,8 @@
.assertSuccessWithOutput(JAVA_OUTPUT);
}
- private void test(
- TestRunResult result, int expectedGetNameCount, int expectedConstString) throws Exception {
+ private void test(SingleTestRunResult result, int expectedGetNameCount, int expectedConstString)
+ throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IdempotentFunctionCallCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IdempotentFunctionCallCanonicalizationTest.java
index d3a87fa..5c8cfd8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IdempotentFunctionCallCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IdempotentFunctionCallCanonicalizationTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -180,11 +180,12 @@
}
private void test(
- TestRunResult result,
+ SingleTestRunResult<?> result,
int expectedMaxCount,
int expectedBooleanValueOfCount,
int expectedIntValueOfCount,
- int expectedLongValueOfCount) throws Exception {
+ int expectedLongValueOfCount)
+ throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StringInMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StringInMonitorTest.java
index f5e1762..bc04d7e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StringInMonitorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StringInMonitorTest.java
@@ -13,10 +13,10 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -104,10 +104,11 @@
}
private void test(
- TestRunResult result,
+ SingleTestRunResult result,
int expectedConstStringCount1,
int expectedConstStringCount2,
- int expectedConstStringCount3) throws Exception {
+ int expectedConstStringCount3)
+ throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 1058615..ffacc6a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -13,9 +13,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
@@ -80,7 +80,7 @@
ClassWithFinal.class
};
String javaOutput = runOnJava(main);
- TestRunResult result =
+ SingleTestRunResult<?> result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
@@ -193,7 +193,7 @@
CodeTestClass.class
};
String javaOutput = runOnJava(main);
- TestRunResult result =
+ SingleTestRunResult<?> result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
@@ -229,7 +229,7 @@
InvalidRootsTestClass.InitNeverReturnsNormally.class
};
String javaOutput = runOnJava(main);
- TestRunResult result =
+ SingleTestRunResult<?> result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableProguardTestOptions()
@@ -286,7 +286,7 @@
LambdasTestClass.IfaceUtil.class
};
String javaOutput = runOnJava(main);
- TestRunResult result =
+ SingleTestRunResult<?> result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.addKeepMainRule(main)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
index dbcb237..f99639f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
@@ -10,9 +10,9 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -99,7 +99,7 @@
}
private void test(
- TestRunResult result, int expectedForNameCount, int expectedConstClassCount)
+ SingleTestRunResult<?> result, int expectedForNameCount, int expectedConstClassCount)
throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 848bf1d..f5d0eb9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -11,8 +11,8 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.ForceInline;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -227,7 +227,7 @@
.assertSuccessWithOutput(JAVA_OUTPUT);
}
- private void test(TestRunResult result, int expectedCount) throws Exception {
+ private void test(SingleTestRunResult<?> result, int expectedCount) throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index a16e8d5..14d7837 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -12,8 +12,8 @@
import com.android.tools.r8.ForceInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.StringUtils;
@@ -187,7 +187,7 @@
.assertSuccessWithOutput(JAVA_OUTPUT);
}
- private void test(TestRunResult result) throws Exception {
+ private void test(SingleTestRunResult<?> result) throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index fc87e9b..77c4810 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -14,10 +14,10 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeDirect;
import com.android.tools.r8.code.InvokeStatic;
@@ -124,7 +124,7 @@
@Test
public void testTrivial() throws Exception {
- TestRunResult result =
+ SingleTestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
@@ -243,7 +243,7 @@
HostOkFieldOnly.class,
CandidateOkFieldOnly.class
};
- TestRunResult result =
+ SingleTestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
@@ -281,7 +281,7 @@
CandidateConflictField.class
};
String javaOutput = runOnJava(main);
- TestRunResult result =
+ SingleTestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
@@ -408,7 +408,7 @@
Candidate.class
};
String javaOutput = runOnJava(main);
- TestRunResult result =
+ SingleTestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
index add1003..5852266 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -107,7 +107,7 @@
}
private void test(
- TestRunResult result,
+ SingleTestRunResult<?> result,
int expectedStringLengthCountInClinit,
int expectedConstNumberCountInClinit,
int expectedStringLengthCountInInstanceMethod,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
index cef72dc..ef8217a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -87,7 +87,8 @@
.assertSuccessWithOutput(JAVA_OUTPUT);
}
- private void test(TestRunResult result, boolean isR8, boolean isReleaseMode) throws Exception {
+ private void test(SingleTestRunResult result, boolean isR8, boolean isReleaseMode)
+ throws Exception {
// TODO(b/154899065): The lack of subtyping made the escape analysis to regard
// StringBuilder#toString as an alias-introducing instruction.
// For now, debug v.s. release mode of D8 have the same result.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index babfa89..1f7b97d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -9,10 +9,10 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -138,7 +138,8 @@
}).count();
}
- private void test(TestRunResult result, int expectedStringContentCheckerCount) throws Exception {
+ private void test(SingleTestRunResult<?> result, int expectedStringContentCheckerCount)
+ throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
@@ -156,7 +157,7 @@
public void testD8() throws Exception {
assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
- TestRunResult result =
+ SingleTestRunResult<?> result =
testForD8()
.debug()
.addProgramClasses(MAIN)
@@ -177,7 +178,7 @@
@Test
public void testR8() throws Exception {
- TestRunResult result =
+ SingleTestRunResult<?> result =
testForR8(parameters.getBackend())
.addProgramClasses(MAIN)
.enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringHashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringHashCodeTest.java
index 2f2ab15..caddcb6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringHashCodeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringHashCodeTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -62,7 +62,7 @@
}
private void test(
- TestRunResult<?> result, int expectedStringHashCodeCount, int expectedConstNumberCount)
+ SingleTestRunResult<?> result, int expectedStringHashCodeCount, int expectedConstNumberCount)
throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index 0fe6e92..5c9c9a3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -61,7 +61,7 @@
assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
}
- private void test(TestRunResult result, int expectedStringIsEmptyCount) throws Exception {
+ private void test(SingleTestRunResult result, int expectedStringIsEmptyCount) throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
index f19fa6f..59c3d5c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
@@ -12,10 +12,10 @@
import com.android.tools.r8.ForceInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -59,7 +59,7 @@
}
private void test(
- TestRunResult result, int expectedStringLengthCount, int expectedConstNumberCount)
+ SingleTestRunResult<?> result, int expectedStringLengthCount, int expectedConstNumberCount)
throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringToStringTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringToStringTest.java
index 0740b43..a6f314d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringToStringTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringToStringTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -69,7 +69,8 @@
}).count();
}
- private void test(TestRunResult result, int expectedStringToStringCount) throws Exception {
+ private void test(SingleTestRunResult<?> result, int expectedStringToStringCount)
+ throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 0333284..7d68bda 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.ForceInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ir.optimize.string.StringValueOfTest.TestClass.Foo;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -80,7 +80,8 @@
instructionSubject.isConstString("null", JumboStringMode.ALLOW)).count();
}
- private void test(TestRunResult<?> result, boolean isR8, boolean isRelease) throws Exception {
+ private void test(SingleTestRunResult<?> result, boolean isR8, boolean isRelease)
+ throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
@@ -107,7 +108,7 @@
@Test
public void testR8() throws Exception {
- TestRunResult result =
+ SingleTestRunResult<?> result =
testForR8(parameters.getBackend())
.addProgramClassesAndInnerClasses(MAIN)
.enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 3c202dd..3f7bb60 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -367,7 +367,8 @@
public void multipleLiveTempRegisters() {
InternalOptions options = new InternalOptions();
AppView<AppInfo> appInfo =
- AppView.createForD8(new AppInfo(DexApplication.builder(options, null).build()));
+ AppView.createForD8(
+ AppInfo.createInitialAppInfo(DexApplication.builder(options, null).build()));
TypeElement objectType =
TypeElement.fromDexType(options.itemFactory.objectType, Nullability.maybeNull(), appInfo);
CollectMovesIterator moves = new CollectMovesIterator();
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index e246335..ca5b2c0 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -56,7 +56,7 @@
@Test
public void splitOverlappingInactiveIntervalWithNoNextUse() {
InternalOptions options = new InternalOptions();
- AppInfo appInfo = new AppInfo(DexApplication.builder(options, null).build());
+ AppInfo appInfo = AppInfo.createInitialAppInfo(DexApplication.builder(options, null).build());
AppView<?> appView = AppView.createForD8(appInfo);
IRCode code = simpleCode();
MyRegisterAllocator allocator = new MyRegisterAllocator(appView, code);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java
new file mode 100644
index 0000000..3fb7b23
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInSealedClassNestedTest extends KotlinMetadataTestBase {
+
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "DataTestControllerStartEvent(source=source, name=reason, display=false, stamp=0)",
+ "[com.android.tools.r8.kotlin.metadata.sealed_lib.TestEvent.DiagnosticEvent,"
+ + " com.android.tools.r8.kotlin.metadata.sealed_lib.Log]");
+ private static final String PKG_LIB = PKG + ".sealed_lib";
+ private static final String PKG_APP = PKG + ".sealed_app";
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteInSealedClassNestedTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static final Map<KotlinTargetVersion, Path> sealedLibJarMap = new HashMap<>();
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path sealedLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "nested"))
+ .compile();
+ sealedLibJarMap.put(targetVersion, sealedLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = sealedLibJarMap.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataInSealedClass_nested() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(sealedLibJarMap.get(targetVersion))
+ .addKeepAllClassesRule()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .addKeepAttributes(
+ ProguardKeepAttributes.INNER_CLASSES, ProguardKeepAttributes.ENCLOSING_METHOD)
+ .compile()
+ .writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/main.kt
new file mode 100644
index 0000000..2d6f4c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/main.kt
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.TestEvent
+
+fun staticDeclaration() {
+ println(TestEvent.DiagnosticEvent.DataTestControllerStartEvent("source", "reason"))
+}
+
+fun reflectiveUse() {
+ println(TestEvent.DiagnosticEvent.DataTestControllerStartEvent::class.supertypes)
+}
+
+fun main() {
+ staticDeclaration()
+ reflectiveUse()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/nested.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/nested.kt
new file mode 100644
index 0000000..edf60d8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/nested.kt
@@ -0,0 +1,33 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.sealed_lib
+
+/** Code originating from this bug b/163359809 */
+
+interface Log {
+ val source: String
+}
+
+sealed class TestEvent(
+ open var stamp: Long = currentStamp
+) {
+ sealed class DiagnosticEvent(
+ open val name: String,
+ open val display: Boolean = false,
+ override var stamp: Long = currentStamp
+ ) : TestEvent(stamp) {
+ data class DataTestControllerStartEvent(
+ override val source: String,
+ override val name: String = DataTestControllerStartEvent::class.java.simpleName,
+ override val display: Boolean = false,
+ override var stamp: Long = currentStamp
+ ) :
+ DiagnosticEvent(name, display, stamp),
+ Log
+ }
+ companion object {
+ var currentStamp = 0L
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
new file mode 100644
index 0000000..a2bb5df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.reflection;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinReflectTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final KotlinTargetVersion targetVersion;
+ private static final String EXPECTED_OUTPUT = "Hello World!";
+ private static final String PKG = KotlinReflectTest.class.getPackage().getName();
+ private static Map<KotlinTargetVersion, Path> compiledJars = new HashMap<>();
+
+ @Parameters(name = "{0}, target: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+ }
+
+ public KotlinReflectTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+ this.parameters = parameters;
+ this.targetVersion = targetVersion;
+ }
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ compiledJars.put(
+ targetVersion,
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ Paths.get(
+ ToolHelper.TESTS_DIR,
+ "java",
+ DescriptorUtils.getBinaryNameFromJavaType(PKG),
+ "SimpleReflect" + FileUtils.KT_EXTENSION))
+ .compile());
+ }
+ }
+
+ @Test
+ public void testCf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramFiles(compiledJars.get(targetVersion))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .run(parameters.getRuntime(), PKG + ".SimpleReflectKt")
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ final File output = temp.newFile("output.zip");
+ testForD8(parameters.getBackend())
+ .addProgramFiles(compiledJars.get(targetVersion))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .setProgramConsumer(new ArchiveConsumer(output.toPath(), true))
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> {
+ options.testing.enableD8ResourcesPassThrough = true;
+ options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+ })
+ .run(parameters.getRuntime(), PKG + ".SimpleReflectKt")
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ final File foo = temp.newFile("foo");
+ testForR8(parameters.getBackend())
+ .addProgramFiles(compiledJars.get(targetVersion))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .allowDiagnosticWarningMessages()
+ .compile()
+ .writeToZip(foo.toPath())
+ .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .run(parameters.getRuntime(), PKG + ".SimpleReflectKt")
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/SimpleReflect.kt b/src/test/java/com/android/tools/r8/kotlin/reflection/SimpleReflect.kt
new file mode 100644
index 0000000..98eb089
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/SimpleReflect.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.reflection
+
+import kotlin.reflect.full.primaryConstructor
+
+class Container(val str: String)
+
+fun main() {
+ val primaryConstructor = Container::class.primaryConstructor
+ val container = primaryConstructor?.call("Hello World!")
+ println(container?.str)
+}
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index daa21a4..5b26d4f 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -9,8 +9,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestCompileResult;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -121,7 +121,7 @@
String targetFieldName,
String targetMethodName) throws Exception {
String mainClassName = testMain.getClassName();
- TestRunResult result =
+ SingleTestRunResult result =
testForR8(Backend.DEX)
.addProgramFiles(getKotlinJarFile(FOLDER))
.addProgramFiles(getJavaJarFile(FOLDER))
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index d2b2aba..4dd2fb0 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -8,7 +8,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.retrace.Retrace;
import com.android.tools.r8.retrace.RetraceCommand;
@@ -274,7 +274,7 @@
return new StackTrace(internalExtractFromJvm(stderr), stderr);
}
- public static StackTrace extractFromJvm(TestRunResult result) {
+ public static StackTrace extractFromJvm(SingleTestRunResult result) {
assertNotEquals(0, result.getExitCode());
return extractFromJvm(result.getStdErr());
}
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/StackTrace.java
index 33be5b5..be74127 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/StackTrace.java
@@ -8,7 +8,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.utils.FileUtils;
@@ -198,7 +198,7 @@
return new StackTrace(internalExtractFromJvm(stderr), stderr);
}
- public static StackTrace extractFromJvm(TestRunResult result) {
+ public static StackTrace extractFromJvm(SingleTestRunResult result) {
assertNotEquals(0, result.getExitCode());
return extractFromJvm(result.getStdErr());
}
diff --git a/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java b/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
index 39b47f6..8d62a04 100644
--- a/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
@@ -13,7 +13,6 @@
import static org.junit.Assume.assumeFalse;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.TestShrinkerBuilder;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.BooleanUtils;
@@ -75,26 +74,27 @@
repackageToRoot, quote, shrinker),
repackageToRoot && quote != Quote.NONE);
- TestRunResult<?> result =
- builder
- .addProgramClasses(mainClass)
- .addKeepRules(getKeepRules())
- .run(mainClass)
- .assertSuccessWithOutput(expectedOutput);
-
- ClassSubject testClassSubject = result.inspector().clazz(mainClass);
- assertThat(testClassSubject, isPresent());
- if (repackageToRoot) {
- if (directive.equals("-flattenpackagehierarchy")) {
- assertThat(testClassSubject.getFinalName(), startsWith("a."));
- } else if (directive.equals("-repackageclasses")) {
- assertThat(testClassSubject.getFinalName(), not(containsString(".")));
- } else {
- fail();
- }
- } else {
- assertThat(testClassSubject.getFinalName(), startsWith("greeter."));
- }
+ builder
+ .addProgramClasses(mainClass)
+ .addKeepRules(getKeepRules())
+ .run(mainClass)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspect(
+ inspector -> {
+ ClassSubject testClassSubject = inspector.clazz(mainClass);
+ assertThat(testClassSubject, isPresent());
+ if (repackageToRoot) {
+ if (directive.equals("-flattenpackagehierarchy")) {
+ assertThat(testClassSubject.getFinalName(), startsWith("a."));
+ } else if (directive.equals("-repackageclasses")) {
+ assertThat(testClassSubject.getFinalName(), not(containsString(".")));
+ } else {
+ fail();
+ }
+ } else {
+ assertThat(testClassSubject.getFinalName(), startsWith("greeter."));
+ }
+ });
}
private List<String> getKeepRules() {
diff --git a/src/test/java/com/android/tools/r8/repackage/CrossPackageInvokeSuperToPackagePrivateMethodTest.java b/src/test/java/com/android/tools/r8/repackage/CrossPackageInvokeSuperToPackagePrivateMethodTest.java
new file mode 100644
index 0000000..32a8cc8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/CrossPackageInvokeSuperToPackagePrivateMethodTest.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.repackage.testclasses.CrossPackageInvokeSuperToPackagePrivateMethodTestClasses;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class CrossPackageInvokeSuperToPackagePrivateMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public CrossPackageInvokeSuperToPackagePrivateMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .apply(this::addProgramClasses)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A", "B", "A", "C", "D", "C");
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::addProgramClasses)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::inspectRunResult);
+ }
+
+ private void addProgramClasses(TestBuilder<?, ?> builder) throws IOException {
+ builder
+ .addProgramClasses(
+ TestClass.class,
+ A.class,
+ B.class,
+ CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.C.class)
+ .addProgramClassFileData(
+ transformer(D.class)
+ .transformMethodInsnInMethod(
+ "packagePrivate",
+ (opcode, owner, name, descriptor, isInterface, continuation) ->
+ continuation.visitMethodInsn(
+ name.equals("packagePrivate") ? Opcodes.INVOKESPECIAL : opcode,
+ name.equals("packagePrivate")
+ ? binaryName(
+ CrossPackageInvokeSuperToPackagePrivateMethodTest.class)
+ + "$B"
+ : owner,
+ name,
+ descriptor,
+ isInterface))
+ .transform());
+ }
+
+ private void inspectRunResult(R8TestRunResult runResult) {
+ if (parameters.isCfRuntime()
+ || parameters.getRuntime().asDex().getVm().getVersion().isDalvik()) {
+ runResult.assertSuccessWithOutputLines("A", "B", "A", "C", "D", "C");
+ } else {
+ runResult.assertSuccessWithOutputLines("A", "B", "A", "C", "D", "B", "A");
+ if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V7_0_0)) {
+ runResult.assertStderrMatches(
+ allOf(
+ containsString("Before Android 4.1, method"),
+ containsString("would have incorrectly overridden the package-private method")));
+ }
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new A().packagePrivate();
+ new B().packagePrivate();
+ new CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.C().runPackagePrivate();
+ new D().packagePrivate();
+ }
+ }
+
+ @NeverClassInline
+ @NeverMerge
+ public static class A {
+
+ @NeverInline
+ void packagePrivate() {
+ System.out.println("A");
+ }
+ }
+
+ @NeverClassInline
+ @NeverMerge
+ public static class B extends A {
+
+ @NeverInline
+ void packagePrivate() {
+ System.out.println("B");
+ super.packagePrivate();
+ }
+ }
+
+ @NeverClassInline
+ public static class D extends CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.C {
+
+ @NeverInline
+ void packagePrivate() {
+ System.out.println("D");
+ /*super.*/packagePrivate();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
new file mode 100644
index 0000000..6d468f4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -0,0 +1,207 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateKeptMethodOnReachableClassDirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateKeptMethodOnReachableClassIndirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateMethodOnKeptClassDirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateMethodOnKeptClassIndirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateMethodOnReachableClassDirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateMethodOnReachableClassIndirect;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPublicKeptMethodAllowRenamingOnReachableClass;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPublicKeptMethodOnReachableClass;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPublicMethodOnKeptClass;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPublicMethodOnKeptClassAllowRenaming;
+import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPublicMethodOnReachableClass;
+import com.android.tools.r8.repackage.testclasses.repackagetest.KeptClass;
+import com.android.tools.r8.repackage.testclasses.repackagetest.KeptClassAllowRenaming;
+import com.android.tools.r8.repackage.testclasses.repackagetest.ReachableClassWithKeptMethod;
+import com.android.tools.r8.repackage.testclasses.repackagetest.ReachableClassWithKeptMethodAllowRenaming;
+import com.android.tools.r8.repackage.testclasses.repackagetest.TestClass;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageTest extends TestBase {
+
+ private static final String REPACKAGE_DIR = "foo";
+
+ private static final List<String> EXPECTED =
+ ImmutableList.of(
+ "KeptClass.publicMethod()",
+ "KeptClass.packagePrivateMethod()",
+ "KeptClass.packagePrivateMethod()",
+ "KeptClassAllowRenaming.publicMethod()",
+ "KeptClassAllowRenaming.packagePrivateMethod()",
+ "KeptClassAllowRenaming.packagePrivateMethod()",
+ "ReachableClassWithKeptMethod.publicMethod()",
+ "ReachableClassWithKeptMethod.packagePrivateMethod()",
+ "ReachableClassWithKeptMethod.packagePrivateMethod()",
+ "ReachableClassWithKeptMethodAllowRenaming.publicMethod()",
+ "ReachableClassWithKeptMethodAllowRenaming.packagePrivateMethod()",
+ "ReachableClassWithKeptMethodAllowRenaming.packagePrivateMethod()",
+ "ReachableClass.publicMethod()",
+ "ReachableClass.packagePrivateMethod()",
+ "ReachableClass.packagePrivateMethod()");
+
+ private final boolean allowAccessModification;
+ private final String flattenPackageHierarchyOrRepackageClasses;
+ private final TestParameters parameters;
+
+ @Parameters(name = "{1}, allow access modification: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ ImmutableList.of("flattenpackagehierarchy", "repackageclasses"),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public RepackageTest(
+ boolean allowAccessModification,
+ String flattenPackageHierarchyOrRepackageClasses,
+ TestParameters parameters) {
+ this.allowAccessModification = allowAccessModification;
+ this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getClassFilesForTestPackage(TestClass.class.getPackage()))
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"",
+ "-keep class " + KeptClass.class.getTypeName(),
+ "-keep,allowobfuscation class " + KeptClassAllowRenaming.class.getTypeName(),
+ "-keepclassmembers class " + ReachableClassWithKeptMethod.class.getTypeName() + " {",
+ " <methods>;",
+ "}",
+ "-keepclassmembers,allowobfuscation class "
+ + ReachableClassWithKeptMethodAllowRenaming.class.getTypeName()
+ + " {",
+ " <methods>;",
+ "}")
+ .allowAccessModification(allowAccessModification)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ forEachClass(
+ (clazz, eligibleForRepackaging) -> {
+ ClassSubject subject = inspector.clazz(clazz);
+ assertThat(subject, isPresent());
+ if (eligibleForRepackaging) {
+ assertEquals(
+ clazz.getTypeName(),
+ flattenPackageHierarchyOrRepackageClasses.equals("flattenpackagehierarchy")
+ ? REPACKAGE_DIR + ".a"
+ : REPACKAGE_DIR + "",
+ subject.getDexProgramClass().getType().getPackageName());
+ } else {
+ assertEquals(
+ clazz.getTypeName(),
+ RepackageTest.class.getPackage().getName() + ".testclasses.repackagetest",
+ subject.getDexProgramClass().getType().getPackageName());
+ }
+ });
+ }
+
+ /**
+ * For each test class, calls {@param consumer} with a boolean that indicates if the class is
+ * eligible for repackaging (or it needs to stay in its original package).
+ */
+ private void forEachClass(BiConsumer<Class<?>, Boolean> consumer) {
+ // TODO(b/165783399): This should be renamed to markAlwaysEligible() and always pass `true` to
+ // the consumer, since these classes should be repackaged independent of
+ // -allowaccessmodification.
+ Consumer<Class<?>> markShouldAlwaysBeEligible =
+ clazz -> consumer.accept(clazz, allowAccessModification);
+ Consumer<Class<?>> markEligibleWithAllowAccessModification =
+ clazz -> consumer.accept(clazz, allowAccessModification);
+
+ // 1) -keep class KeptClass
+
+ // 1.A) Accessing a public method on a kept class is OK.
+ markShouldAlwaysBeEligible.accept(AccessPublicMethodOnKeptClass.class);
+
+ // 1.B) Accessing a package-private method on a kept class requires -allowaccessmodification.
+ markEligibleWithAllowAccessModification.accept(
+ AccessPackagePrivateMethodOnKeptClassDirect.class);
+
+ // 1.C) Accessing a package-private method that accesses a package-private method on a kept
+ // class requires -allowaccessmodification.
+ markEligibleWithAllowAccessModification.accept(
+ AccessPackagePrivateMethodOnKeptClassIndirect.class);
+
+ // 2) -keep,allowobfuscation class KeptClass
+
+ // 2.A, 2.B, 2.C) Accessing a method on a kept class that is allowed to be renamed is OK.
+ markShouldAlwaysBeEligible.accept(AccessPublicMethodOnKeptClassAllowRenaming.class);
+ markShouldAlwaysBeEligible.accept(
+ AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.class);
+ markShouldAlwaysBeEligible.accept(
+ AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.class);
+
+ // 3) -keepclassmembers class ReachableClassWithKeptMethod { <methods>; }
+
+ // 3.A, 3.B, 3.C) Accessing a kept method is OK.
+ markShouldAlwaysBeEligible.accept(AccessPublicKeptMethodOnReachableClass.class);
+ markShouldAlwaysBeEligible.accept(AccessPackagePrivateKeptMethodOnReachableClassDirect.class);
+ markShouldAlwaysBeEligible.accept(AccessPackagePrivateKeptMethodOnReachableClassIndirect.class);
+
+ // 4) -keepclassmembers,allowobfuscation class ReachableClassWithKeptMethod { <methods>; }
+
+ // 4.A, 4.B, 4.C) Accessing a kept method is OK.
+ markShouldAlwaysBeEligible.accept(AccessPublicKeptMethodAllowRenamingOnReachableClass.class);
+ markShouldAlwaysBeEligible.accept(
+ AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.class);
+ markShouldAlwaysBeEligible.accept(
+ AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.class);
+
+ // 5) No keep rule.
+
+ // 5.A, 5.B, 5.C) Accessing a non-kept method is OK.
+ markShouldAlwaysBeEligible.accept(AccessPublicMethodOnReachableClass.class);
+ markShouldAlwaysBeEligible.accept(AccessPackagePrivateMethodOnReachableClassDirect.class);
+ markShouldAlwaysBeEligible.accept(AccessPackagePrivateMethodOnReachableClassIndirect.class);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.java b/src/test/java/com/android/tools/r8/repackage/testclasses/CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.java
new file mode 100644
index 0000000..fc9639a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.repackage.CrossPackageInvokeSuperToPackagePrivateMethodTest;
+
+public class CrossPackageInvokeSuperToPackagePrivateMethodTestClasses {
+
+ @NeverClassInline
+ @NeverMerge
+ public static class C extends CrossPackageInvokeSuperToPackagePrivateMethodTest.B {
+
+ @NeverInline
+ void packagePrivate() {
+ System.out.println("C");
+ }
+
+ public void runPackagePrivate() {
+ packagePrivate();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
new file mode 100644
index 0000000..a7af296
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect {
+
+ @NeverInline
+ static void test() {
+ ReachableClassWithKeptMethodAllowRenaming.packagePrivateMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
new file mode 100644
index 0000000..9310128
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect {
+
+ @NeverInline
+ static void test() {
+ AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
new file mode 100644
index 0000000..1c159f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateKeptMethodOnReachableClassDirect {
+
+ @NeverInline
+ static void test() {
+ ReachableClassWithKeptMethod.packagePrivateMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
new file mode 100644
index 0000000..043c43a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateKeptMethodOnReachableClassIndirect {
+
+ @NeverInline
+ static void test() {
+ AccessPackagePrivateKeptMethodOnReachableClassDirect.test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
new file mode 100644
index 0000000..45049a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect {
+
+ @NeverInline
+ static void test() {
+ KeptClassAllowRenaming.packagePrivateMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
new file mode 100644
index 0000000..8ff327f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect {
+
+ @NeverInline
+ static void test() {
+ AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
new file mode 100644
index 0000000..8ae7f71
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateMethodOnKeptClassDirect {
+
+ @NeverInline
+ static void test() {
+ KeptClass.packagePrivateMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
new file mode 100644
index 0000000..6e769dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateMethodOnKeptClassIndirect {
+
+ @NeverInline
+ static void test() {
+ AccessPackagePrivateMethodOnKeptClassDirect.test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
new file mode 100644
index 0000000..8ef5364
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateMethodOnReachableClassDirect {
+
+ @NeverInline
+ static void test() {
+ ReachableClass.packagePrivateMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
new file mode 100644
index 0000000..63d0072
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPackagePrivateMethodOnReachableClassIndirect {
+
+ @NeverInline
+ static void test() {
+ AccessPackagePrivateMethodOnReachableClassDirect.test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
new file mode 100644
index 0000000..460e3e1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPublicKeptMethodAllowRenamingOnReachableClass {
+
+ @NeverInline
+ static void test() {
+ ReachableClassWithKeptMethodAllowRenaming.publicMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
new file mode 100644
index 0000000..be42852
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPublicKeptMethodOnReachableClass {
+
+ @NeverInline
+ static void test() {
+ ReachableClassWithKeptMethod.publicMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
new file mode 100644
index 0000000..54f999d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPublicMethodOnKeptClass {
+
+ @NeverInline
+ static void test() {
+ KeptClass.publicMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
new file mode 100644
index 0000000..16acfb5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPublicMethodOnKeptClassAllowRenaming {
+
+ @NeverInline
+ static void test() {
+ KeptClassAllowRenaming.publicMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
new file mode 100644
index 0000000..4414fd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class AccessPublicMethodOnReachableClass {
+
+ @NeverInline
+ static void test() {
+ ReachableClass.publicMethod();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/KeptClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/KeptClass.java
new file mode 100644
index 0000000..5f2632f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/KeptClass.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+
+public class KeptClass {
+
+ @NeverInline
+ static void packagePrivateMethod() {
+ System.out.println("KeptClass.packagePrivateMethod()");
+ }
+
+ @NeverInline
+ public static void publicMethod() {
+ System.out.println("KeptClass.publicMethod()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/KeptClassAllowRenaming.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/KeptClassAllowRenaming.java
new file mode 100644
index 0000000..f6ca601
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/KeptClassAllowRenaming.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+
+public class KeptClassAllowRenaming {
+
+ @NeverInline
+ static void packagePrivateMethod() {
+ System.out.println("KeptClassAllowRenaming.packagePrivateMethod()");
+ }
+
+ @NeverInline
+ public static void publicMethod() {
+ System.out.println("KeptClassAllowRenaming.publicMethod()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
new file mode 100644
index 0000000..57b2d65
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class ReachableClass {
+
+ @NeverInline
+ static void packagePrivateMethod() {
+ System.out.println("ReachableClass.packagePrivateMethod()");
+ }
+
+ @NeverInline
+ public static void publicMethod() {
+ System.out.println("ReachableClass.publicMethod()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClassWithKeptMethod.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClassWithKeptMethod.java
new file mode 100644
index 0000000..e408270
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClassWithKeptMethod.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+
+public class ReachableClassWithKeptMethod {
+
+ @NeverInline
+ static void packagePrivateMethod() {
+ System.out.println("ReachableClassWithKeptMethod.packagePrivateMethod()");
+ }
+
+ @NeverInline
+ public static void publicMethod() {
+ System.out.println("ReachableClassWithKeptMethod.publicMethod()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClassWithKeptMethodAllowRenaming.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClassWithKeptMethodAllowRenaming.java
new file mode 100644
index 0000000..7555b9b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClassWithKeptMethodAllowRenaming.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+import com.android.tools.r8.NeverInline;
+
+public class ReachableClassWithKeptMethodAllowRenaming {
+
+ @NeverInline
+ static void packagePrivateMethod() {
+ System.out.println("ReachableClassWithKeptMethodAllowRenaming.packagePrivateMethod()");
+ }
+
+ @NeverInline
+ public static void publicMethod() {
+ System.out.println("ReachableClassWithKeptMethodAllowRenaming.publicMethod()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/TestClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/TestClass.java
new file mode 100644
index 0000000..51d3af0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/TestClass.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagetest;
+
+public class TestClass {
+
+ public static void main(String[] args) {
+ testAccessesToMethodOnKeptClass();
+ testAccessesToMethodOnKeptClassAllowRenaming();
+ testAccessesToKeptMethodOnReachableClass();
+ testAccessesToKeptMethodAllowRenamingOnReachableClass();
+ testAccessesToMethodOnReachableClass();
+ }
+
+ static void testAccessesToMethodOnKeptClass() {
+ // 1) public method ingoing access
+ AccessPublicMethodOnKeptClass.test();
+
+ // 2) package-private method ingoing access
+ AccessPackagePrivateMethodOnKeptClassDirect.test();
+ AccessPackagePrivateMethodOnKeptClassIndirect.test();
+ }
+
+ static void testAccessesToMethodOnKeptClassAllowRenaming() {
+ // 1) public method ingoing access
+ AccessPublicMethodOnKeptClassAllowRenaming.test();
+
+ // 2) package-private method ingoing access
+ AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.test();
+ AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.test();
+ }
+
+ static void testAccessesToKeptMethodOnReachableClass() {
+ // 1) public method ingoing access
+ AccessPublicKeptMethodOnReachableClass.test();
+
+ // 2) package-private method ingoing access
+ AccessPackagePrivateKeptMethodOnReachableClassDirect.test();
+ AccessPackagePrivateKeptMethodOnReachableClassIndirect.test();
+ }
+
+ static void testAccessesToKeptMethodAllowRenamingOnReachableClass() {
+ // 1) public method ingoing access
+ AccessPublicKeptMethodAllowRenamingOnReachableClass.test();
+
+ // 2) package-private method ingoing access
+ AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.test();
+ AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.test();
+ }
+
+ static void testAccessesToMethodOnReachableClass() {
+ // 1) public method ingoing access
+ AccessPublicMethodOnReachableClass.test();
+
+ // 2) package-private method ingoing access
+ AccessPackagePrivateMethodOnReachableClassDirect.test();
+ AccessPackagePrivateMethodOnReachableClassIndirect.test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/DeterministicPrintUsagesTest.java b/src/test/java/com/android/tools/r8/shaking/DeterministicPrintUsagesTest.java
new file mode 100644
index 0000000..b9a56b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/DeterministicPrintUsagesTest.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DeterministicPrintUsagesTest extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public DeterministicPrintUsagesTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ public static class UsageConsumer implements StringConsumer {
+
+ List<String> strings = new ArrayList<>();
+
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ strings.add(string);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ strings = Collections.unmodifiableList(strings);
+ }
+ }
+
+ @Test
+ public void test() throws Exception {
+ List<String> names = getClassNames();
+ List<byte[]> classes = getClasses(names);
+ UsageConsumer usage1 = getUsageInfo(classes);
+ UsageConsumer usage2 = getUsageInfo(classes);
+ assertEquals(usage1.strings, usage2.strings);
+ String content = String.join("", usage1.strings);
+ for (String name : names) {
+ assertThat(content, containsString(name));
+ }
+ for (int i = 0; i < 10; i++) {
+ assertThat(content, containsString("int f" + i));
+ assertThat(content, containsString("void m" + i + "()"));
+ }
+ }
+
+ private List<String> getClassNames() {
+ List<String> descriptors = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ descriptors.add("a.A" + i);
+ }
+ return descriptors;
+ }
+
+ private List<byte[]> getClasses(List<String> names) throws Exception {
+ List<byte[]> classes = new ArrayList<>();
+ for (String name : names) {
+ classes.add(
+ transformer(A.class)
+ .setClassDescriptor(DescriptorUtils.javaTypeToDescriptor(name))
+ .transform());
+ }
+ return classes;
+ }
+
+ private UsageConsumer getUsageInfo(List<byte[]> classes) throws Exception {
+ UsageConsumer consumer = new UsageConsumer();
+ testForR8(Backend.CF)
+ .addProgramClassFileData(classes)
+ .addProgramClassFileData(
+ transformer(TestClass.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+
+ @Override
+ public FieldVisitor visitField(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ Object value) {
+ assertEquals("f", name);
+ for (int i = 0; i < 10; i++) {
+ FieldVisitor fv =
+ super.visitField(access, name + i, descriptor, signature, value);
+ fv.visitEnd();
+ }
+ return null;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ if (name.equals("m")) {
+ for (int i = 0; i < 10; i++) {
+ MethodVisitor mv =
+ super.visitMethod(
+ access, name + i, descriptor, signature, exceptions);
+ mv.visitCode();
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitEnd();
+ }
+ return null;
+ }
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ })
+ .transform())
+ .addKeepMainRule(TestClass.class)
+ .apply(b -> b.getBuilder().setProguardUsageConsumer(consumer))
+ .compile()
+ .assertNoMessages();
+ return consumer;
+ }
+
+ // Repeated with a transformer.
+ static class A {}
+
+ static class TestClass {
+
+ // Repeated with a transformer.
+ static int f;
+
+ // Repeated with a transformer.
+ static void m() {}
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
index 87c3303..83c55fd 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
@@ -12,8 +12,8 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.shaking.attributes.testclasses.Outer;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -69,7 +69,7 @@
final ClassSubject outer;
final ClassSubject inner;
- TestResult(TestRunResult result) throws Throwable {
+ TestResult(SingleTestRunResult<?> result) throws Throwable {
this.stdout = result.getStdOut();
this.inspector = result.inspector();
this.outer = inspector.clazz(Outer.class);
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index f4884c3..ad6c793 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -79,7 +79,7 @@
ProgramMethod method = getProgramMethod(originalApplication, methodSig);
// Get the IR pre-optimization.
- IRCode code = method.buildIR(AppView.createForD8(new AppInfo(application)));
+ IRCode code = method.buildIR(AppView.createForD8(AppInfo.createInitialAppInfo(application)));
// Find the exit block and assert that the value is a phi merging the exceptional edge
// with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index d4f8b5b..299a6c0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -62,7 +62,8 @@
@Override
public IRCode buildIR() {
assert codeInspector.application.options.programConsumer != null;
- return getProgramMethod().buildIR(AppView.createForD8(new AppInfo(codeInspector.application)));
+ return getProgramMethod()
+ .buildIR(AppView.createForD8(AppInfo.createInitialAppInfo(codeInspector.application)));
}
@Override
diff --git a/third_party/youtube/youtube.android_15.33.tar.gz.sha1 b/third_party/youtube/youtube.android_15.33.tar.gz.sha1
new file mode 100644
index 0000000..3ce1c4a
--- /dev/null
+++ b/third_party/youtube/youtube.android_15.33.tar.gz.sha1
@@ -0,0 +1 @@
+cdff350c62bf8c72d97be51590e90ad9105b5499
\ No newline at end of file
diff --git a/tools/asmifier.py b/tools/asmifier.py
index c2fbdc0..5290f0a 100755
--- a/tools/asmifier.py
+++ b/tools/asmifier.py
@@ -10,7 +10,7 @@
import sys
import utils
-ASM_VERSION = '7.2'
+ASM_VERSION = '8.0'
ASM_JAR = 'asm-' + ASM_VERSION + '.jar'
ASM_UTIL_JAR = 'asm-util-' + ASM_VERSION + '.jar'
diff --git a/tools/download_all_benchmark_dependencies.py b/tools/download_all_benchmark_dependencies.py
index 95b92ed..9d6b082 100755
--- a/tools/download_all_benchmark_dependencies.py
+++ b/tools/download_all_benchmark_dependencies.py
@@ -9,6 +9,7 @@
import sys
import utils
import os
+import retrace_benchmark
BUILD_TARGETS = ['downloadDeps', 'downloadAndroidCts', 'downloadDx']
@@ -26,8 +27,7 @@
utils.DownloadFromGoogleCloudStorage(utils.ANDROID_SDK + '.tar.gz.sha1',
bucket='r8-deps-internal',
auth=True)
- utils.DownloadFromGoogleCloudStorage(
- os.path.join(utils.THIRD_PARTY, 'retrace_benchmark') + '.tar.gz.sha1')
+ retrace_benchmark.download_benchmarks()
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 3641f0c..e813756 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -843,7 +843,7 @@
or args.maven
or (args.studio and not args.no_sync)
or (args.desugar_library and not args.dry_run)):
- utils.check_prodacces()
+ utils.check_gcert()
if args.google3:
targets_to_run.append(prepare_google3(args))
diff --git a/tools/retrace_benchmark.py b/tools/retrace_benchmark.py
index a819ea5..e29bb86 100755
--- a/tools/retrace_benchmark.py
+++ b/tools/retrace_benchmark.py
@@ -36,11 +36,20 @@
help='The retracer to use',
choices=RETRACERS,
required=True)
+ parser.add_argument('--download-benchmarks',
+ help='Download retrace benchmarks',
+ default=False,
+ action='store_true')
options = parser.parse_args(argv)
return options
+def download_benchmarks():
+ utils.DownloadFromGoogleCloudStorage(
+ os.path.join(utils.THIRD_PARTY, 'retrace_benchmark') + '.tar.gz.sha1')
def run_retrace(options, temp):
+ if options.download_benchmarks:
+ download_benchmarks()
if options.retracer == 'r8':
retracer_args = [
'-cp', utils.R8LIB_JAR, 'com.android.tools.r8.retrace.Retrace']
diff --git a/tools/utils.py b/tools/utils.py
index 4a171ad..3aad6c4 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -336,8 +336,8 @@
with tarfile.open(filename, 'r:gz') as tar:
tar.extractall(path=dirname)
-def check_prodacces():
- subprocess.check_call(['prodaccess'])
+def check_gcert():
+ subprocess.check_call(['gcert'])
# Note that gcs is eventually consistent with regards to list operations.
# This is not a problem in our case, but don't ever use this method
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index 828e04b..46b0b9d 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -34,6 +34,9 @@
V15_09_BASE = os.path.join(BASE, 'youtube.android_15.09')
V15_09_PREFIX = os.path.join(V15_09_BASE, 'YouTubeRelease')
+V15_33_BASE = os.path.join(BASE, 'youtube.android_15.33')
+V15_33_PREFIX = os.path.join(V15_33_BASE, 'YouTubeRelease')
+
# NOTE: we always use android.jar for SDK v25, later we might want to revise it
# to use proper android.jar version for each of youtube version separately.
ANDROID_JAR = utils.get_android_jar(25)
@@ -241,4 +244,34 @@
'min-api' : ANDROID_L_API,
}
},
+ '15.33': {
+ 'dex' : {
+ 'inputs': [os.path.join(V15_33_BASE, 'YouTubeRelease_unsigned.apk')],
+ 'pgmap': '%s_proguard.map' % V15_33_PREFIX,
+ 'libraries' : [ANDROID_JAR],
+ 'min-api' : ANDROID_L_API,
+ },
+ 'deploy' : {
+ # When -injars and -libraryjars are used for specifying inputs library
+ # sanitization is on by default. For this version of YouTube -injars and
+ # -libraryjars are not used, but library sanitization is still required.
+ 'sanitize_libraries': True,
+ 'inputs': ['%s_deploy.jar' % V15_33_PREFIX],
+ 'libraries' : [os.path.join(V15_33_BASE, 'legacy_YouTubeRelease_combined_library_jars.jar')],
+ 'pgconf': [
+ '%s_proguard.config' % V15_33_PREFIX,
+ '%s_proguard_missing_classes.config' % V15_33_PREFIX,
+ '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY],
+ 'maindexrules' : [
+ os.path.join(V15_33_BASE, 'mainDexClasses.rules'),
+ os.path.join(V15_33_BASE, 'main-dex-classes-release-optimized.pgcfg'),
+ os.path.join(V15_33_BASE, 'main_dex_YouTubeRelease_proguard.cfg')],
+ 'min-api' : ANDROID_H_MR2_API,
+ },
+ 'proguarded' : {
+ 'inputs': ['%s_proguard.jar' % V15_33_PREFIX],
+ 'pgmap': '%s_proguard.map' % V15_33_PREFIX,
+ 'min-api' : ANDROID_L_API,
+ }
+ },
}