Merge commit 'f2a9fcf875f650017c6a5593096baafc6b05ca51' into dev-release
Change-Id: I266691d5986b918d75dc65af6a1177fdf46adfcb
diff --git a/.gitignore b/.gitignore
index ba2303e..bb6648e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
!third_party/chrome/*.sha1
-!third_party/gmail/*.sha1
!third_party/gmscore/*.sha1
!third_party/internal/*.sha1
!third_party/nest/*.sha1
@@ -48,8 +47,8 @@
third_party/android_jar/lib
third_party/android_jar/lib-v[0-9][0-9]
third_party/android_jar/lib-v[0-9][0-9].tar.gz
-third_party/android_jar/lib-master
-third_party/android_jar/lib-master.tar.gz
+third_party/android_jar/lib-main
+third_party/android_jar/lib-main.tar.gz
third_party/android_jar/lib.tar.gz
third_party/android_jar/libcore_latest
third_party/android_jar/libcore_latest.tar.gz
@@ -111,7 +110,6 @@
third_party/examplesAndroidPGenerated.tar.gz
third_party/framework
third_party/framework.tar.gz
-third_party/gmail/*
third_party/gmscore/*
third_party/google/google-java-format/1.14.0
third_party/google/google-java-format/1.14.0.tar.gz
diff --git a/compatibility-faq.md b/compatibility-faq.md
index cb33077..37109c4 100644
--- a/compatibility-faq.md
+++ b/compatibility-faq.md
@@ -29,6 +29,10 @@
`-keep[classmembers],allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification class-specification`
Additionally, for attributes describing a relationship such as `InnerClass` and
`EnclosingMethod`, non-compat mode requires both endpoints being kept.
+- When optimizing or minifying the `SourceFile` attribute will always be
+rewritten to `SourceFile` unless `-renamesourcefileattribute` is used in which
+case the provided value is used. The original source file name is in the mapping
+file and when optimizing or minifying a mapping file is always produced.
## Stack traces and retracing
When compiling with R8, a mapping file can be produced to support mapping stack
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index b1ca8e7..121a668 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -644,7 +644,7 @@
fun getThirdPartyAndroidJars() : List<ThirdPartyDependency> {
return listOf(
"libcore_latest",
- "lib-master",
+ "lib-main",
"lib-v14",
"lib-v15",
"lib-v19",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 0020bab..f2dfda6 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -63,14 +63,6 @@
"rewrite_prefix": {
"java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
},
- "emulate_interface": {
- "java.util.Collection": {
- "rewrittenType": "j$.util.Collection",
- "emulatedMethods": [
- "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
- ]
- }
- },
"retarget_method": {
"java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
"java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -99,6 +91,14 @@
"rewrite_prefix": {
"java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
},
+ "emulate_interface": {
+ "java.util.Collection": {
+ "rewrittenType": "j$.util.Collection",
+ "emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+ ]
+ }
+ },
"retarget_method": {
"java.util.stream.Collector java.util.stream.Collectors#filtering(java.util.function.Predicate, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
"java.util.stream.Collector java.util.stream.Collectors#flatMapping(java.util.function.Function, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
@@ -252,6 +252,7 @@
"java.util.Collection": {
"rewrittenType": "j$.util.Collection",
"emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
"java.util.stream.Stream java.util.Collection#stream()",
"java.util.stream.Stream java.util.Collection#parallelStream()",
"java.util.Spliterator java.util.Collection#spliterator()",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index aa8b1c0..1aff889 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -77,14 +77,6 @@
"java.io.DesugarInputStream": "j$.io.DesugarInputStream",
"java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
},
- "emulate_interface": {
- "java.util.Collection": {
- "rewrittenType": "j$.util.Collection",
- "emulatedMethods": [
- "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
- ]
- }
- },
"retarget_method": {
"java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
"java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -119,6 +111,14 @@
"rewrite_prefix": {
"java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
},
+ "emulate_interface": {
+ "java.util.Collection": {
+ "rewrittenType": "j$.util.Collection",
+ "emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+ ]
+ }
+ },
"retarget_method": {
"java.util.stream.Collector java.util.stream.Collectors#filtering(java.util.function.Predicate, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
"java.util.stream.Collector java.util.stream.Collectors#flatMapping(java.util.function.Function, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
@@ -402,6 +402,7 @@
"java.util.Collection": {
"rewrittenType": "j$.util.Collection",
"emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
"java.util.stream.Stream java.util.Collection#stream()",
"java.util.stream.Stream java.util.Collection#parallelStream()",
"java.util.Spliterator java.util.Collection#spliterator()",
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 46f1ce4..85d86a6 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -771,7 +771,9 @@
internal.configureDesugaredLibrary(desugaredLibrarySpecification, synthesizedClassPrefix);
internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
- if (!enableMissingLibraryApiModeling) {
+ if (internal.isGeneratingClassFiles()
+ || (System.getProperty("com.android.tools.r8.enableApiOutliningAndStubbing") == null
+ && !enableMissingLibraryApiModeling)) {
internal.apiModelingOptions().disableApiCallerIdentification();
internal.apiModelingOptions().disableOutliningAndStubbing();
}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
index 88e9f55..28125f6 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
@@ -31,7 +31,7 @@
* <p>The context class is the class for which the global synthetic data is needed. If compiling
* in DexIndexed mode, the context class will be null.
*
- * <p>The accept method will be called at most once for a given context class (any only once at
+ * <p>The accept method will be called at most once for a given context class (and only once at
* all for a DexIndexed mode compilation). The global data for that class may be the same as for
* other context classes, but it will be provided for each context.
*
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index c9590ea..ccfe752 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -34,11 +34,12 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ThrowExceptionCode;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.PrimaryD8L8IRConverter;
import com.android.tools.r8.ir.desugar.TypeRewriter;
import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
+import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
import com.android.tools.r8.naming.RecordRewritingNamingLens;
import com.android.tools.r8.naming.VarHandleDesugaringRewritingNamingLens;
@@ -72,15 +73,14 @@
@KeepForApi
public class GlobalSyntheticsGenerator {
- @SuppressWarnings("ReferenceEquality")
private static boolean ensureAllGlobalSyntheticsModeled(SyntheticNaming naming) {
for (SyntheticKind kind : naming.kinds()) {
assert !kind.isGlobal()
|| !kind.isMayOverridesNonProgramType()
- || kind == naming.RECORD_TAG
- || kind == naming.API_MODEL_STUB
- || kind == naming.METHOD_HANDLES_LOOKUP
- || kind == naming.VAR_HANDLE;
+ || kind.equals(naming.RECORD_TAG)
+ || kind.equals(naming.API_MODEL_STUB)
+ || kind.equals(naming.METHOD_HANDLES_LOOKUP)
+ || kind.equals(naming.VAR_HANDLE);
}
return true;
}
@@ -129,8 +129,13 @@
timing.end();
assert GlobalSyntheticsGeneratorVerifier.verifyExpectedClassesArePresent(appView);
-
- ApplicationWriter.create(appView, options.getMarker()).write(executorService, app);
+ if (options.isGeneratingDex()) {
+ ApplicationWriter.create(appView, options.getMarker()).write(executorService, app);
+ } else {
+ assert options.isGeneratingClassFiles();
+ new CfApplicationWriter(appView, options.getMarker())
+ .write(options.getClassFileConsumer(), app);
+ }
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
} catch (IOException e) {
@@ -202,11 +207,18 @@
VarHandleDesugaring.ensureMethodHandlesLookupClass(
appView, varHandleEventConsumer, synthesizingContext);
- IRConverter converter = new IRConverter(appView);
- converter.processSimpleSynthesizeMethods(methodsToProcess, executorService);
+ // Commit all the synthetics to the program and then convert as per D8.
+ // We must run proper D8 conversion as the global synthetics may give rise to additional
+ // synthetics as part of their implementation.
+ assert appView.getSyntheticItems().hasPendingSyntheticClasses();
+ appView.setAppInfo(
+ new AppInfo(
+ appView.appInfo().getSyntheticItems().commit(appView.app()),
+ appView.appInfo().getMainDexInfo()));
+
+ new PrimaryD8L8IRConverter(appView, Timing.empty()).convert(appView, executorService);
appView
- .withoutClassHierarchy()
.setAppInfo(
new AppInfo(
appView.appInfo().getSyntheticItems().commit(appView.app()),
@@ -221,11 +233,12 @@
VarHandleDesugaringRewritingNamingLens.createVarHandleDesugaringRewritingNamingLens(
appView));
- // Add global synthetic classes for api stubs.
- createAllApiStubs(appView, synthesizingContext, executorService);
+ if (appView.options().isGeneratingDex()) {
+ // Add global synthetic classes for api stubs.
+ createAllApiStubs(appView, synthesizingContext, executorService);
+ }
appView
- .withoutClassHierarchy()
.setAppInfo(
new AppInfo(
appView.appInfo().getSyntheticItems().commit(appView.app()),
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
index 44f54ba..9ffc2e8 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
@@ -7,21 +7,25 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Set;
/**
* Immutable command structure for an invocation of the {@link GlobalSyntheticsGenerator} compiler.
@@ -29,9 +33,10 @@
@KeepForApi
public final class GlobalSyntheticsGeneratorCommand {
- private final ProgramConsumer programConsumer;
+ private final GlobalSyntheticsConsumer globalsConsumer;
private final Reporter reporter;
private final int minApiLevel;
+ private final boolean classfileDesugaringOnly;
private final boolean printHelp;
private final boolean printVersion;
@@ -41,10 +46,15 @@
private final DexItemFactory factory = new DexItemFactory();
private GlobalSyntheticsGeneratorCommand(
- AndroidApp inputApp, ProgramConsumer programConsumer, Reporter reporter, int minApiLevel) {
+ AndroidApp inputApp,
+ GlobalSyntheticsConsumer globalsConsumer,
+ Reporter reporter,
+ int minApiLevel,
+ boolean classfileDesugaringOnly) {
this.inputApp = inputApp;
- this.programConsumer = programConsumer;
+ this.globalsConsumer = globalsConsumer;
this.minApiLevel = minApiLevel;
+ this.classfileDesugaringOnly = classfileDesugaringOnly;
this.reporter = reporter;
this.printHelp = false;
this.printVersion = false;
@@ -55,8 +65,9 @@
this.printVersion = printVersion;
this.inputApp = null;
- this.programConsumer = null;
+ this.globalsConsumer = null;
this.minApiLevel = AndroidApiLevel.B.getLevel();
+ this.classfileDesugaringOnly = false;
reporter = new Reporter();
}
@@ -130,9 +141,15 @@
assert !internal.debug;
assert !internal.minimalMainDex;
internal.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minApiLevel));
- assert !internal.intermediate;
assert internal.retainCompileTimeAnnotations;
- internal.programConsumer = programConsumer;
+ internal.intermediate = true;
+ internal.programConsumer =
+ classfileDesugaringOnly ? new ThrowingCfConsumer() : new ThrowingDexConsumer();
+ internal.setGlobalSyntheticsConsumer(globalsConsumer);
+ if (classfileDesugaringOnly) {
+ internal.apiModelingOptions().disableApiCallerIdentification();
+ internal.apiModelingOptions().disableOutliningAndStubbing();
+ }
// Assert and fixup defaults.
assert !internal.isShrinking();
@@ -148,6 +165,33 @@
return internal;
}
+ private static class ThrowingCfConsumer implements ClassFileConsumer {
+
+ @Override
+ public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+ throw new Unreachable("Unexpected attempt to write a non-global artifact");
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ // Nothing to do.
+ }
+ }
+
+ private static class ThrowingDexConsumer implements DexIndexedConsumer {
+
+ @Override
+ public void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ throw new Unreachable("Unexpected attempt to write a non-global artifact");
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ // Nothing to do.
+ }
+ }
+
/**
* Builder for constructing a GlobalSyntheticsGeneratorCommand.
*
@@ -156,9 +200,10 @@
@KeepForApi
public static class Builder {
- private ProgramConsumer programConsumer = null;
+ private GlobalSyntheticsConsumer globalsConsumer = null;
private final Reporter reporter;
private int minApiLevel = AndroidApiLevel.B.getLevel();
+ private boolean classfileDesugaringOnly = false;
private boolean printHelp = false;
private boolean printVersion = false;
private final AndroidApp.Builder appBuilder = AndroidApp.builder();
@@ -177,6 +222,11 @@
return this;
}
+ public Builder setClassfileDesugaringOnly(boolean value) {
+ this.classfileDesugaringOnly = value;
+ return this;
+ }
+
/** Set the value of the print-help flag. */
public Builder setPrintHelp(boolean printHelp) {
this.printHelp = printHelp;
@@ -210,17 +260,32 @@
return this;
}
- /** Set an output path to consume the resulting program. */
- public Builder setProgramConsumerOutput(Path path) {
- return setProgramConsumer(
- FileUtils.isArchive(path)
- ? new DexIndexedConsumer.ArchiveConsumer(path, false)
- : new DexIndexedConsumer.DirectoryConsumer(path, false));
+ /** Set a destination to write the resulting global synthetics output file. */
+ public Builder setGlobalSyntheticsOutput(Path path) {
+ return setGlobalSyntheticsConsumer(
+ new GlobalSyntheticsConsumer() {
+
+ private boolean written = false;
+
+ @Override
+ public synchronized void accept(
+ ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+ if (written) {
+ throw new Unreachable("Unexpected attempt to repeatedly write global synthetics");
+ }
+ written = true;
+ try {
+ Files.write(path, data.copyByteData());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
}
- /** Set a consumer for obtaining the resulting program. */
- public Builder setProgramConsumer(ProgramConsumer programConsumer) {
- this.programConsumer = programConsumer;
+ /** Set a consumer for obtaining the resulting global synthetics output. */
+ public Builder setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalsConsumer) {
+ this.globalsConsumer = globalsConsumer;
return this;
}
@@ -230,7 +295,7 @@
return new GlobalSyntheticsGeneratorCommand(printHelp, printVersion);
}
return new GlobalSyntheticsGeneratorCommand(
- appBuilder.build(), programConsumer, reporter, minApiLevel);
+ appBuilder.build(), globalsConsumer, reporter, minApiLevel, classfileDesugaringOnly);
}
private boolean isPrintHelpOrPrintVersion() {
@@ -241,9 +306,8 @@
if (isPrintHelpOrPrintVersion()) {
return;
}
- if (!(programConsumer instanceof DexIndexedConsumer)) {
- reporter.error(
- "GlobalSyntheticsGenerator does not support compiling to dex per class or class files");
+ if (globalsConsumer == null) {
+ reporter.error("GlobalSyntheticsGenerator does not support compiling without output");
}
}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
index 9022e28..de61833 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
@@ -21,6 +21,7 @@
private static final String LOWER_CASE_NAME = "globalsyntheticsgenerator";
private static final String MIN_API_FLAG = "--min-api";
+ private static final String CLASSFILE_DESUGARING_MODE = "--classfile";
private static final String USAGE_MESSAGE =
StringUtils.lines("Usage: " + LOWER_CASE_NAME + " [options] " + "where options are:");
@@ -29,7 +30,14 @@
return ImmutableList.<ParseFlagInfo>builder()
.add(ParseFlagInfoImpl.getMinApi())
.add(ParseFlagInfoImpl.getLib())
- .add(ParseFlagInfoImpl.flag1("--output", "<dex-file>", "Output result in <dex-file>."))
+ .add(
+ ParseFlagInfoImpl.flag1(
+ "--output", "<globals-file>", "Output result in <globals-file>."))
+ .add(
+ ParseFlagInfoImpl.flag0(
+ "--classfile",
+ "Generate globals for only classfile to classfile desugaring.",
+ "(By default globals for both classfile and dex desugaring are generated)."))
.add(ParseFlagInfoImpl.getVersion(LOWER_CASE_NAME))
.add(ParseFlagInfoImpl.getHelp())
.build();
@@ -98,6 +106,8 @@
}
} else if (arg.equals("--lib")) {
builder.addLibraryFiles(Paths.get(nextArg));
+ } else if (arg.equals(CLASSFILE_DESUGARING_MODE)) {
+ builder.setClassfileDesugaringOnly(true);
} else if (arg.startsWith("--")) {
builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
}
@@ -105,6 +115,6 @@
if (outputPath == null) {
outputPath = Paths.get(".");
}
- return builder.setProgramConsumerOutput(outputPath);
+ return builder.setGlobalSyntheticsOutput(outputPath);
}
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7597634..819b349 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1264,6 +1264,9 @@
horizontalClassMergerOptions.disable();
// R8 CF output does not support desugaring so disable it.
internal.desugarState = DesugarState.OFF;
+ // TODO(b/333477035): Since D8 dexing now supports outline/stubbing API calls R8/CF should
+ // likely disable API caller identification too so as not to prevent inlining.
+ internal.apiModelingOptions().disableOutliningAndStubbing();
}
// EXPERIMENTAL flags.
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
index 3821b7f..aaf01ff 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
@@ -21,14 +21,14 @@
public AndroidApiLevelCompute() {
knownApiLevelCache = new KnownApiLevel[AndroidApiLevel.API_DATABASE_LEVEL.getLevel() + 1];
for (AndroidApiLevel value : AndroidApiLevel.values()) {
- if (value != AndroidApiLevel.MASTER) {
+ if (value != AndroidApiLevel.MAIN) {
knownApiLevelCache[value.getLevel()] = new KnownApiLevel(value);
}
}
}
public KnownApiLevel of(AndroidApiLevel apiLevel) {
- if (apiLevel == AndroidApiLevel.MASTER) {
+ if (apiLevel == AndroidApiLevel.MAIN) {
return ComputedApiLevel.master();
}
return knownApiLevelCache[apiLevel.getLevel()];
@@ -73,7 +73,7 @@
}
public ComputedApiLevel computeInitialMinApiLevel(InternalOptions options) {
- if (options.getMinApiLevel() == AndroidApiLevel.MASTER) {
+ if (options.getMinApiLevel() == AndroidApiLevel.MAIN) {
return ComputedApiLevel.master();
}
return new KnownApiLevel(options.getMinApiLevel());
diff --git a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
index 24b7251..5e5c280 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
@@ -161,7 +161,7 @@
class KnownApiLevel implements ComputedApiLevel {
- private static final KnownApiLevel MASTER_INSTANCE = new KnownApiLevel(AndroidApiLevel.MASTER);
+ private static final KnownApiLevel MASTER_INSTANCE = new KnownApiLevel(AndroidApiLevel.MAIN);
private final AndroidApiLevel apiLevel;
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 04ff802..5e31c47 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -189,13 +189,13 @@
if (inputApp.hasMainDexList()) {
for (StringResource resource : inputApp.getMainDexListResources()) {
if (emitDeprecatedDiagnostics) {
- options.reporter.warning(new UnsupportedMainDexListUsageDiagnostic(resource.getOrigin()));
+ options.reporter.error(new UnsupportedMainDexListUsageDiagnostic(resource.getOrigin()));
}
addToMainDexClasses(app, builder, MainDexListParser.parseList(resource, itemFactory));
}
if (!inputApp.getMainDexClasses().isEmpty()) {
if (emitDeprecatedDiagnostics) {
- options.reporter.warning(new UnsupportedMainDexListUsageDiagnostic(Origin.unknown()));
+ options.reporter.error(new UnsupportedMainDexListUsageDiagnostic(Origin.unknown()));
}
addToMainDexClasses(
app,
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 6694cb0..4fdbb35 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -89,6 +89,7 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -216,7 +217,12 @@
Collection<DexProgramClass> allClasses = classes;
classes = new ArrayList<>(allClasses.size());
for (DexProgramClass clazz : allClasses) {
- if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+ if (appView.getSyntheticItems().isGlobalSyntheticClassTransitive(clazz)) {
+ Consumer<DexProgramClass> globalSyntheticCreatedCallback =
+ appView.options().testing.globalSyntheticCreatedCallback;
+ if (globalSyntheticCreatedCallback != null) {
+ globalSyntheticCreatedCallback.accept(clazz);
+ }
globalSynthetics.add(clazz);
} else {
classes.add(clazz);
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 e096d1e..42e9f77 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -259,15 +259,6 @@
defaultTypeRewriter(appInfo));
}
- public static <T extends AppInfo> AppView<T> createForSimulatingD8InR8(T appInfo) {
- return new AppView<>(
- appInfo,
- ArtProfileCollection.empty(),
- StartupProfile.empty(),
- WholeProgramOptimizations.OFF,
- defaultTypeRewriter(appInfo));
- }
-
public static AppView<AppInfoWithClassHierarchy> createForSimulatingR8InD8(
DirectMappedDexApplication application, MainDexInfo mainDexInfo) {
ClassToFeatureSplitMap classToFeatureSplitMap =
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 6f89669..9d6d32e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -526,6 +526,15 @@
return false;
}
+ public boolean hasThrowingInstructions() {
+ for (DexInstruction instruction : instructions) {
+ if (instruction.canThrow()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public Code asCode() {
return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
index 3250adb..30e2f54 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
@@ -12,14 +12,30 @@
public class DexTypeUtils {
+ public static DexType computeApiSafeLeastUpperBound(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
+ DexType leastUpperBound = computeLeastUpperBound(appView, types);
+ return findApiSafeUpperBound(appView, leastUpperBound);
+ }
+
public static DexType computeLeastUpperBound(
AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
TypeElement join =
TypeElement.join(Iterables.transform(types, type -> type.toTypeElement(appView)), appView);
- return findApiSafeUpperBound(appView, toDexType(appView, join));
+ return toDexType(appView, join);
}
- @SuppressWarnings("ReferenceEquality")
+ public static boolean isApiSafe(
+ AppView<? extends AppInfoWithClassHierarchy> appView, DexType type) {
+ DexType apiSafeUpperBound = findApiSafeUpperBound(appView, type);
+ return apiSafeUpperBound.isIdenticalTo(type);
+ }
+
+ public static boolean isLeastUpperBoundApiSafe(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
+ return isApiSafe(appView, computeLeastUpperBound(appView, types));
+ }
+
public static DexType toDexType(
AppView<? extends AppInfoWithClassHierarchy> appView, TypeElement type) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
@@ -33,7 +49,7 @@
}
assert type.isClassType();
ClassTypeElement classType = type.asClassType();
- if (classType.getClassType() != dexItemFactory.objectType) {
+ if (classType.getClassType().isNotIdenticalTo(dexItemFactory.objectType)) {
return classType.getClassType();
}
if (classType.getInterfaces().hasSingleKnownInterface()) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index 778132e..6c98445 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -189,7 +189,7 @@
DexEncodedField newField;
if (needsRelaxedType(targetField, sourceFields)) {
DexType newFieldType =
- DexTypeUtils.computeLeastUpperBound(
+ DexTypeUtils.computeApiSafeLeastUpperBound(
appView,
Iterables.transform(
Iterables.concat(IterableUtils.singleton(targetField), sourceFields),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 0db2480..3499c30 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -77,7 +77,6 @@
private ClassMerger(
AppView<?> appView,
- IRCodeProvider codeProvider,
HorizontalClassMergerGraphLens.Builder lensBuilder,
HorizontalMergeGroup group,
Collection<VirtualMethodMerger> virtualMethodMergers) {
@@ -93,8 +92,7 @@
// Method mergers.
this.classInitializerMerger = ClassInitializerMerger.create(group);
this.instanceInitializerMergers =
- InstanceInitializerMergerCollection.create(
- appView, classIdentifiers, codeProvider, group, lensBuilder);
+ InstanceInitializerMergerCollection.create(appView, classIdentifiers, group, lensBuilder);
this.virtualMethodMergers = virtualMethodMergers;
buildClassIdentifierMap();
@@ -365,14 +363,12 @@
public static class Builder {
private final AppView<?> appView;
- private final IRCodeProvider codeProvider;
private final HorizontalMergeGroup group;
private List<VirtualMethodMerger> virtualMethodMergers;
- public Builder(AppView<?> appView, IRCodeProvider codeProvider, HorizontalMergeGroup group) {
+ public Builder(AppView<?> appView, HorizontalMergeGroup group) {
this.appView = appView;
- this.codeProvider = codeProvider;
this.group = group;
}
@@ -464,7 +460,7 @@
public ClassMerger build(
HorizontalClassMergerGraphLens.Builder lensBuilder) {
- return new ClassMerger(appView, codeProvider, lensBuilder, group, virtualMethodMergers);
+ return new ClassMerger(appView, lensBuilder, group, virtualMethodMergers);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 9307f89..51c1aca 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -20,8 +20,6 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
import com.android.tools.r8.ir.conversion.LirConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.naming.IdentifierMinifier;
import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
@@ -72,11 +70,7 @@
throws ExecutionException {
timing.begin("HorizontalClassMerger");
if (shouldRun()) {
- IRCodeProvider codeProvider =
- appView.hasClassHierarchy()
- ? IRCodeProvider.create(appView.withClassHierarchy(), this::getConversionOptions)
- : IRCodeProvider.createThrowing();
- run(runtimeTypeCheckInfo, codeProvider, executorService, timing);
+ run(runtimeTypeCheckInfo, executorService, timing);
assert ArtProfileCompletenessChecker.verify(appView);
@@ -95,13 +89,8 @@
&& !appView.hasCfByteCodePassThroughMethods();
}
- private MutableMethodConversionOptions getConversionOptions() {
- return MethodConversionOptions.forLirPhase(appView);
- }
-
private void run(
RuntimeTypeCheckInfo runtimeTypeCheckInfo,
- IRCodeProvider codeProvider,
ExecutorService executorService,
Timing timing)
throws ExecutionException {
@@ -128,7 +117,7 @@
new HorizontalClassMergerGraphLens.Builder();
// Determine which classes need a class id field.
- List<ClassMerger.Builder> classMergerBuilders = createClassMergerBuilders(codeProvider, groups);
+ List<ClassMerger.Builder> classMergerBuilders = createClassMergerBuilders(groups);
initializeClassIdFields(classMergerBuilders);
// Ensure that all allocations of classes that end up needing a class id use a constructor on
@@ -141,7 +130,7 @@
ProfileCollectionAdditions profileCollectionAdditions =
ProfileCollectionAdditions.create(appView);
SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder =
- SyntheticInitializerConverter.builder(appView, codeProvider);
+ SyntheticInitializerConverter.builder(appView);
List<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfos = new ArrayList<>();
PrunedItems prunedItems =
applyClassMergers(
@@ -184,7 +173,6 @@
// Set the new graph lens before finalizing any synthetic code.
appView.setGraphLens(horizontalClassMergerGraphLens);
- codeProvider.setGraphLens(horizontalClassMergerGraphLens);
// Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation
// sites, fields accesses, etc. are correctly transferred to the target classes.
@@ -378,13 +366,13 @@
}
private List<ClassMerger.Builder> createClassMergerBuilders(
- IRCodeProvider codeProvider, Collection<HorizontalMergeGroup> groups) {
+ Collection<HorizontalMergeGroup> groups) {
List<ClassMerger.Builder> classMergerBuilders = new ArrayList<>(groups.size());
for (HorizontalMergeGroup group : groups) {
assert group.isNonTrivial();
assert group.hasInstanceFieldMap();
assert group.hasTarget();
- classMergerBuilders.add(new ClassMerger.Builder(appView, codeProvider, group));
+ classMergerBuilders.add(new ClassMerger.Builder(appView, group));
}
return classMergerBuilders;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
deleted file mode 100644
index 95c4020..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging;
-
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
-import java.util.function.Supplier;
-
-public interface IRCodeProvider {
-
- IRCode buildIR(ProgramMethod method);
-
- void setGraphLens(GraphLens graphLens);
-
- static IRCodeProvider create(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- Supplier<MutableMethodConversionOptions> getConversionOptions) {
- return new IRCodeProviderImpl(appView, getConversionOptions);
- }
-
- static IRCodeProvider createThrowing() {
- return new IRCodeProvider() {
- @Override
- public IRCode buildIR(ProgramMethod method) {
- throw new UnsupportedOperationException("Should never build IR for methods in D8");
- }
-
- @Override
- public void setGraphLens(GraphLens graphLens) {}
- };
- }
-
- class IRCodeProviderImpl implements IRCodeProvider {
-
- private final AppView<AppInfo> appViewForConversion;
- private Supplier<MutableMethodConversionOptions> getConversionOptions;
-
- private IRCodeProviderImpl(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- Supplier<MutableMethodConversionOptions> getConversionOptions) {
- // At this point the code rewritings described by repackaging and synthetic finalization have
- // not been applied to the code objects. These code rewritings will be applied in the
- // application writer. We therefore simulate that we are in D8, to allow building IR for each
- // of the class initializers without applying the unapplied code rewritings, to avoid that we
- // apply the lens more than once to the same piece of code.
- AppView<AppInfo> appViewForConversion =
- AppView.createForSimulatingD8InR8(
- AppInfo.createInitialAppInfo(
- appView.appInfo().app(), GlobalSyntheticsStrategy.forNonSynthesizing()));
- appViewForConversion.setGraphLens(appView.graphLens());
- appViewForConversion.setCodeLens(appView.codeLens());
- this.appViewForConversion = appViewForConversion;
- this.getConversionOptions = getConversionOptions;
- }
-
- @Override
- public IRCode buildIR(ProgramMethod method) {
- return method
- .getDefinition()
- .getCode()
- .buildIR(method, appViewForConversion, getConversionOptions.get());
- }
-
- @Override
- public void setGraphLens(GraphLens graphLens) {
- appViewForConversion.setGraphLens(graphLens);
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index 96aeb10..c7dfba3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Iterables;
@@ -39,7 +40,6 @@
public static InstanceInitializerDescription analyze(
AppView<? extends AppInfoWithClassHierarchy> appView,
- IRCodeProvider codeProvider,
HorizontalMergeGroup group,
InstanceInitializer instanceInitializer) {
if (instanceInitializer.isAbsent()) {
@@ -61,19 +61,17 @@
builder.addInvokeConstructor(invokedConstructor, invokedConstructorArguments);
return builder.build();
} else {
- return analyze(appView, codeProvider, group, instanceInitializer.asPresent().getMethod());
+ return analyze(appView, group, instanceInitializer.asPresent().getMethod());
}
}
- @SuppressWarnings("ReferenceEquality")
public static InstanceInitializerDescription analyze(
AppView<? extends AppInfoWithClassHierarchy> appView,
- IRCodeProvider codeProvider,
HorizontalMergeGroup group,
ProgramMethod instanceInitializer) {
InstanceInitializerDescription.Builder builder =
InstanceInitializerDescription.builder(appView, instanceInitializer);
- IRCode code = codeProvider.buildIR(instanceInitializer);
+ IRCode code = instanceInitializer.buildIR(appView, MethodConversionOptions.nonConverting());
GraphLens codeLens = instanceInitializer.getDefinition().getCode().getCodeLens(appView);
WorkList<BasicBlock> workList = WorkList.newIdentityWorkList(code.entryBlock());
while (workList.hasNext()) {
@@ -108,8 +106,9 @@
DexField fieldReference = instancePut.getField();
DexField lensRewrittenFieldReference =
appView.graphLens().lookupField(fieldReference, codeLens);
- if (lensRewrittenFieldReference.getHolderType()
- != instanceInitializer.getHolderType()) {
+ if (lensRewrittenFieldReference
+ .getHolderType()
+ .isNotIdenticalTo(instanceInitializer.getHolderType())) {
return invalid();
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 256971c..be78b39 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -101,16 +101,12 @@
DexType[] newParameters =
new DexType[representative.getParameters().size() + BooleanUtils.intValue(needsClassId)];
System.arraycopy(oldParameters, 0, newParameters, 0, oldParameters.length);
- for (int i = 0; i < oldParameters.length; i++) {
- final int parameterIndex = i;
- Set<DexType> parameterTypes =
- SetUtils.newIdentityHashSet(
- builder ->
- instanceInitializers.forEach(
- instanceInitializer ->
- builder.accept(instanceInitializer.getParameter(parameterIndex))));
+ for (int parameterIndex = 0; parameterIndex < oldParameters.length; parameterIndex++) {
+ Set<DexType> parameterTypes = getParameterTypes(instanceInitializers, parameterIndex);
if (parameterTypes.size() > 1) {
- newParameters[i] = DexTypeUtils.computeLeastUpperBound(appView, parameterTypes);
+ DexType leastUpperBound = DexTypeUtils.computeLeastUpperBound(appView, parameterTypes);
+ assert DexTypeUtils.isApiSafe(appView, leastUpperBound);
+ newParameters[parameterIndex] = leastUpperBound;
}
}
if (needsClassId) {
@@ -120,6 +116,15 @@
return dexItemFactory.createInstanceInitializer(group.getTarget().getType(), newParameters);
}
+ private static Set<DexType> getParameterTypes(
+ List<ProgramMethod> instanceInitializers, int parameterIndex) {
+ return SetUtils.newIdentityHashSet(
+ builder ->
+ instanceInitializers.forEach(
+ instanceInitializer ->
+ builder.accept(instanceInitializer.getParameter(parameterIndex))));
+ }
+
/**
* Returns a special original method signature for the synthesized constructor that did not exist
* prior to horizontal class merging. Otherwise we might accidentally think that the synthesized
@@ -169,9 +174,11 @@
createNewGroup();
}
- private void createNewGroup() {
+ private List<ProgramMethod> createNewGroup() {
estimatedDexCodeSize = 0;
- instanceInitializerGroups.add(new ArrayList<>());
+ List<ProgramMethod> newGroup = new ArrayList<>();
+ instanceInitializerGroups.add(newGroup);
+ return newGroup;
}
public Builder add(ProgramMethod instanceInitializer) {
@@ -190,10 +197,40 @@
}
public Builder addEquivalent(ProgramMethod instanceInitializer) {
- ListUtils.last(instanceInitializerGroups).add(instanceInitializer);
+ // If adding the given constructor to the current merge group leads to any API unsafe
+ // parameter types, then the constructor should be merged into a new group.
+ List<ProgramMethod> eligibleGroup = null;
+ for (List<ProgramMethod> candidateGroup : instanceInitializerGroups) {
+ if (isMergeApiSafe(candidateGroup, instanceInitializer)) {
+ eligibleGroup = candidateGroup;
+ break;
+ }
+ }
+ if (eligibleGroup == null) {
+ eligibleGroup = createNewGroup();
+ }
+ eligibleGroup.add(instanceInitializer);
return this;
}
+ private boolean isMergeApiSafe(List<ProgramMethod> group, ProgramMethod instanceInitializer) {
+ if (group.isEmpty()) {
+ return true;
+ }
+ for (int parameterIndex = 0;
+ parameterIndex < instanceInitializer.getParameters().size();
+ parameterIndex++) {
+ Set<DexType> parameterTypes = getParameterTypes(group, parameterIndex);
+ // Adding the given instance initializer to the group can only lead to an API unsafe
+ // parameter type if the instance initializer contributes a new parameter type to the group.
+ if (parameterTypes.add(instanceInitializer.getParameter(parameterIndex))
+ && !DexTypeUtils.isLeastUpperBoundApiSafe(appView, parameterTypes)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public List<InstanceInitializerMerger> build(HorizontalMergeGroup group) {
assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
return ListUtils.map(
@@ -203,18 +240,19 @@
appView, classIdentifiers, group, instanceInitializers, lensBuilder));
}
- public InstanceInitializerMerger buildSingle(
+ public List<InstanceInitializerMerger> buildEquivalent(
HorizontalMergeGroup group, InstanceInitializerDescription instanceInitializerDescription) {
assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
- assert instanceInitializerGroups.size() == 1;
- List<ProgramMethod> instanceInitializers = ListUtils.first(instanceInitializerGroups);
- return new InstanceInitializerMerger(
- appView,
- classIdentifiers,
- group,
- instanceInitializers,
- lensBuilder,
- instanceInitializerDescription);
+ return ListUtils.map(
+ instanceInitializerGroups,
+ instanceInitializers ->
+ new InstanceInitializerMerger(
+ appView,
+ classIdentifiers,
+ group,
+ instanceInitializers,
+ lensBuilder,
+ instanceInitializerDescription));
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index e0088ec..5b9e401 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -40,7 +40,6 @@
public static InstanceInitializerMergerCollection create(
AppView<?> appView,
Reference2IntMap<DexType> classIdentifiers,
- IRCodeProvider codeProvider,
HorizontalMergeGroup group,
HorizontalClassMergerGraphLens.Builder lensBuilder) {
if (!appView.hasClassHierarchy()) {
@@ -61,7 +60,7 @@
instanceInitializer -> {
InstanceInitializerDescription description =
InstanceInitializerAnalysis.analyze(
- appViewWithClassHierarchy, codeProvider, group, instanceInitializer);
+ appViewWithClassHierarchy, group, instanceInitializer);
if (description != null) {
buildersByDescription
.computeIfAbsent(
@@ -80,14 +79,17 @@
equivalentInstanceInitializerMergers = new LinkedHashMap<>();
buildersByDescription.forEach(
(description, builder) -> {
- InstanceInitializerMerger instanceInitializerMerger =
- builder.buildSingle(group, description);
- if (instanceInitializerMerger.size() == 1) {
- // If there is only one constructor with a specific behavior, then consider it for
- // normal instance initializer merging below.
- buildersWithoutDescription.addAll(instanceInitializerMerger.getInstanceInitializers());
- } else {
- equivalentInstanceInitializerMergers.put(description, instanceInitializerMerger);
+ List<InstanceInitializerMerger> instanceInitializerMergers =
+ builder.buildEquivalent(group, description);
+ for (InstanceInitializerMerger instanceInitializerMerger : instanceInitializerMergers) {
+ if (instanceInitializerMerger.size() == 1) {
+ // If there is only one constructor with a specific behavior, then consider it for
+ // normal instance initializer merging below.
+ buildersWithoutDescription.addAll(
+ instanceInitializerMerger.getInstanceInitializers());
+ } else {
+ equivalentInstanceInitializerMergers.put(description, instanceInitializerMerger);
+ }
}
});
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index 55a073a..85ad211 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -4,14 +4,12 @@
package com.android.tools.r8.horizontalclassmerging.code;
-import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.util.ArrayList;
@@ -26,25 +24,22 @@
public class SyntheticInitializerConverter {
private final AppView<?> appView;
- private final IRCodeProvider codeProvider;
private final List<ProgramMethod> classInitializers;
- private SyntheticInitializerConverter(
- AppView<?> appView, IRCodeProvider codeProvider, List<ProgramMethod> classInitializers) {
+ private SyntheticInitializerConverter(AppView<?> appView, List<ProgramMethod> classInitializers) {
this.appView = appView;
- this.codeProvider = codeProvider;
this.classInitializers = classInitializers;
}
- public static Builder builder(AppView<?> appView, IRCodeProvider codeProvider) {
- return new Builder(appView, codeProvider);
+ public static Builder builder(AppView<?> appView) {
+ return new Builder(appView);
}
public void convertClassInitializers(ExecutorService executorService) throws ExecutionException {
if (!classInitializers.isEmpty()) {
assert appView.dexItemFactory().verifyNoCachedTypeElements();
- IRConverter converter = new IRConverter(createAppViewForConversion());
+ IRConverter converter = new IRConverter(appView);
ThreadUtils.processItems(
classInitializers,
method -> processMethod(method, converter),
@@ -54,30 +49,8 @@
}
}
- private AppView<AppInfo> createAppViewForConversion() {
- assert appView.enableWholeProgramOptimizations();
- assert appView.hasClassHierarchy();
-
- // At this point the code rewritings described by repackaging and synthetic finalization have
- // not been applied to the code objects. These code rewritings will be applied in the
- // application writer. We therefore simulate that we are in D8, to allow building IR for each of
- // the class initializers without applying the unapplied code rewritings, to avoid that we apply
- // the lens more than once to the same piece of code.
-
- // Since we are now running in D8 mode clear type elements cache.
- appView.dexItemFactory().clearTypeElementsCache();
-
- AppView<AppInfo> appViewForConversion =
- AppView.createForSimulatingD8InR8(
- AppInfo.createInitialAppInfo(
- appView.appInfo().app(), GlobalSyntheticsStrategy.forNonSynthesizing()));
- appViewForConversion.setGraphLens(appView.graphLens());
- appViewForConversion.setCodeLens(appView.codeLens());
- return appViewForConversion;
- }
-
private void processMethod(ProgramMethod method, IRConverter converter) {
- IRCode code = codeProvider.buildIR(method);
+ IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
converter.removeDeadCodeAndFinalizeIR(
code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
}
@@ -89,13 +62,11 @@
public static class Builder {
private final AppView<?> appView;
- private final IRCodeProvider codeProvider;
private final List<ProgramMethod> classInitializers = new ArrayList<>();
- private Builder(AppView<?> appView, IRCodeProvider codeProvider) {
+ private Builder(AppView<?> appView) {
this.appView = appView;
- this.codeProvider = codeProvider;
}
public Builder addClassInitializer(ProgramMethod method) {
@@ -104,7 +75,7 @@
}
public SyntheticInitializerConverter build() {
- return new SyntheticInitializerConverter(appView, codeProvider, classInitializers);
+ return new SyntheticInitializerConverter(appView, classInitializers);
}
}
}
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 9d6a8b6..82f47fe 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
@@ -65,7 +65,6 @@
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.membervaluepropagation.D8MemberValuePropagation;
@@ -330,22 +329,6 @@
return onWaveDoneActions != null;
}
- public void processSimpleSynthesizeMethods(
- List<ProgramMethod> methods, ExecutorService executorService) throws ExecutionException {
- ThreadUtils.processItems(
- methods,
- this::processAndFinalizeSimpleSynthesizedMethod,
- options.getThreadingModule(),
- executorService);
- }
-
- private void processAndFinalizeSimpleSynthesizedMethod(ProgramMethod method) {
- IRCode code = method.buildIR(appView);
- assert code != null;
- new MoveResultRewriter(appView).run(code, Timing.empty());
- removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
- }
-
/**
* This will replace the Dex code in the method with the Dex code generated from the provided IR.
*
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java
new file mode 100644
index 0000000..2862449
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java
@@ -0,0 +1,328 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion.passes;
+
+import static com.android.tools.r8.utils.BitUtils.ALL_BITS_SET_MASK;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.Add;
+import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.Binop;
+import com.android.tools.r8.ir.code.Mul;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Or;
+import com.android.tools.r8.ir.code.Shl;
+import com.android.tools.r8.ir.code.Shr;
+import com.android.tools.r8.ir.code.Sub;
+import com.android.tools.r8.ir.code.Ushr;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.Xor;
+
+/**
+ * A Binop descriptor describes left and right identity and absorbing element of binop. <code>
+ * In a space K, for a binop *:
+ * - i is left identity if for each x in K, i * x = x.
+ * - i is right identity if for each x in K, x * i = x.
+ * - a is left absorbing if for each x in K, a * x = a.
+ * - a is right absorbing if for each x in K, x * a = a.
+ * In a space K, a binop * is associative if for each x,y,z in K, (x * y) * z = x * (y * z).
+ * </code>
+ */
+enum BinopDescriptor {
+ ADD(true) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return Add.create(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer leftIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ int evaluate(int left, int right) {
+ return left + right;
+ }
+
+ @Override
+ long evaluate(long left, long right) {
+ return left + right;
+ }
+ },
+ SUB(false) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return new Sub(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ int evaluate(int left, int right) {
+ return left - right;
+ }
+
+ @Override
+ long evaluate(long left, long right) {
+ return left - right;
+ }
+ },
+ MUL(true) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return Mul.create(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer leftIdentity(boolean isBooleanValue) {
+ return 1;
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 1;
+ }
+
+ @Override
+ Integer leftAbsorbing(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer rightAbsorbing(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ int evaluate(int left, int right) {
+ return left * right;
+ }
+
+ @Override
+ long evaluate(long left, long right) {
+ return left * right;
+ }
+ },
+ // The following two can be improved if we handle ZeroDivide.
+ DIV(false) {
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 1;
+ }
+ },
+ REM(false),
+ AND(true) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return And.create(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer leftIdentity(boolean isBooleanValue) {
+ return allBitsSet(isBooleanValue);
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return allBitsSet(isBooleanValue);
+ }
+
+ @Override
+ Integer leftAbsorbing(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer rightAbsorbing(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ int evaluate(int left, int right) {
+ return left & right;
+ }
+
+ @Override
+ long evaluate(long left, long right) {
+ return left & right;
+ }
+ },
+ OR(true) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return Or.create(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer leftIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer leftAbsorbing(boolean isBooleanValue) {
+ return allBitsSet(isBooleanValue);
+ }
+
+ @Override
+ Integer rightAbsorbing(boolean isBooleanValue) {
+ return allBitsSet(isBooleanValue);
+ }
+
+ @Override
+ int evaluate(int left, int right) {
+ return left | right;
+ }
+
+ @Override
+ long evaluate(long left, long right) {
+ return left | right;
+ }
+ },
+ XOR(true) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return Xor.create(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer leftIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ int evaluate(int left, int right) {
+ return left ^ right;
+ }
+
+ @Override
+ long evaluate(long left, long right) {
+ return left ^ right;
+ }
+ },
+ SHL(false) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return new Shl(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer leftAbsorbing(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ boolean isShift() {
+ return true;
+ }
+ },
+ SHR(false) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return new Shr(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer leftAbsorbing(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ boolean isShift() {
+ return true;
+ }
+ },
+ USHR(false) {
+ @Override
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ return new Ushr(numericType, dest, left, right);
+ }
+
+ @Override
+ Integer rightIdentity(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ Integer leftAbsorbing(boolean isBooleanValue) {
+ return 0;
+ }
+
+ @Override
+ boolean isShift() {
+ return true;
+ }
+ };
+
+ final boolean associativeAndCommutative;
+
+ BinopDescriptor(boolean associativeAndCommutative) {
+ this.associativeAndCommutative = associativeAndCommutative;
+ }
+
+ Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+ throw new Unreachable();
+ }
+
+ Integer allBitsSet(boolean isBooleanValue) {
+ return isBooleanValue ? 1 : ALL_BITS_SET_MASK;
+ }
+
+ Integer leftIdentity(boolean isBooleanValue) {
+ return null;
+ }
+
+ Integer rightIdentity(boolean isBooleanValue) {
+ return null;
+ }
+
+ Integer leftAbsorbing(boolean isBooleanValue) {
+ return null;
+ }
+
+ Integer rightAbsorbing(boolean isBooleanValue) {
+ return null;
+ }
+
+ int evaluate(int left, int right) {
+ throw new Unreachable();
+ }
+
+ long evaluate(long left, long right) {
+ throw new Unreachable();
+ }
+
+ boolean isShift() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
index 160335c..7a480d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.ir.conversion.passes;
-import static com.android.tools.r8.utils.BitUtils.ALL_BITS_SET_MASK;
+import static com.android.tools.r8.ir.conversion.passes.BinopDescriptor.ADD;
+import static com.android.tools.r8.ir.conversion.passes.BinopDescriptor.SUB;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -18,10 +18,12 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.LogicalBinop;
import com.android.tools.r8.ir.code.Mul;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Or;
import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Rem;
import com.android.tools.r8.ir.code.Shl;
import com.android.tools.r8.ir.code.Shr;
@@ -45,8 +47,8 @@
private Map<Class<?>, BinopDescriptor> createBinopDescriptors() {
ImmutableMap.Builder<Class<?>, BinopDescriptor> builder = ImmutableMap.builder();
- builder.put(Add.class, BinopDescriptor.ADD);
- builder.put(Sub.class, BinopDescriptor.SUB);
+ builder.put(Add.class, ADD);
+ builder.put(Sub.class, SUB);
builder.put(Mul.class, BinopDescriptor.MUL);
builder.put(Div.class, BinopDescriptor.DIV);
builder.put(Rem.class, BinopDescriptor.REM);
@@ -59,186 +61,6 @@
return builder.build();
}
- /**
- * A Binop descriptor describes left and right identity and absorbing element of binop. <code>
- * In a space K, for a binop *:
- * - i is left identity if for each x in K, i * x = x.
- * - i is right identity if for each x in K, x * i = x.
- * - a is left absorbing if for each x in K, a * x = a.
- * - a is right absorbing if for each x in K, x * a = a.
- * In a space K, a binop * is associative if for each x,y,z in K, (x * y) * z = x * (y * z).
- * </code>
- */
- private enum BinopDescriptor {
- ADD(0, 0, null, null, true) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return Add.create(numericType, dest, left, right);
- }
-
- @Override
- int evaluate(int left, int right) {
- return left + right;
- }
-
- @Override
- long evaluate(long left, long right) {
- return left + right;
- }
- },
- SUB(null, 0, null, null, false) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return new Sub(numericType, dest, left, right);
- }
-
- @Override
- int evaluate(int left, int right) {
- return left - right;
- }
-
- @Override
- long evaluate(long left, long right) {
- return left - right;
- }
- },
- MUL(1, 1, 0, 0, true) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return Mul.create(numericType, dest, left, right);
- }
-
- @Override
- int evaluate(int left, int right) {
- return left * right;
- }
-
- @Override
- long evaluate(long left, long right) {
- return left * right;
- }
- },
- // The following two can be improved if we handle ZeroDivide.
- DIV(null, 1, null, null, false),
- REM(null, null, null, null, false),
- AND(ALL_BITS_SET_MASK, ALL_BITS_SET_MASK, 0, 0, true) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return And.create(numericType, dest, left, right);
- }
-
- @Override
- int evaluate(int left, int right) {
- return left & right;
- }
-
- @Override
- long evaluate(long left, long right) {
- return left & right;
- }
- },
- OR(0, 0, ALL_BITS_SET_MASK, ALL_BITS_SET_MASK, true) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return Or.create(numericType, dest, left, right);
- }
-
- @Override
- int evaluate(int left, int right) {
- return left | right;
- }
-
- @Override
- long evaluate(long left, long right) {
- return left | right;
- }
- },
- XOR(0, 0, null, null, true) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return Xor.create(numericType, dest, left, right);
- }
-
- @Override
- int evaluate(int left, int right) {
- return left ^ right;
- }
-
- @Override
- long evaluate(long left, long right) {
- return left ^ right;
- }
- },
- SHL(null, 0, 0, null, false) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return new Shl(numericType, dest, left, right);
- }
-
- @Override
- boolean isShift() {
- return true;
- }
- },
- SHR(null, 0, 0, null, false) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return new Shr(numericType, dest, left, right);
- }
-
- @Override
- boolean isShift() {
- return true;
- }
- },
- USHR(null, 0, 0, null, false) {
- @Override
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- return new Ushr(numericType, dest, left, right);
- }
-
- @Override
- boolean isShift() {
- return true;
- }
- };
-
- final Integer leftIdentity;
- final Integer rightIdentity;
- final Integer leftAbsorbing;
- final Integer rightAbsorbing;
- final boolean associativeAndCommutative;
-
- BinopDescriptor(
- Integer leftIdentity,
- Integer rightIdentity,
- Integer leftAbsorbing,
- Integer rightAbsorbing,
- boolean associativeAndCommutative) {
- this.leftIdentity = leftIdentity;
- this.rightIdentity = rightIdentity;
- this.leftAbsorbing = leftAbsorbing;
- this.rightAbsorbing = rightAbsorbing;
- this.associativeAndCommutative = associativeAndCommutative;
- }
-
- Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
- throw new Unreachable();
- }
-
- int evaluate(int left, int right) {
- throw new Unreachable();
- }
-
- long evaluate(long left, long right) {
- throw new Unreachable();
- }
-
- boolean isShift() {
- return false;
- }
- }
-
@Override
protected String getRewriterId() {
return "BinopRewriter";
@@ -263,7 +85,7 @@
|| binop.getNumericType() == NumericType.LONG) {
BinopDescriptor binopDescriptor = descriptors.get(binop.getClass());
assert binopDescriptor != null;
- if (identityAbsorbingSimplification(iterator, binop, binopDescriptor)) {
+ if (identityAbsorbingSimplification(iterator, binop, binopDescriptor, code)) {
hasChanged = true;
continue;
}
@@ -287,7 +109,7 @@
ConstNumber constBRight = getConstNumber(binop.rightValue());
if ((constBLeft != null && constBRight != null)
|| (constBLeft == null && constBRight == null)) {
- return false;
+ return successiveLogicalSimplificationNoConstant(iterator, binop, binopDescriptor, code);
}
Value otherValue = constBLeft == null ? binop.leftValue() : binop.rightValue();
if (otherValue.isPhi() || !otherValue.getDefinition().isBinop()) {
@@ -308,14 +130,14 @@
if (binopDescriptor.associativeAndCommutative) {
// a * x * b => x * (a * b) where (a * b) is a constant.
assert binop.isCommutative();
- Value newConst = addNewConstNumber(code, iterator, constB, constA, binopDescriptor);
- replaceBinop(iterator, code, input, newConst, binopDescriptor);
+ rewriteIntoConstThenBinop(
+ iterator, binopDescriptor, binopDescriptor, constB, constA, input, true, code);
return true;
} else if (binopDescriptor.isShift()) {
// x shift: a shift: b => x shift: (a + b) where a + b is a constant.
if (constBRight != null && constARight != null) {
- Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
- replaceBinop(iterator, code, input, newConst, binopDescriptor);
+ rewriteIntoConstThenBinop(
+ iterator, ADD, binopDescriptor, constB, constA, input, false, code);
return true;
}
} else if (binop.isSub() && constBRight != null) {
@@ -323,12 +145,10 @@
// x - a - b => x - (a + b) where (a + b) is a constant.
// We ignore b - (x - a) and b - (a - x) with constBRight != null.
if (constARight == null) {
- Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
- replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+ rewriteIntoConstThenBinop(iterator, SUB, SUB, constA, constB, input, true, code);
return true;
} else {
- Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
- replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+ rewriteIntoConstThenBinop(iterator, ADD, SUB, constB, constA, input, false, code);
return true;
}
}
@@ -337,19 +157,16 @@
// x + a - b => x + (a - b) where (a - b) is a constant.
// a + x - b => x + (a - b) where (a - b) is a constant.
// We ignore b - (x + a) and b - (a + x) with constBRight != null.
- Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
- replaceBinop(iterator, code, newConst, input, BinopDescriptor.ADD);
+ rewriteIntoConstThenBinop(iterator, SUB, ADD, constA, constB, input, true, code);
return true;
} else if (binop.isAdd() && prevBinop.isSub()) {
// x - a + b => x - (a - b) where (a - b) is a constant.
// a - x + b => (a + b) - x where (a + b) is a constant.
if (constALeft == null) {
- Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
- replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+ rewriteIntoConstThenBinop(iterator, SUB, SUB, constA, constB, input, false, code);
return true;
} else {
- Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
- replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+ rewriteIntoConstThenBinop(iterator, ADD, SUB, constB, constA, input, true, code);
return true;
}
}
@@ -357,6 +174,162 @@
return false;
}
+ private boolean successiveLogicalSimplificationNoConstant(
+ InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) {
+ if (!(binop.isAnd() || binop.isOr())) {
+ return false;
+ }
+ if (binop.leftValue().isPhi() || binop.rightValue().isPhi()) {
+ return false;
+ }
+ LogicalBinop leftDef = binop.leftValue().getDefinition().asLogicalBinop();
+ LogicalBinop rightDef = binop.rightValue().getDefinition().asLogicalBinop();
+ if (leftDef == null
+ || rightDef == null
+ || (leftDef.getClass() != rightDef.getClass())
+ || (leftDef.getNumericType() != rightDef.getNumericType())) {
+ return false;
+ }
+ // These optimizations were implemented mostly to deal with Compose specific bit patterns.
+ if (leftDef.isAnd() || leftDef.isOr()) {
+ return andOrOnCommonInputSimplification(
+ iterator, binop, leftDef, rightDef, binopDescriptor, code);
+ }
+ if (leftDef.isShl() || leftDef.isShr() || leftDef.isUshr()) {
+ return shiftOnCommonValueSharing(iterator, binop, leftDef, rightDef, binopDescriptor, code);
+ }
+ return false;
+ }
+
+ private boolean andOrOnCommonInputSimplification(
+ InstructionListIterator iterator,
+ Instruction binop,
+ LogicalBinop leftDef,
+ LogicalBinop rightDef,
+ BinopDescriptor binopDescriptor,
+ IRCode code) {
+ // For all permutations of & and |, represented by &| and |&.
+ // (x &| a) |& (x &| b) => (a |& b) &| x.
+ // a |& b will be simplified into a new constant if both constant.
+ Value x, a, b;
+ if (leftDef.leftValue() == rightDef.leftValue()) {
+ x = leftDef.leftValue();
+ a = leftDef.rightValue();
+ b = rightDef.rightValue();
+ } else if (leftDef.leftValue() == rightDef.rightValue()) {
+ x = leftDef.leftValue();
+ a = leftDef.rightValue();
+ b = rightDef.leftValue();
+ } else if (leftDef.rightValue() == rightDef.leftValue()) {
+ x = leftDef.rightValue();
+ a = leftDef.leftValue();
+ b = rightDef.rightValue();
+ } else if (leftDef.rightValue() == rightDef.rightValue()) {
+ x = leftDef.rightValue();
+ a = leftDef.leftValue();
+ b = rightDef.leftValue();
+ } else {
+ return false;
+ }
+
+ rewriteIntoTwoSuccessiveBinops(
+ iterator,
+ binop.getPosition(),
+ binopDescriptor,
+ descriptors.get(leftDef.getClass()),
+ a,
+ b,
+ x,
+ code);
+ return true;
+ }
+
+ private boolean shiftOnCommonValueSharing(
+ InstructionListIterator iterator,
+ Instruction binop,
+ LogicalBinop leftDef,
+ LogicalBinop rightDef,
+ BinopDescriptor binopDescriptor,
+ IRCode code) {
+ // For all permutations of & and |, represented by &|, and any shift operation.
+ // (x shift: val) &| (y shift: val) => (x &| y) shift: val.
+ // x |& y will be simplified into a new constant if both constant.
+ ConstNumber constLeft = getConstNumber(leftDef.rightValue());
+ if (constLeft != null) {
+ // val is a constant.
+ ConstNumber constRight = getConstNumber(rightDef.rightValue());
+ if (constRight == null) {
+ return false;
+ }
+ if (constRight.getRawValue() != constLeft.getRawValue()) {
+ return false;
+ }
+ } else {
+ // val is not constant.
+ if (leftDef.rightValue() != rightDef.rightValue()) {
+ return false;
+ }
+ }
+
+ rewriteIntoTwoSuccessiveBinops(
+ iterator,
+ binop.getPosition(),
+ binopDescriptor,
+ descriptors.get(leftDef.getClass()),
+ leftDef.leftValue(),
+ rightDef.leftValue(),
+ leftDef.rightValue(),
+ code);
+ return true;
+ }
+
+ private void rewriteIntoTwoSuccessiveBinops(
+ InstructionListIterator iterator,
+ Position position,
+ BinopDescriptor firstBinop,
+ BinopDescriptor secondBinop,
+ Value firstLeft,
+ Value firstRight,
+ Value secondRight,
+ IRCode code) {
+ // This creates something along the lines of:
+ // `(firstLeft firstBinop: firstRight) secondBinop: secondOther`.
+ ConstNumber constA = getConstNumber(firstLeft);
+ if (constA != null) {
+ ConstNumber constB = getConstNumber(firstRight);
+ if (constB != null) {
+ rewriteIntoConstThenBinop(
+ iterator, firstBinop, secondBinop, constA, constB, secondRight, true, code);
+ return;
+ }
+ }
+ Binop newFirstBinop = instantiateBinop(code, firstLeft, firstRight, firstBinop);
+ newFirstBinop.setPosition(position);
+ iterator.previous();
+ iterator.add(newFirstBinop);
+ iterator.next();
+ replaceBinop(iterator, code, newFirstBinop.outValue(), secondRight, secondBinop);
+ iterator.previous();
+ }
+
+ private void rewriteIntoConstThenBinop(
+ InstructionListIterator iterator,
+ BinopDescriptor firstBinop,
+ BinopDescriptor secondBinop,
+ ConstNumber firstLeft,
+ ConstNumber firstRight,
+ Value secondOther,
+ boolean newConstFlowsIntoLeft,
+ IRCode code) {
+ Value firstOutValue = insertNewConstNumber(code, iterator, firstLeft, firstRight, firstBinop);
+ replaceBinop(
+ iterator,
+ code,
+ newConstFlowsIntoLeft ? firstOutValue : secondOther,
+ newConstFlowsIntoLeft ? secondOther : firstOutValue,
+ secondBinop);
+ }
+
private void replaceBinop(
InstructionListIterator iterator,
IRCode code,
@@ -365,11 +338,8 @@
BinopDescriptor binopDescriptor) {
Binop newBinop = instantiateBinop(code, left, right, binopDescriptor);
iterator.replaceCurrentInstruction(newBinop);
- // We need to reset the iterator state after replaceCurrentInstruction so that Iterator#remove()
- // can work in identityAbsorbingSimplification by calling previous then next.
+ // We need to reset the iterator state to process the new instruction(s).
iterator.previous();
- iterator.next();
- identityAbsorbingSimplification(iterator, newBinop, binopDescriptor);
}
private Binop instantiateBinop(IRCode code, Value left, Value right, BinopDescriptor descriptor) {
@@ -379,7 +349,7 @@
return descriptor.instantiate(numericType, newValue, left, right);
}
- private Value addNewConstNumber(
+ private Value insertNewConstNumber(
IRCode code,
InstructionListIterator iterator,
ConstNumber left,
@@ -400,30 +370,46 @@
}
private boolean identityAbsorbingSimplification(
- InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor) {
+ InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) {
ConstNumber constNumber = getConstNumber(binop.leftValue());
if (constNumber != null) {
+ boolean isBooleanValue = binop.outValue().knownToBeBoolean();
if (simplify(
binop,
iterator,
constNumber,
- binopDescriptor.leftIdentity,
+ binopDescriptor.leftIdentity(isBooleanValue),
binop.rightValue(),
- binopDescriptor.leftAbsorbing,
+ binopDescriptor.leftAbsorbing(isBooleanValue),
binop.leftValue())) {
return true;
}
}
constNumber = getConstNumber(binop.rightValue());
if (constNumber != null) {
- return simplify(
+ boolean isBooleanValue = binop.outValue().knownToBeBoolean();
+ if (simplify(
binop,
iterator,
constNumber,
- binopDescriptor.rightIdentity,
+ binopDescriptor.rightIdentity(isBooleanValue),
binop.leftValue(),
- binopDescriptor.rightAbsorbing,
- binop.rightValue());
+ binopDescriptor.rightAbsorbing(isBooleanValue),
+ binop.rightValue())) {
+ return true;
+ }
+ }
+ if (binop.leftValue() == binop.rightValue()) {
+ if (binop.isXor() || binop.isSub()) {
+ // a ^ a => 0, a - a => 0
+ ConstNumber zero = new ConstNumber(code.createValue(binop.outValue().getType()), 0);
+ iterator.replaceCurrentInstruction(zero);
+ } else if (binop.isAnd() || binop.isOr()) {
+ // a & a => a, a | a => a.
+ binop.outValue().replaceUsers(binop.leftValue());
+ iterator.remove();
+ }
+ return true;
}
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index d28ac39..97f2d27 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -43,7 +43,8 @@
if (appView.options().isGeneratingClassFiles()) {
return NonEmptyCfInstructionDesugaringCollection.createForCfToCfNonDesugar(appView);
}
- return NonEmptyCfInstructionDesugaringCollection.createForCfToDexNonDesugar(appView);
+ return NonEmptyCfInstructionDesugaringCollection.createForCfToDexNonDesugar(
+ appView, apiLevelCompute);
}
public static CfInstructionDesugaringCollection empty() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 485322e..407daa8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -74,6 +74,10 @@
if (alwaysThrowingInstructionDesugaring != null) {
desugarings.add(alwaysThrowingInstructionDesugaring);
}
+ if (appView.options().apiModelingOptions().enableOutliningOfMethods) {
+ assert appView.options().isGeneratingDex();
+ yieldingDesugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute));
+ }
if (appView.options().desugarState.isOff()) {
this.nestBasedAccessDesugaring = null;
this.recordRewriter = null;
@@ -100,9 +104,6 @@
if (disableDesugarer != null) {
desugarings.add(disableDesugarer);
}
- if (appView.options().apiModelingOptions().enableOutliningOfMethods) {
- yieldingDesugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute));
- }
if (appView.options().enableTryWithResourcesDesugaring()) {
desugarings.add(new TwrInstructionDesugaring(appView));
}
@@ -174,11 +175,12 @@
return desugaringCollection;
}
- static NonEmptyCfInstructionDesugaringCollection createForCfToDexNonDesugar(AppView<?> appView) {
+ static NonEmptyCfInstructionDesugaringCollection createForCfToDexNonDesugar(
+ AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
assert appView.options().desugarState.isOff();
assert appView.options().isGeneratingDex();
NonEmptyCfInstructionDesugaringCollection desugaringCollection =
- new NonEmptyCfInstructionDesugaringCollection(appView, noAndroidApiLevelCompute());
+ new NonEmptyCfInstructionDesugaringCollection(appView, apiLevelCompute);
desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
desugaringCollection.yieldingDesugarings.add(
new UnrepresentableInDexInstructionRemover(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
index e13fb7c..9f9738e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
@@ -24,6 +24,8 @@
public ApiLevelRange(
AndroidApiLevel apiLevelBelowOrEqual, AndroidApiLevel apiLevelGreaterOrEqual) {
+ assert apiLevelGreaterOrEqual == null
+ || apiLevelBelowOrEqual.isGreaterThanOrEqualTo(apiLevelGreaterOrEqual);
this.apiLevelBelowOrEqual = apiLevelBelowOrEqual;
this.apiLevelGreaterOrEqual = apiLevelGreaterOrEqual;
}
@@ -40,6 +42,15 @@
return apiLevelGreaterOrEqual != null;
}
+ public boolean overlap(ApiLevelRange other) {
+ AndroidApiLevel start =
+ apiLevelGreaterOrEqual == null ? AndroidApiLevel.B : apiLevelGreaterOrEqual;
+ AndroidApiLevel otherStart =
+ other.apiLevelGreaterOrEqual == null ? AndroidApiLevel.B : other.apiLevelGreaterOrEqual;
+ return start.isLessThanOrEqualTo(other.apiLevelBelowOrEqual)
+ && otherStart.isLessThanOrEqualTo(apiLevelBelowOrEqual);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -74,4 +85,13 @@
}
return apiLevelGreaterOrEqual.compareTo(other.apiLevelGreaterOrEqual);
}
+
+ @Override
+ public String toString() {
+ return "[ "
+ + (apiLevelGreaterOrEqual == null ? "B" : apiLevelGreaterOrEqual)
+ + " ; "
+ + apiLevelBelowOrEqual
+ + " ]";
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
index e3a9dff..80ca5b3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
-import com.google.common.collect.ImmutableMap;
import java.util.Map;
import java.util.Objects;
@@ -35,18 +34,6 @@
return emulatedMethods;
}
- public EmulatedInterfaceDescriptor merge(EmulatedInterfaceDescriptor other) {
- if (!rewrittenType.isIdenticalTo(other.getRewrittenType())) {
- throw new UnsupportedOperationException(
- "Emulated interface descriptor can only be merged on the same rewritten type.");
- }
- ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor> builder =
- ImmutableMap.builder();
- builder.putAll(getEmulatedMethods());
- builder.putAll(other.getEmulatedMethods());
- return new EmulatedInterfaceDescriptor(rewrittenType, builder.build());
- }
-
@Override
public Object[] toJsonStruct(
MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index f28dae7..d27ba9b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -362,8 +362,8 @@
emulatedVirtualRetargetThroughEmulatedInterface = ImmutableMap.builder();
private final ImmutableMap.Builder<DexMethod, DexMethod[]> apiGenericTypesConversion =
ImmutableMap.builder();
- private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
- new IdentityHashMap<>();
+ private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
+ ImmutableMap.builder();
private final LinkedHashMap<DexType, WrapperDescriptor> wrappers = new LinkedHashMap<>();
private final ImmutableMap.Builder<DexType, DexType> legacyBackport = ImmutableMap.builder();
private final ImmutableSet.Builder<DexType> dontRetarget = ImmutableSet.builder();
@@ -409,12 +409,8 @@
nonEmulatedVirtualRetarget.put(src, dest);
}
- public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor newDescriptor) {
- assert newDescriptor != null;
- EmulatedInterfaceDescriptor oldDescriptor = emulatedInterfaces.get(src);
- EmulatedInterfaceDescriptor mergedDescriptor =
- oldDescriptor == null ? newDescriptor : newDescriptor.merge(oldDescriptor);
- emulatedInterfaces.put(src, mergedDescriptor);
+ public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor descriptor) {
+ emulatedInterfaces.put(src, descriptor);
}
public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
@@ -491,7 +487,7 @@
emulatedVirtualRetarget.build(),
emulatedVirtualRetargetThroughEmulatedInterface.build(),
apiGenericTypesConversion.build(),
- ImmutableMap.copyOf(emulatedInterfaces),
+ emulatedInterfaces.build(),
wrappers,
legacyBackport.build(),
dontRetarget.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
index 46ec6ba..f15da9b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
@@ -56,7 +56,7 @@
public class MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter {
- static final int MACHINE_VERSION_NUMBER = 201;
+ static final int MACHINE_VERSION_NUMBER = 200;
private final DexItemFactory factory;
private final Map<String, String> packageMap = new TreeMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index c5c7348..843a915 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexApplication;
@@ -30,8 +31,10 @@
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
public class HumanToMachineSpecificationConverter {
@@ -63,6 +66,8 @@
Map<ApiLevelRange, MachineRewritingFlags> libraryFlags =
convertRewritingFlagMap(humanSpec.getLibraryFlags(), synthesizedPrefix, true, identifier);
+ checkDisjointEmulatedInterfaceFlags(commonFlags, programFlags, libraryFlags);
+
MultiAPILevelMachineDesugaredLibrarySpecification machineSpec =
new MultiAPILevelMachineDesugaredLibrarySpecification(
humanSpec.getOrigin(), machineTopLevelFlags, commonFlags, libraryFlags, programFlags);
@@ -70,6 +75,56 @@
return machineSpec;
}
+ // For backward compatibility, there can be only one emulated interface flag for a given type at
+ // a given API level.
+ private void checkDisjointEmulatedInterfaceFlags(
+ Map<ApiLevelRange, MachineRewritingFlags> commonFlags,
+ Map<ApiLevelRange, MachineRewritingFlags> programFlags,
+ Map<ApiLevelRange, MachineRewritingFlags> libraryFlags) {
+ Set<DexType> commonTypes = emulatedInterfaceTypes(commonFlags);
+ Set<DexType> programTypes = emulatedInterfaceTypes(programFlags);
+ Set<DexType> libraryTypes = emulatedInterfaceTypes(libraryFlags);
+ if (!Sets.intersection(commonTypes, programTypes).isEmpty()
+ || !Sets.intersection(commonTypes, libraryTypes).isEmpty()
+ || !Sets.intersection(libraryTypes, programTypes).isEmpty()) {
+ throw new CompilationError("Cannot have emulated interface split across flag types");
+ }
+ checkEmulatedInterfaceMap(commonFlags);
+ checkEmulatedInterfaceMap(programFlags);
+ checkEmulatedInterfaceMap(libraryFlags);
+ }
+
+ private void checkEmulatedInterfaceMap(Map<ApiLevelRange, MachineRewritingFlags> flagMap) {
+ Map<DexType, List<ApiLevelRange>> rangesForType = new IdentityHashMap<>();
+ flagMap.forEach(
+ (range, flags) ->
+ flags
+ .getEmulatedInterfaces()
+ .forEach(
+ (ei, descr) -> {
+ rangesForType.putIfAbsent(ei, new ArrayList<>());
+ rangesForType.get(ei).add(range);
+ }));
+ rangesForType.keySet().removeIf(t -> rangesForType.get(t).size() == 1);
+ rangesForType.forEach(
+ (type, ranges) -> {
+ for (ApiLevelRange range1 : ranges) {
+ for (ApiLevelRange range2 : ranges) {
+ if (!Objects.equals(range1, range2) && range1.overlap(range2)) {
+ throw new CompilationError(
+ "Unsupported Machine specification for " + type + " " + range1 + " " + range2);
+ }
+ }
+ }
+ });
+ }
+
+ private Set<DexType> emulatedInterfaceTypes(Map<ApiLevelRange, MachineRewritingFlags> flagMap) {
+ Set<DexType> types = Sets.newIdentityHashSet();
+ flagMap.forEach((range, flags) -> types.addAll(flags.getEmulatedInterfaces().keySet()));
+ return types;
+ }
+
private Map<ApiLevelRange, MachineRewritingFlags> convertRewritingFlagMap(
Map<ApiLevelRange, HumanRewritingFlags> libFlags,
String synthesizedPrefix,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index c1f203a..13a06ea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -44,6 +44,7 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
@@ -453,6 +454,10 @@
if (instanceGet.instructionMayHaveSideEffects(appView, context)) {
return false;
}
+ if (!AndroidApiLevelUtils.isApiSafeForReference(
+ instanceGet.getField().getType(), appView)) {
+ return false;
+ }
NewInstance newInstance = null;
if (instanceGet.object().isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
newInstance = instanceGet.object().getDefinition().asNewInstance();
@@ -476,6 +481,10 @@
if (staticGet.instructionMayHaveSideEffects(appView, context)) {
return false;
}
+ if (!AndroidApiLevelUtils.isApiSafeForReference(
+ staticGet.getField().getType(), appView)) {
+ return false;
+ }
if (!isReadOfEffectivelyFinalFieldOutsideInitializer(staticGet)
&& !isEffectivelyFinalField(staticGet)) {
return false;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index ee0ce51..b5b5299 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -65,6 +65,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
@@ -161,8 +162,13 @@
Collection<DexProgramClass> allClasses = classes;
classes = new ArrayList<>(allClasses.size());
for (DexProgramClass clazz : allClasses) {
- if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+ if (appView.getSyntheticItems().isGlobalSyntheticClassTransitive(clazz)) {
globalSyntheticClasses.add(clazz);
+ Consumer<DexProgramClass> globalSyntheticCreatedCallback =
+ appView.options().testing.globalSyntheticCreatedCallback;
+ if (globalSyntheticCreatedCallback != null) {
+ globalSyntheticCreatedCallback.accept(clazz);
+ }
} else {
classes.add(clazz);
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 61fd940..7f56b25 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -427,6 +427,26 @@
return isGlobalSyntheticClass(clazz.getType());
}
+ public boolean isGlobalSyntheticClassTransitive(DexProgramClass clazz) {
+ // Fast path the common case where the class is not synthetic at all.
+ if (!isSynthetic(clazz)) {
+ return false;
+ }
+ if (isGlobalSyntheticClass(clazz)) {
+ return true;
+ }
+ DexType type = clazz.getType();
+ for (SyntheticReference<?, ?, ?> reference : committed.getItems(type)) {
+ // Only a single context should exist for a globally derived synthetic, so return early.
+ return isGlobalSyntheticClass(reference.getContext().getSynthesizingContextType());
+ }
+ SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
+ if (definition != null) {
+ return isGlobalSyntheticClass(definition.getContext().getSynthesizingContextType());
+ }
+ return false;
+ }
+
private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
if (references == null) {
return false;
@@ -1017,6 +1037,8 @@
Consumer<DexProgramClass> globalSyntheticCreatedCallback =
appView.options().testing.globalSyntheticCreatedCallback;
if (globalSyntheticCreatedCallback != null) {
+ // These are also reported in the writer to ensure transitive classes are reported too.
+ // However, we keep the test reporting here too to fail fast on direct globals.
globalSyntheticCreatedCallback.accept(globalSynthetic);
}
addGlobalContexts(globalSynthetic.getType(), contexts);
@@ -1046,6 +1068,7 @@
SyntheticKind kind = kindSelector.select(naming);
DexType type =
SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), appView);
+
SyntheticProgramClassBuilder classBuilder =
new SyntheticProgramClassBuilder(type, kind, outerContext, appView.dexItemFactory());
DexProgramClass clazz =
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index b2d2536..05c0b45 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -44,7 +44,7 @@
Sv2(32),
T(33),
U(34),
- MASTER(35); // API level for master is tentative.
+ MAIN(35); // API level for main is tentative.
// When updating LATEST and a new version goes public, add a new api-versions.xml to third_party
// and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest. Together
@@ -54,7 +54,7 @@
public static final AndroidApiLevel API_DATABASE_LEVEL = LATEST;
- public static final AndroidApiLevel UNKNOWN = MASTER;
+ public static final AndroidApiLevel UNKNOWN = MAIN;
/** Constant used to signify some unknown min api when compiling platform. */
public static final int ANDROID_PLATFORM_CONSTANT = 10000;
@@ -106,7 +106,7 @@
case V40:
return AndroidApiLevel.R;
case V41:
- return AndroidApiLevel.MASTER;
+ return AndroidApiLevel.MAIN;
default:
throw new Unreachable();
}
@@ -186,7 +186,7 @@
case 34:
return U;
default:
- return MASTER;
+ return MAIN;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index f448ee9..0e53313 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -117,6 +117,25 @@
return apiLevelOfOriginal.max(apiLevel).isLessThanOrEqualTo(options.getMinApiLevel()).isTrue();
}
+ public static boolean isApiSafeForReference(DexType type, AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexType baseType = type.toBaseType(dexItemFactory);
+ if (baseType.isPrimitiveType()) {
+ return true;
+ }
+ DexClass baseClass = appView.definitionFor(baseType);
+ if (baseClass == null) {
+ // This could be a library class that is only available on newer api levels.
+ return false;
+ }
+ if (!baseClass.isLibraryClass()) {
+ // Program and classpath classes are not api level dependent.
+ return true;
+ }
+ LibraryClass baseLibraryClass = baseClass.asLibraryClass();
+ return isApiSafeForReference(baseLibraryClass, appView);
+ }
+
public static boolean isApiSafeForReference(LibraryDefinition definition, AppView<?> appView) {
return isApiSafeForReference(
definition, appView.apiLevelCompute(), appView.options(), appView.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index f96774a..dc62cf4 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -66,8 +66,8 @@
public static DexVersion getDexVersion(AndroidApiLevel androidApiLevel) {
switch (androidApiLevel) {
- // MASTER is an unknown higher api version we therefore choose the highest known version.
- case MASTER:
+ // MAIN is an unknown higher api version we therefore choose the highest known version.
+ case MAIN:
case U:
case T:
case Sv2:
diff --git a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
index 1428459..4fd254a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.Version;
+import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
@@ -203,12 +204,28 @@
public void finished(AppView<?> appView) {
Map<DexType, Set<DexType>> globalsToContexts =
appView.getSyntheticItems().getFinalGlobalSyntheticContexts(appView);
+ // The global synthetics generator is generating the world of globals, thus no contexts exist.
+ if (appView.options().tool.equals(Tool.GlobalSyntheticsGenerator)) {
+ assert globalsToContexts.isEmpty();
+ GlobalsFileBuilder builder = new GlobalsFileBuilder(getKind());
+ globalToBytes.forEach(
+ (globalType, globalBytes) -> {
+ builder.addGlobalSynthetic(globalType.toDescriptorString(), globalBytes);
+ });
+ try {
+ clientConsumer.accept(ByteDataView.of(builder.build()), null, appView.reporter());
+ } catch (IOException e) {
+ appView.reporter().error(new ExceptionDiagnostic(e));
+ }
+ clientConsumer.finished(appView.reporter());
+ return;
+ }
+ // Otherwise, there must be at least one context for any global.
Map<DexType, Set<DexType>> contextToGlobals = new IdentityHashMap<>();
for (DexType globalType : globalToBytes.keySet()) {
// It would be good to assert that the global is a synthetic type, but the naming-lens
// is not applied to SyntheticItems in AppView.
Set<DexType> contexts = globalsToContexts.get(globalType);
- // TODO(b/231598779): Contexts should never be null once fixed for records.
assert contexts != null;
assert !contexts.isEmpty();
for (DexType contextType : contexts) {
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
index a1bb8a5..a583e5f 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -84,7 +84,9 @@
mappedPositions);
}
- assert !mappedPositions.isEmpty() || dexCode.instructions.length == 1;
+ assert !mappedPositions.isEmpty()
+ || dexCode.instructions.length == 1
+ || !dexCode.hasThrowingInstructions();
pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
return mappedPositions;
}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index 35b4218..7bdbaa8 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -291,6 +291,7 @@
originalType)) {
assert appView.options().lineNumberOptimization.isOff()
|| hasAtMostOnePosition(appView, definition)
+ || !hasThrowingInstructions(definition)
|| appView.isCfByteCodePassThrough(definition);
return this;
}
@@ -624,6 +625,11 @@
return code.isDexCode() && code.asDexCode().instructions.length == 1;
}
+ private boolean hasThrowingInstructions(DexEncodedMethod definition) {
+ Code code = definition.getCode();
+ return code.isDexCode() && code.asDexCode().hasThrowingInstructions();
+ }
+
private ClassNaming.Builder getBuilder() {
if (builder == null) {
builder =
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
deleted file mode 100644
index 775a308..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
+++ /dev/null
@@ -1,40 +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.apiusagesample;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.ClassFileResourceProvider;
-import com.android.tools.r8.DirectoryClassFileProvider;
-import com.android.tools.r8.ProgramResource;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class CachingArchiveClassFileProvider extends ArchiveClassFileProvider {
-
- private ConcurrentHashMap<String, ProgramResource> resources = new ConcurrentHashMap<>();
-
- private CachingArchiveClassFileProvider(Path archive) throws IOException {
- super(archive);
- }
-
- @Override
- public ProgramResource getProgramResource(String descriptor) {
- return resources.computeIfAbsent(descriptor, super::getProgramResource);
- }
-
- public static ClassFileResourceProvider getProvider(Path entry)
- throws IOException {
- if (Files.isRegularFile(entry)) {
- return new CachingArchiveClassFileProvider(entry);
- } else if (Files.isDirectory(entry)) {
- return DirectoryClassFileProvider.fromDirectory(entry);
- } else {
- throw new FileNotFoundException(entry.toString());
- }
- }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
deleted file mode 100644
index 80a2845..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ /dev/null
@@ -1,628 +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.apiusagesample;
-
-import com.android.tools.r8.ArchiveProgramResourceProvider;
-import com.android.tools.r8.AssertionsConfiguration;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.D8;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DesugarGraphConsumer;
-import com.android.tools.r8.DexFilePerClassFileConsumer;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProgramResource;
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.StringUtils;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class D8ApiUsageSample {
-
- private static final Origin origin =
- new Origin(Origin.root()) {
- @Override
- public String part() {
- return "D8ApiUsageSample";
- }
- };
-
- private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
-
- /**
- * Example invocation:
- *
- * <pre>
- * java -jar d8-api-uses.jar \
- * --output path/to/output/dir \
- * --min-api minApiLevel \
- * --lib path/to/library.jar \
- * --classpath path/to/classpath.jar \
- * path/to/input{1,2,3}.{jar,class}
- * </pre>
- */
- public static void main(String[] args) {
- // Parse arguments with the commandline parser to make use of its API.
- D8Command.Builder cmd = D8Command.parse(args, origin);
- CompilationMode mode = cmd.getMode();
- Path temp = cmd.getOutputPath();
- int minApiLevel = cmd.getMinApiLevel();
- // The Builder API does not provide access to the concrete paths
- // (everything is put into providers) so manually parse them here.
- List<Path> libraries = new ArrayList<>(1);
- List<Path> classpath = new ArrayList<>(args.length);
- List<Path> mainDexList = new ArrayList<>(1);
- List<Path> mainDexRules = new ArrayList<>(1);
- List<Path> inputs = new ArrayList<>(args.length);
- for (int i = 0; i < args.length; i++) {
- if (args[i].equals("--lib")) {
- libraries.add(Paths.get(args[++i]));
- } else if (args[i].equals("--classpath")) {
- classpath.add(Paths.get(args[++i]));
- } else if (args[i].equals("--main-dex-list")) {
- mainDexList.add(Paths.get(args[++i]));
- } else if (args[i].equals("--main-dex-rules")) {
- mainDexRules.add(Paths.get(args[++i]));
- } else if (isArchive(args[i]) || isClassFile(args[i])) {
- inputs.add(Paths.get(args[i]));
- }
- }
- if (!Files.exists(temp) || !Files.isDirectory(temp)) {
- throw new RuntimeException("Must supply a temp/output directory");
- }
- if (inputs.isEmpty()) {
- throw new RuntimeException("Must supply program inputs");
- }
- if (classpath.isEmpty()) {
- throw new RuntimeException("Must supply classpath inputs");
- }
- if (libraries.isEmpty()) {
- throw new RuntimeException("Must supply library inputs");
- }
- if (mainDexList.isEmpty()) {
- throw new RuntimeException("Must supply main-dex-list inputs");
- }
- if (mainDexRules.isEmpty()) {
- throw new RuntimeException("Must supply main-dex-rules inputs");
- }
-
- useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
- useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
- useProgramData(minApiLevel, libraries, classpath, inputs);
- useProgramResourceProvider(minApiLevel, libraries, classpath, inputs);
- useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
- useMainDexListFiles(minApiLevel, libraries, classpath, inputs, mainDexList);
- useMainDexClasses(minApiLevel, libraries, classpath, inputs, mainDexList);
- useMainDexRulesFiles(minApiLevel, libraries, classpath, inputs, mainDexRules);
- useMainDexRules(minApiLevel, libraries, classpath, inputs, mainDexRules);
- useAssertionConfig(minApiLevel, libraries, classpath, inputs);
- useVArgVariants(minApiLevel, libraries, classpath, inputs, mainDexList);
- incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
- }
-
- // Check API support for compiling Java class-files from the file system.
- private static void useProgramFileList(
- CompilationMode mode,
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMode(mode)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Check API support for compiling Java class-files from byte content.
- private static void useProgramData(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath);
- for (ClassFileContent classfile : readClassFiles(inputs)) {
- builder.addClassProgramData(classfile.data, classfile.origin);
- }
- for (Path input : inputs) {
- if (isDexFile(input)) {
- builder.addDexProgramData(Files.readAllBytes(input), new PathOrigin(input));
- }
- }
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- // Check API support for compiling Java class-files from a program provider abstraction.
- private static void useProgramResourceProvider(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath);
- for (Path input : inputs) {
- if (isArchive(input)) {
- builder.addProgramResourceProvider(
- ArchiveProgramResourceProvider.fromArchive(
- input, ArchiveProgramResourceProvider::includeClassFileEntries));
- } else if (isClassFile(input)) {
- builder.addProgramResourceProvider(
- new ProgramResourceProvider() {
- @Override
- public Collection<ProgramResource> getProgramResources() throws ResourceException {
- return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
- }
- });
- } else if (isDexFile(input)) {
- builder.addProgramResourceProvider(
- new ProgramResourceProvider() {
- @Override
- public Collection<ProgramResource> getProgramResources() throws ResourceException {
- return Collections.singleton(ProgramResource.fromFile(Kind.DEX, input));
- }
- });
- }
- }
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useLibraryAndClasspathProvider(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addProgramFiles(inputs);
- for (Path library : libraries) {
- builder.addLibraryResourceProvider(CachingArchiveClassFileProvider.getProvider(library));
- }
- for (Path path : classpath) {
- builder.addClasspathResourceProvider(CachingArchiveClassFileProvider.getProvider(path));
- }
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useMainDexListFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs,
- Collection<Path> mainDexList) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .addMainDexListFiles(mainDexList)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useMainDexClasses(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs,
- Collection<Path> mainDexList) {
- try {
- List<String> mainDexClasses = new ArrayList<>(1);
- for (Path path : mainDexList) {
- for (String line : Files.readAllLines(path)) {
- String entry = line.trim();
- if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
- continue;
- }
- mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
- }
- }
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .addMainDexClasses(mainDexClasses)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useMainDexRulesFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs,
- Collection<Path> mainDexRules) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .addMainDexRulesFiles(mainDexRules)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useMainDexRules(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs,
- Collection<Path> mainDexRulesFiles) {
- try {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs);
- for (Path mainDexRulesFile : mainDexRulesFiles) {
- builder.addMainDexRules(
- Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
- }
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useAssertionConfig(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
- .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setCompileTimeEnable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setPassthrough()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setCompileTimeDisable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setCompileTimeEnable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setPassthrough()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setCompileTimeDisable()
- .build())
- .addAssertionsConfiguration(
- AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
- .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
- .addAssertionsConfiguration(
- AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Check API support for all the varg variants.
- private static void useVArgVariants(
- int minApiLevel,
- List<Path> libraries,
- List<Path> classpath,
- List<Path> inputs,
- List<Path> mainDexList) {
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries.get(0))
- .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
- .addClasspathFiles(classpath.get(0))
- .addClasspathFiles(classpath.stream().skip(1).toArray(Path[]::new))
- .addProgramFiles(inputs.get(0))
- .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
- .addMainDexListFiles(mainDexList.get(0))
- .addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void incrementalCompileAndMerge(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- // Compile and merge via index intermediates.
- mergeIntermediates(
- minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
- // Compile and merge via per-classfile intermediates.
- mergeIntermediates(
- minApiLevel, compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
- }
-
- private static Collection<byte[]> compileToIndexedIntermediates(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setIntermediate(true)
- .setProgramConsumer(consumer)
- .addClasspathFiles(classpath)
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .setDisableDesugaring(false)
- .setDesugarGraphConsumer(new MyDesugarGraphConsumer())
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- return consumer.bytes;
- }
-
- private static Collection<byte[]> compileToPerClassFileIntermediates(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> classpath,
- Collection<Path> inputs) {
- PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
- try {
- D8.run(
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(consumer)
- .addLibraryFiles(libraries)
- .addClasspathFiles(classpath)
- .addProgramFiles(inputs)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- return consumer.bytes;
- }
-
- private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
- D8Command.Builder builder =
- D8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .setDisableDesugaring(true);
- for (byte[] intermediate : intermediates) {
- builder.addDexProgramData(intermediate, Origin.unknown());
- }
- try {
- D8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected merging error", e);
- }
- }
-
- // Helpers for tests.
- // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
- // rely on it.
-
- private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
- List<ClassFileContent> classfiles = new ArrayList<>();
- for (Path file : files) {
- if (isArchive(file)) {
- Origin zipOrigin = new PathOrigin(file);
- ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
- ZipEntry entry;
- while (null != (entry = zip.getNextEntry())) {
- String name = entry.getName();
- if (isClassFile(name)) {
- Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
- classfiles.add(new ClassFileContent(origin, readBytes(zip)));
- }
- }
- } else if (isClassFile(file)) {
- classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
- }
- }
- return classfiles;
- }
-
- private static byte[] readBytes(InputStream stream) throws IOException {
- try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
- byte[] buffer = new byte[0xffff];
- for (int length; (length = stream.read(buffer)) != -1; ) {
- bytes.write(buffer, 0, length);
- }
- return bytes.toByteArray();
- }
- }
-
- private static boolean isClassFile(Path file) {
- return isClassFile(file.toString());
- }
-
- private static boolean isClassFile(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".class");
- }
-
- private static boolean isDexFile(Path file) {
- return isDexFile(file.toString());
- }
-
- private static boolean isDexFile(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".dex");
- }
-
- private static boolean isArchive(Path file) {
- return isArchive(file.toString());
- }
-
- private static boolean isArchive(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".zip") || file.endsWith(".jar");
- }
-
- private static class ClassFileContent {
- final Origin origin;
- final byte[] data;
-
- public ClassFileContent(Origin origin, byte[] data) {
- this.origin = origin;
- this.data = data;
- }
- }
-
- private static class IndexIntermediatesConsumer implements DexIndexedConsumer {
-
- List<byte[]> bytes = new ArrayList<>();
-
- @Override
- public synchronized void accept(
- int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
- bytes.add(data);
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {}
- }
-
- private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
-
- List<byte[]> bytes = new ArrayList<>();
-
- @Override
- public synchronized void accept(
- String primaryClassDescriptor,
- byte[] data,
- Set<String> descriptors,
- DiagnosticsHandler handler) {
- bytes.add(data);
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {}
- }
-
- private static class EnsureOutputConsumer implements DexIndexedConsumer {
- boolean hasOutput = false;
-
- @Override
- public synchronized void accept(
- int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
- hasOutput = true;
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {
- if (!hasOutput) {
- handler.error(new StringDiagnostic("Expected to produce output but had none"));
- }
- }
- }
-
- private static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
-
- @Override
- public void accept(Origin dependent, Origin dependency) {
- }
-
- public void finished() {
-
- }
- }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
deleted file mode 100644
index d523a53..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
+++ /dev/null
@@ -1,89 +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.apiusagesample;
-
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.position.Position;
-import com.android.tools.r8.position.TextPosition;
-import com.android.tools.r8.position.TextRange;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-class D8DiagnosticsHandler implements DiagnosticsHandler {
-
- public D8DiagnosticsHandler() {
- }
-
- public static Origin getOrigin(Path root, Path entry) {
- if (Files.isRegularFile(root)) {
- return new ArchiveEntryOrigin(entry.toString(), new PathOrigin(root));
- } else {
- return new PathOrigin(root.resolve(entry.toString()));
- }
- }
-
- @Override
- public void error(Diagnostic error) {
- convertToMessage(error);
- }
-
- @Override
- public void warning(Diagnostic warning) {
- if (warning instanceof InterfaceDesugarMissingTypeDiagnostic) {
- desugarInterfaceMethodInfo((InterfaceDesugarMissingTypeDiagnostic) warning);
- } else {
- convertToMessage(warning);
- }
- }
-
- @Override
- public void info(Diagnostic info) {
- convertToMessage(info);
- }
-
- void desugarInterfaceMethodInfo(InterfaceDesugarMissingTypeDiagnostic info) {
- System.out.println("desugar is missing: " + info.getMissingType().toString());
- System.out.println(" used from: " + info.getContextType().toString());
- convertToMessage(info);
- }
-
- protected void convertToMessage(Diagnostic diagnostic) {
- String textMessage = diagnostic.getDiagnosticMessage();
-
- Origin origin = diagnostic.getOrigin();
- Position positionInOrigin = diagnostic.getPosition();
- String position;
- if (origin instanceof PathOrigin) {
- Path originFile = ((PathOrigin) origin).getPath();
- if (positionInOrigin instanceof TextRange) {
- TextRange textRange = (TextRange) positionInOrigin;
- position = originFile + ": "
- + textRange.getStart().getLine() + "," + textRange.getStart().getColumn()
- + " - " + textRange.getEnd().getLine() + "," + textRange.getEnd().getColumn();
- } else if (positionInOrigin instanceof TextPosition) {
- TextPosition textPosition = (TextPosition) positionInOrigin;
- position = originFile + ": "
- + textPosition.getLine() + "," + textPosition.getColumn();
- } else {
- position = originFile.toString();
- }
- } else if (origin.parent() instanceof PathOrigin) {
- Path originFile = ((PathOrigin) origin.parent()).getPath();
- position = originFile.toString();
- } else {
- position = "UNKNOWN";
- if (origin != Origin.unknown()) {
- textMessage = origin.toString() + ": " + textMessage;
- }
- }
-
- System.out.println(position + ": " + textMessage);
- }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
deleted file mode 100644
index 521ccba..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ /dev/null
@@ -1,612 +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.apiusagesample;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.ArchiveProgramResourceProvider;
-import com.android.tools.r8.AssertionsConfiguration;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProgramResource;
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.Version;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.StringUtils;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class R8ApiUsageSample {
-
- private static final Origin origin =
- new Origin(Origin.root()) {
- @Override
- public String part() {
- return "R8ApiUsageSample";
- }
- };
-
- private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
-
- /**
- * Example invocation:
- *
- * <pre>
- * java -jar r8-api-uses.jar \
- * --output path/to/output/dir \
- * --min-api minApiLevel \
- * --lib path/to/library.jar \
- * path/to/input{1,2,3}.{jar,class}
- * </pre>
- */
- public static void main(String[] args) {
- // Check version API
- checkVersionApi();
- // Parse arguments with the commandline parser to make use of its API.
- R8Command.Builder cmd = R8Command.parse(args, origin);
- CompilationMode mode = cmd.getMode();
- Path temp = cmd.getOutputPath();
- int minApiLevel = cmd.getMinApiLevel();
- // The Builder API does not provide access to the concrete paths
- // (everything is put into providers) so manually parse them here.
- List<Path> libraries = new ArrayList<>(1);
- List<Path> mainDexList = new ArrayList<>(1);
- List<Path> mainDexRules = new ArrayList<>(1);
- List<Path> pgConf = new ArrayList<>(1);
- List<Path> inputs = new ArrayList<>(args.length);
- for (int i = 0; i < args.length; i++) {
- if (args[i].equals("--lib")) {
- libraries.add(Paths.get(args[++i]));
- } else if (args[i].equals("--main-dex-list")) {
- mainDexList.add(Paths.get(args[++i]));
- } else if (args[i].equals("--main-dex-rules")) {
- mainDexRules.add(Paths.get(args[++i]));
- } else if (args[i].equals("--pg-conf")) {
- pgConf.add(Paths.get(args[++i]));
- } else if (isArchive(args[i]) || isClassFile(args[i])) {
- inputs.add(Paths.get(args[i]));
- }
- }
- if (!Files.exists(temp) || !Files.isDirectory(temp)) {
- throw new RuntimeException("Must supply a temp/output directory");
- }
- if (inputs.isEmpty()) {
- throw new RuntimeException("Must supply program inputs");
- }
- if (libraries.isEmpty()) {
- throw new RuntimeException("Must supply library inputs");
- }
- if (mainDexList.isEmpty()) {
- throw new RuntimeException("Must supply main-dex-list inputs");
- }
- if (mainDexRules.isEmpty()) {
- throw new RuntimeException("Must supply main-dex-rules inputs");
- }
- if (pgConf.isEmpty()) {
- throw new RuntimeException("Must supply pg-conf inputs");
- }
-
- useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
- useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
- useProgramData(minApiLevel, libraries, inputs);
- useProgramResourceProvider(minApiLevel, libraries, inputs);
- useLibraryResourceProvider(minApiLevel, libraries, inputs);
- useMainDexListFiles(minApiLevel, libraries, inputs, mainDexList);
- useMainDexClasses(minApiLevel, libraries, inputs, mainDexList);
- useMainDexRulesFiles(minApiLevel, libraries, inputs, mainDexRules);
- useMainDexRules(minApiLevel, libraries, inputs, mainDexRules);
- useProguardConfigFiles(minApiLevel, libraries, inputs, mainDexList, pgConf);
- useProguardConfigLines(minApiLevel, libraries, inputs, mainDexList, pgConf);
- useAssertionConfig(minApiLevel, libraries, inputs);
- useVArgVariants(minApiLevel, libraries, inputs, mainDexList, mainDexRules, pgConf);
- useProguardConfigConsumers(minApiLevel, libraries, inputs, pgConf);
- }
-
- private static class InMemoryStringConsumer implements StringConsumer {
- public String value = null;
-
- @Override
- public void accept(String string, DiagnosticsHandler handler) {
- value = string;
- }
- }
-
- private static void useProguardConfigConsumers(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
- InMemoryStringConsumer usageConsumer = new InMemoryStringConsumer();
- InMemoryStringConsumer seedsConsumer = new InMemoryStringConsumer();
- InMemoryStringConsumer configConsumer = new InMemoryStringConsumer();
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addProguardConfigurationFiles(pgConf)
- .setProguardUsageConsumer(usageConsumer)
- .setProguardSeedsConsumer(seedsConsumer)
- .setProguardConfigurationConsumer(configConsumer)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exception", e);
- }
- if (usageConsumer.value == null) {
- throw new RuntimeException("Expected usage info but had none");
- }
- if (seedsConsumer.value == null) {
- throw new RuntimeException("Expected seeds info but had none");
- }
- if (configConsumer.value == null) {
- throw new RuntimeException("Expected config info but had none");
- }
- }
-
- // Check API support for compiling Java class-files from the file system.
- private static void useProgramFileList(
- CompilationMode mode, int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMode(mode)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Check API support for compiling Java class-files from byte content.
- private static void useProgramData(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries);
- for (ClassFileContent classfile : readClassFiles(inputs)) {
- builder.addClassProgramData(classfile.data, classfile.origin);
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- // Check API support for compiling Java class-files from a program provider abstraction.
- private static void useProgramResourceProvider(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries);
- for (Path input : inputs) {
- if (isArchive(input)) {
- builder.addProgramResourceProvider(
- ArchiveProgramResourceProvider.fromArchive(
- input, ArchiveProgramResourceProvider::includeClassFileEntries));
- } else {
- builder.addProgramResourceProvider(
- new ProgramResourceProvider() {
- @Override
- public Collection<ProgramResource> getProgramResources() throws ResourceException {
- return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
- }
- });
- }
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useLibraryResourceProvider(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addProgramFiles(inputs);
- for (Path library : libraries) {
- builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useMainDexListFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexList) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexListFiles(mainDexList)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useMainDexClasses(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexList) {
- try {
- List<String> mainDexClasses = new ArrayList<>(1);
- for (Path path : mainDexList) {
- for (String line : Files.readAllLines(path)) {
- String entry = line.trim();
- if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
- continue;
- }
- mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
- }
- }
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexClasses(mainDexClasses)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useMainDexRulesFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexRules) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexRulesFiles(mainDexRules)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useMainDexRules(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexRulesFiles) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs);
- for (Path mainDexRulesFile : mainDexRulesFiles) {
- builder.addMainDexRules(
- Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useProguardConfigFiles(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexList,
- List<Path> pgConf) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexListFiles(mainDexList)
- .addProguardConfigurationFiles(pgConf)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- private static void useProguardConfigLines(
- int minApiLevel,
- Collection<Path> libraries,
- Collection<Path> inputs,
- Collection<Path> mainDexList,
- List<Path> pgConf) {
- try {
- R8Command.Builder builder =
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addMainDexListFiles(mainDexList);
- for (Path file : pgConf) {
- builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
- }
- R8.run(builder.build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- } catch (IOException e) {
- throw new RuntimeException("Unexpected IO exception", e);
- }
- }
-
- private static void useAssertionConfig(
- int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setDisableTreeShaking(true)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries)
- .addProgramFiles(inputs)
- .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
- .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setCompileTimeEnable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setPassthrough()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopePackage("com.android.tools.apiusagesample")
- .setCompileTimeDisable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setCompileTimeEnable()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setPassthrough()
- .build())
- .addAssertionsConfiguration(
- b ->
- b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
- .setCompileTimeDisable()
- .build())
- .addAssertionsConfiguration(
- AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
- .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
- .addAssertionsConfiguration(
- AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Check API support for all the varg variants.
- private static void useVArgVariants(
- int minApiLevel,
- List<Path> libraries,
- List<Path> inputs,
- List<Path> mainDexList,
- List<Path> mainDexRules,
- List<Path> pgConf) {
- try {
- R8.run(
- R8Command.builder(handler)
- .setMinApiLevel(minApiLevel)
- .setProgramConsumer(new EnsureOutputConsumer())
- .addLibraryFiles(libraries.get(0))
- .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
- .addProgramFiles(inputs.get(0))
- .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
- .addMainDexListFiles(mainDexList.get(0))
- .addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
- .addMainDexRulesFiles(mainDexRules.get(0))
- .addMainDexRulesFiles(mainDexRules.stream().skip(1).toArray(Path[]::new))
- .addProguardConfigurationFiles(pgConf.get(0))
- .addProguardConfigurationFiles(pgConf.stream().skip(1).toArray(Path[]::new))
- .build());
- } catch (CompilationFailedException e) {
- throw new RuntimeException("Unexpected compilation exceptions", e);
- }
- }
-
- // Helpers for tests.
- // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
- // rely on it.
-
- private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
- List<ClassFileContent> classfiles = new ArrayList<>();
- for (Path file : files) {
- if (isArchive(file)) {
- Origin zipOrigin = new PathOrigin(file);
- ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
- ZipEntry entry;
- while (null != (entry = zip.getNextEntry())) {
- String name = entry.getName();
- if (isClassFile(name)) {
- Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
- classfiles.add(new ClassFileContent(origin, readBytes(zip)));
- }
- }
- } else if (isClassFile(file)) {
- classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
- }
- }
- return classfiles;
- }
-
- private static byte[] readBytes(InputStream stream) throws IOException {
- try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
- byte[] buffer = new byte[0xffff];
- for (int length; (length = stream.read(buffer)) != -1; ) {
- bytes.write(buffer, 0, length);
- }
- return bytes.toByteArray();
- }
- }
-
- private static boolean isClassFile(Path file) {
- return isClassFile(file.toString());
- }
-
- private static boolean isClassFile(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".class");
- }
-
- private static boolean isArchive(Path file) {
- return isArchive(file.toString());
- }
-
- private static boolean isArchive(String file) {
- file = StringUtils.toLowerCase(file);
- return file.endsWith(".zip") || file.endsWith(".jar");
- }
-
- private static class ClassFileContent {
- final Origin origin;
- final byte[] data;
-
- public ClassFileContent(Origin origin, byte[] data) {
- this.origin = origin;
- this.data = data;
- }
- }
-
- private static class EnsureOutputConsumer implements DexIndexedConsumer {
- boolean hasOutput = false;
-
- @Override
- public synchronized void accept(
- int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
- hasOutput = true;
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {
- if (!hasOutput) {
- handler.error(new StringDiagnostic("Expected to produce output but had none"));
- }
- }
- }
-
- private static void checkVersionApi() {
- String labelValue;
- int labelAccess;
- try {
- Field field = Version.class.getDeclaredField("LABEL");
- labelAccess = field.getModifiers();
- labelValue = (String) field.get(Version.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- if (!Modifier.isPublic(labelAccess)
- || !Modifier.isStatic(labelAccess)
- || !Modifier.isFinal(labelAccess)) {
- throw new RuntimeException("Expected public static final LABEL");
- }
- if (labelValue.isEmpty()) {
- throw new RuntimeException("Expected LABEL constant");
- }
- if (Version.LABEL.isEmpty()) {
- throw new RuntimeException("Expected LABEL constant");
- }
- if (Version.getVersionString() == null) {
- throw new RuntimeException("Expected getVersionString API");
- }
- if (Version.getMajorVersion() < -1) {
- throw new RuntimeException("Expected getMajorVersion API");
- }
- if (Version.getMinorVersion() < -1) {
- throw new RuntimeException("Expected getMinorVersion API");
- }
- if (Version.getPatchVersion() < -1) {
- throw new RuntimeException("Expected getPatchVersion API");
- }
- if (Version.getPreReleaseString() == null && false) {
- throw new RuntimeException("Expected getPreReleaseString API");
- }
- if (Version.isDevelopmentVersion() && false) {
- throw new RuntimeException("Expected isDevelopmentVersion API");
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
deleted file mode 100644
index d660bed..0000000
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ /dev/null
@@ -1,111 +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;
-
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.FileUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class D8ApiBinaryCompatibilityTests extends TestBase {
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
- }
-
- public D8ApiBinaryCompatibilityTests(TestParameters parameters) {
- parameters.assertNoneRuntime();
- }
-
- @Test
- public void testCompatibility() throws IOException {
- Path jar = ToolHelper.API_SAMPLE_JAR;
- String main = "com.android.tools.apiusagesample.D8ApiUsageSample";
- int minApiLevel = AndroidApiLevel.K.getLevel();
-
- Path lib1 =
- Paths.get(
- ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
- "desugaringwithmissingclasslib1" + JAR_EXTENSION);
- Path lib2 =
- Paths.get(
- ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
- "desugaringwithmissingclasslib2" + JAR_EXTENSION);
- Path inputDir =
- Paths.get(
- ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR, "classes", "desugaringwithmissingclasstest1");
- List<Path> input =
- ImmutableList.of(
- inputDir.resolve("ImplementMethodsWithDefault.class"), inputDir.resolve("Main.class"));
-
- Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
- FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
-
- Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
- FileUtils.writeTextFile(mainDexRules, "# empty file");
-
- // It is important to place the api usage sample jar after the current classpath because we want
- // to find D8/R8 classes before the ones in the jar, otherwise renamed classes and fields cannot
- // be found.
- String classPath = System.getProperty("java.class.path") + File.pathSeparator + jar;
- List<String> command =
- ImmutableList.<String>builder()
- .addAll(
- ImmutableList.of(
- ToolHelper.getJavaExecutable(),
- "-cp",
- classPath,
- main,
- // Compiler arguments.
- "--output",
- temp.newFolder().getAbsolutePath(),
- "--min-api",
- Integer.toString(minApiLevel),
- "--main-dex-list",
- mainDexList.toString(),
- "--main-dex-rules",
- mainDexRules.toString(),
- "--lib",
- ToolHelper.getAndroidJar(AndroidApiLevel.getAndroidApiLevel(minApiLevel))
- .toString(),
- "--classpath",
- lib1.toString(),
- "--classpath",
- lib2.toString()))
- .addAll(input.stream().map(Path::toString).collect(Collectors.toList()))
- .build();
-
- ProcessBuilder builder = new ProcessBuilder(command);
- ProcessResult result = ToolHelper.runProcess(builder);
- assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
- Assert.assertEquals("", filterOutMainDexListWarnings(result.stdout));
- Assert.assertEquals("", result.stderr);
- }
-
- public static String filterOutMainDexListWarnings(String output) {
- StringBuilder builder = new StringBuilder();
- for (String line : output.split("\n")) {
- if (!line.contains("Unsupported usage of main-dex list")) {
- builder.append(line).append("\n");
- }
- }
- return builder.toString();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
deleted file mode 100644
index 67753da..0000000
--- a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
+++ /dev/null
@@ -1,87 +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;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.FileUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-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 R8ApiBinaryCompatibilityTests extends TestBase {
-
- static final Path JAR = ToolHelper.API_SAMPLE_JAR;
- static final String MAIN = "com.android.tools.apiusagesample.R8ApiUsageSample";
- static final AndroidApiLevel MIN_API = AndroidApiLevel.K;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
- }
-
- public R8ApiBinaryCompatibilityTests(TestParameters parameters) {
- parameters.assertNoneRuntime();
- }
-
- @Test
- public void testCompatibility() throws IOException {
- List<Path> inputs =
- ImmutableList.of(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "arithmetic.jar"));
-
- String keepMain = "-keep public class arithmetic.Arithmetic {\n"
- + " public static void main(java.lang.String[]);\n"
- + "}";
-
- Path pgConf = temp.getRoot().toPath().resolve("pg.conf");
- FileUtils.writeTextFile(pgConf, keepMain);
-
- Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
- FileUtils.writeTextFile(mainDexRules, keepMain);
-
- Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
- FileUtils.writeTextFile(mainDexList, "arithmetic/Arithmetic.class");
-
- List<String> command =
- ImmutableList.<String>builder()
- .addAll(
- ImmutableList.of(
- ToolHelper.getJavaExecutable(),
- "-cp",
- JAR.toString() + File.pathSeparator + System.getProperty("java.class.path"),
- MAIN,
- // Compiler arguments.
- "--output",
- temp.newFolder().toString(),
- "--min-api",
- Integer.toString(MIN_API.getLevel()),
- "--pg-conf",
- pgConf.toString(),
- "--main-dex-rules",
- mainDexRules.toString(),
- "--main-dex-list",
- mainDexList.toString(),
- "--lib",
- ToolHelper.getAndroidJar(MIN_API).toString()))
- .addAll(inputs.stream().map(Path::toString).collect(Collectors.toList()))
- .build();
-
- ProcessBuilder builder = new ProcessBuilder(command);
- ProcessResult result = ToolHelper.runProcess(builder);
- assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
- assertEquals("", D8ApiBinaryCompatibilityTests.filterOutMainDexListWarnings(result.stdout));
- assertEquals("", result.stderr);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/androidapi/AndroidJarMasterTest.java b/src/test/java/com/android/tools/r8/androidapi/AndroidJarMasterTest.java
index 6d9c804..0b0b108 100644
--- a/src/test/java/com/android/tools/r8/androidapi/AndroidJarMasterTest.java
+++ b/src/test/java/com/android/tools/r8/androidapi/AndroidJarMasterTest.java
@@ -30,7 +30,7 @@
@Test
public void testD8() throws Exception {
testForD8(parameters.getBackend())
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MASTER))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MAIN))
.addInnerClasses(getClass())
.setMinApi(parameters)
.run(parameters.getRuntime(), TestClass.class)
@@ -41,7 +41,7 @@
public void testR8() throws Exception {
parameters.assumeR8TestParameters();
testForR8(parameters.getBackend())
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MASTER))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MAIN))
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java
new file mode 100644
index 0000000..c463f1b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2024, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelConstantCanonicalizationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ int sdkInt = parameters.isCfRuntime() ? 0 : parameters.getApiLevel().getLevel();
+ List<String> outputLines = new ArrayList<>();
+ if (sdkInt < 22) {
+ outputLines.add("No cigar!");
+ } else if (sdkInt == 22) {
+ outputLines.add("apiLevel22");
+ } else {
+ outputLines.add("apiLevel23");
+ }
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class, Version.class)
+ .addLibraryClasses(ApiLevel22.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-assumevalues class " + Version.class.getTypeName() + " {",
+ " public static int getSdkInt(int) return " + sdkInt + "..42;",
+ "}")
+ .apply(setMockApiLevelForClass(ApiLevel22.class, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ List<InstructionSubject> filteredInstructions =
+ inspector
+ .clazz(A.class)
+ .uniqueMethodWithOriginalName("m")
+ .streamInstructions()
+ .filter(i -> i.isInstanceGet() | i.isIf())
+ .collect(Collectors.toList());
+ // The instance get can only be moved before the if, when the type of the field is
+ // safe to reference on all supported API levels.
+ if (sdkInt < 22) {
+ assertTrue(filteredInstructions.get(0).isIf());
+ assertTrue(filteredInstructions.get(1).isInstanceGet());
+ } else {
+ assertTrue(filteredInstructions.get(0).isInstanceGet());
+ assertTrue(filteredInstructions.get(1).isIf());
+ }
+ })
+ .addRunClasspathClasses(ApiLevel22.class)
+ .run(parameters.getRuntime(), Main.class, Integer.toString(sdkInt))
+ .assertSuccessWithOutputLines(outputLines);
+ }
+
+ public static class ApiLevel22 {
+
+ public void apiLevel22() {
+ System.out.println("apiLevel22");
+ }
+
+ public void apiLevel23() {
+ System.out.println("apiLevel23");
+ }
+ }
+
+ public static class A {
+
+ private final ApiLevel22 f;
+
+ A(int sdk) {
+ f = Version.getSdkInt(sdk) >= 22 ? new ApiLevel22() : null;
+ }
+
+ public void m(int sdk) {
+ if (sdk >= 23) {
+ if (f != null) {
+ f.apiLevel23();
+ }
+ } else if (sdk == 22) {
+ if (f != null) {
+ f.apiLevel22();
+ }
+ } else {
+ System.out.println("No cigar!");
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ int sdk = Integer.parseInt(args[0]);
+ A a = new A(sdk);
+ a.m(sdk);
+ }
+ }
+
+ public static class Version {
+
+ // -assumevalues ...
+ public static int getSdkInt(int sdk) {
+ return sdk;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
index 10b38c6..c1ed5f3 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
@@ -168,13 +168,8 @@
"foo",
Collections.emptyList(),
null));
- if (isR8 && parameters.isCfRuntime()) {
- verifyHelper.isOutlinedFromUntil(
- Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
- } else {
- verifyHelper.isOutlinedFromUntilAlsoForCf(
- Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
- }
+ verifyHelper.isOutlinedFromUntil(
+ Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
}
// Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
index d01a450..1ee2689 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
@@ -150,13 +150,8 @@
"foo",
Collections.emptyList(),
null));
- if (isR8 && parameters.isCfRuntime()) {
- verifyHelper.isOutlinedFromUntil(
- Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
- } else {
- verifyHelper.isOutlinedFromUntilAlsoForCf(
- Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
- }
+ verifyHelper.isOutlinedFromUntil(
+ Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
}
// Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
index 4c6763e..ae77d7b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
@@ -9,6 +9,7 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompilerBuilder;
@@ -17,6 +18,8 @@
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -38,12 +41,17 @@
}
private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ setupTestBuilderWithoutProgram(
+ testBuilder.addProgramClasses(Main.class).addAndroidBuildVersion(getApiLevelForRuntime()));
+ }
+
+ private void setupTestBuilderWithoutProgram(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
+ throws Exception {
testBuilder
+ .markAndroidBuildVersionAsActive(getApiLevelForRuntime())
.addLibraryClasses(LibraryClass.class, LibraryProvider.class)
.addDefaultRuntimeLibrary(parameters)
- .addProgramClasses(Main.class)
.setMinApi(parameters)
- .addAndroidBuildVersion(getApiLevelForRuntime())
.apply(setMockApiLevelForClass(LibraryProvider.class, AndroidApiLevel.B))
.apply(
setMockApiLevelForMethod(
@@ -78,6 +86,52 @@
}
@Test
+ public void testD8Cf() throws Exception {
+ parameters.assumeCfRuntime();
+ testForD8(parameters.getBackend())
+ .setMinApi(parameters)
+ .setMode(CompilationMode.DEBUG)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .inspect(
+ inspector ->
+ // Compiling to CF should never result in stubs and outlining for API modeling.
+ verifyThat(inspector, parameters, LibraryClass.class)
+ .hasNotCheckCastOutlinedFrom(getMainMethod()))
+ .applyIf(
+ addToBootClasspath(),
+ b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+ b -> b.addBootClasspathClasses(LibraryProvider.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8CfAndDexNoDesugaring() throws Exception {
+ parameters.assumeDexRuntime();
+ D8TestCompileResult compileResult =
+ testForD8(Backend.CF)
+ .setMinApi(parameters)
+ .setMode(CompilationMode.DEBUG)
+ .apply(this::setupTestBuilder)
+ .compile();
+ Path out = compileResult.writeToZip();
+ testForD8()
+ .disableDesugaring()
+ .setMode(CompilationMode.DEBUG)
+ .apply(this::setupTestBuilderWithoutProgram)
+ .addProgramFiles(out)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(
+ addToBootClasspath(),
+ b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+ b -> b.addBootClasspathClasses(LibraryProvider.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
public void testD8Debug() throws Exception {
parameters.assumeDexRuntime();
testForD8()
@@ -127,7 +181,11 @@
private void inspect(CodeInspector inspector) throws Exception {
verifyThat(inspector, parameters, LibraryClass.class)
- .hasCheckCastOutlinedFromUntil(Main.class.getMethod("main", String[].class), classApiLevel);
+ .hasCheckCastOutlinedFromUntil(getMainMethod(), classApiLevel);
+ }
+
+ private static Method getMainMethod() throws NoSuchMethodException {
+ return Main.class.getMethod("main", String[].class);
}
private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 33fe85c..c546154 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -138,27 +138,33 @@
TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
compilerBuilder.addOptionsModification(
options -> {
- options.apiModelingOptions().enableLibraryApiModeling = true;
- options.apiModelingOptions().enableStubbingOfClasses = true;
- // Our tests rely on us amending the library path with additional classes that are not
- // in the library.
- options.testing.globalSyntheticCreatedCallback = null;
+ if (options.isGeneratingDex()) {
+ options.apiModelingOptions().enableLibraryApiModeling = true;
+ options.apiModelingOptions().enableStubbingOfClasses = true;
+ // Our tests rely on us amending the library path with additional classes that are not
+ // in the library.
+ options.testing.globalSyntheticCreatedCallback = null;
+ }
});
}
public static void enableStubbingOfClasses(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
compilerBuilder.addOptionsModification(
options -> {
- options.apiModelingOptions().enableLibraryApiModeling = true;
- options.apiModelingOptions().enableStubbingOfClasses = true;
+ if (options.isGeneratingDex()) {
+ options.apiModelingOptions().enableLibraryApiModeling = true;
+ options.apiModelingOptions().enableStubbingOfClasses = true;
+ }
});
}
public static void enableOutliningOfMethods(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
compilerBuilder.addOptionsModification(
options -> {
- options.apiModelingOptions().enableLibraryApiModeling = true;
- options.apiModelingOptions().enableOutliningOfMethods = true;
+ if (options.isGeneratingDex()) {
+ options.apiModelingOptions().enableLibraryApiModeling = true;
+ options.apiModelingOptions().enableOutliningOfMethods = true;
+ }
});
}
@@ -496,14 +502,6 @@
}
}
- void isOutlinedFromUntilAlsoForCf(Executable method, AndroidApiLevel apiLevel) {
- if (parameters.getApiLevel().isLessThan(apiLevel)) {
- isOutlinedFrom(method);
- } else {
- isNotOutlinedFrom(method);
- }
- }
-
void isOutlinedFrom(Executable method) {
// Check that the call is in a synthetic class.
List<FoundMethodSubject> outlinedMethod =
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClInitMergeSuperTypeApiLevelTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClInitMergeSuperTypeApiLevelTest.java
index 164755b..f0de7ae 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClInitMergeSuperTypeApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClInitMergeSuperTypeApiLevelTest.java
@@ -6,10 +6,12 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -39,11 +41,14 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
+ private boolean canUseExecutable() {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
+ }
+
private TypeReference getMergeReferenceForApiLevel() {
- boolean canUseExecutable =
- parameters.isDexRuntime()
- && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
- return Reference.typeFromTypeName(typeName(canUseExecutable ? Executable.class : Object.class));
+ return Reference.typeFromTypeName(
+ typeName(canUseExecutable() ? Executable.class : Object.class));
}
@Test
@@ -59,17 +64,34 @@
inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
.compile()
.inspect(
inspector -> {
ClassSubject clazz = inspector.clazz(A.class);
assertThat(clazz, isPresent());
- TypeReference mergeTypeRef = getMergeReferenceForApiLevel();
- MethodSubject init = clazz.init(mergeTypeRef.getTypeName(), "int");
- assertThat(init, isPresent());
+
assertTrue(
clazz.allFields().stream()
- .anyMatch(f -> mergeTypeRef.equals(f.getFinalReference().getFieldType())));
+ .anyMatch(
+ f ->
+ f.getFinalReference()
+ .getFieldType()
+ .equals(getMergeReferenceForApiLevel())));
+
+ assertEquals(
+ canUseExecutable() ? 1 : 2,
+ clazz.allMethods(MethodSubject::isInstanceInitializer).size());
+ if (canUseExecutable()) {
+ MethodSubject constructorInit = clazz.init(Executable.class.getTypeName(), "int");
+ assertThat(constructorInit, isPresent());
+ } else {
+ MethodSubject constructorInit = clazz.init(Constructor.class.getTypeName());
+ assertThat(constructorInit, isPresent());
+
+ MethodSubject methodInit = clazz.init(Method.class.getTypeName());
+ assertThat(methodInit, isPresent());
+ }
})
.run(parameters.getRuntime(), Main.class)
// The test succeeds for some unknown reason.
@@ -92,6 +114,7 @@
}
}
+ @NoVerticalClassMerging
public abstract static class Factory {
abstract Object newInstance() throws Exception;
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest.java
new file mode 100644
index 0000000..dac6de6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.classmerging.horizontal.EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest.Main.encode;
+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.NoFieldTypeStrengthening;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, Parent.class, A.class, B.class, MyLibraryClass.class)
+ .addLibraryClasses(LibraryClassBase.class, LibraryClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addKeepClassAndMembersRules(Main.class, Parent.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableNoFieldTypeStrengtheningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ // Verify that the two constructors A.<init> and B.<init> have not been merged.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(
+ 2, aClassSubject.allMethods(MethodSubject::isInstanceInitializer).size());
+ })
+ .addRunClasspathClasses(LibraryClassBase.class, LibraryClass.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("LibraryClass", "MyLibraryClass");
+ }
+
+ static class LibraryClassBase {}
+
+ static class LibraryClass extends LibraryClassBase {
+
+ @Override
+ public String toString() {
+ return "LibraryClass";
+ }
+ }
+
+ // @Keep
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(new LibraryClass()));
+ System.out.println(new B(new MyLibraryClass()));
+ }
+
+ public static String encode(Object o) {
+ return o.toString();
+ }
+ }
+
+ // @Keep
+ static class Parent {
+
+ Parent(LibraryClass lib) {}
+ }
+
+ static class A extends Parent {
+
+ @NoFieldTypeStrengthening LibraryClassBase f;
+
+ A(LibraryClass c) {
+ super(c);
+ f = c;
+ }
+
+ @Override
+ public String toString() {
+ // Use `f` to ensure it is not removed.
+ return encode(f);
+ }
+ }
+
+ static class B extends Parent {
+
+ @NoFieldTypeStrengthening LibraryClassBase f;
+
+ B(MyLibraryClass d) {
+ super(d);
+ f = d;
+ }
+
+ @Override
+ public String toString() {
+ // Use `f` to ensure it is not removed.
+ return encode(f);
+ }
+ }
+
+ static class MyLibraryClass extends LibraryClass {
+
+ @Override
+ public String toString() {
+ return "MyLibraryClass";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
index 11a96b7..250a8e9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
@@ -5,18 +5,13 @@
package com.android.tools.r8.classmerging.horizontal;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThrows;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -30,7 +25,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters()
- .withDexRuntimes()
+ .withDefaultDexRuntime()
.withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
.build();
}
@@ -39,42 +34,23 @@
// Ensure the main-dex-rules variant of this test (PreventMergeMainDexTracingTest) is sufficient.
@Test
public void testR8() throws Exception {
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addKeepClassAndMembersRules(Main.class)
- .addMainDexListClasses(A.class, Main.class)
- .addOptionsModification(options -> options.minimalMainDex = true)
- .enableNeverClassInliningAnnotations()
- .setMinApi(parameters)
- .allowDiagnosticMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)))
- .apply(this::checkCompileResult)
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("main dex");
- }
-
- private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
- Path out = temp.newFolder().toPath();
- compileResult.app.writeToDirectory(out, OutputMode.DexIndexed);
- Path classes = out.resolve("classes.dex");
- Path classes2 = out.resolve("classes2.dex");
- inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()));
- inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
- }
-
- private void inspectMainDex(CodeInspector inspector) {
- assertThat(inspector.clazz(A.class), isPresent());
- assertThat(inspector.clazz(B.class), not(isPresent()));
- }
-
- private void inspectSecondaryDex(CodeInspector inspector) {
- assertThat(inspector.clazz(A.class), not(isPresent()));
- assertThat(inspector.clazz(B.class), isPresent());
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepClassAndMembersRules(Main.class)
+ .addMainDexListClasses(A.class, Main.class)
+ .addOptionsModification(options -> options.minimalMainDex = true)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters)
+ .allowDiagnosticMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(
+ diagnosticType(UnsupportedMainDexListUsageDiagnostic.class))));
}
public static class Main {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index c50b238..5d349fc 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -20,11 +20,16 @@
import com.android.tools.r8.compilerapi.globalsyntheticsgenerator.GlobalSyntheticsGeneratorTest;
import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
import com.android.tools.r8.compilerapi.inputmap.InputMapTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexClassesTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexListTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexRulesTest;
import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
import com.android.tools.r8.compilerapi.mockdata.MockClass;
import com.android.tools.r8.compilerapi.mockdata.MockClassWithAssertion;
import com.android.tools.r8.compilerapi.mockdata.PostStartupMockClass;
import com.android.tools.r8.compilerapi.partitionmap.PartitionMapCommandTest;
+import com.android.tools.r8.compilerapi.sampleapi.D8ApiUsageSampleTest;
+import com.android.tools.r8.compilerapi.sampleapi.R8ApiUsageSampleTest;
import com.android.tools.r8.compilerapi.sourcefile.CustomSourceFileTest;
import com.android.tools.r8.compilerapi.startupprofile.StartupProfileApiTest;
import com.android.tools.r8.compilerapi.syntheticscontexts.SyntheticContextsConsumerTest;
@@ -47,6 +52,8 @@
private static final List<Class<? extends CompilerApiTest>> CLASSES_FOR_BINARY_COMPATIBILITY =
ImmutableList.of(
ApiTestingSetUpTest.ApiTest.class,
+ D8ApiUsageSampleTest.ApiTest.class,
+ R8ApiUsageSampleTest.ApiTest.class,
CustomMapIdTest.ApiTest.class,
CustomSourceFileTest.ApiTest.class,
AssertionConfigurationTest.ApiTest.class,
@@ -66,7 +73,10 @@
PartitionMapCommandTest.ApiTest.class,
CancelCompilationCheckerTest.ApiTest.class,
GlobalSyntheticsGeneratorTest.ApiTest.class,
- InputMapTest.ApiTest.class);
+ InputMapTest.ApiTest.class,
+ MainDexListTest.ApiTest.class,
+ MainDexClassesTest.ApiTest.class,
+ MainDexRulesTest.ApiTest.class);
private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
ImmutableList.of();
diff --git a/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java b/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
index 6cab4d7..c3e49a5 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
@@ -3,13 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.compilerapi.globalsyntheticsgenerator;
-import com.android.tools.r8.DexIndexedConsumer;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
import com.android.tools.r8.GlobalSyntheticsGenerator;
import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
-import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.compilerapi.CompilerApiTest;
import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.references.ClassReference;
import org.junit.Test;
public class GlobalSyntheticsGeneratorTest extends CompilerApiTestRunner {
@@ -27,8 +33,23 @@
public void testGlobalSynthetics() throws Exception {
new ApiTest(ApiTest.PARAMETERS)
.run(
- new DexIndexedConsumer.ArchiveConsumer(
- temp.newFolder().toPath().resolve("output.zip")));
+ new GlobalSyntheticsConsumer() {
+ boolean hasOutput = false;
+
+ @Override
+ public void accept(
+ ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+ assertNull(context);
+ assertFalse(hasOutput);
+ assertTrue(data.getLength() > 0);
+ hasOutput = true;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ assertTrue(hasOutput);
+ }
+ });
}
public static class ApiTest extends CompilerApiTest {
@@ -37,18 +58,25 @@
super(parameters);
}
- public void run(ProgramConsumer programConsumer) throws Exception {
+ public void run(GlobalSyntheticsConsumer consumer) throws Exception {
GlobalSyntheticsGenerator.run(
GlobalSyntheticsGeneratorCommand.builder()
.addLibraryFiles(getAndroidJar())
.setMinApiLevel(33)
- .setProgramConsumer(programConsumer)
+ .setGlobalSyntheticsConsumer(consumer)
.build());
}
@Test
public void testGlobalSynthetics() throws Exception {
- run(DexIndexedConsumer.emptyConsumer());
+ run(
+ new GlobalSyntheticsConsumer() {
+ @Override
+ public void accept(
+ ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+ // Ignoring output in API test.
+ }
+ });
}
}
}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java
new file mode 100644
index 0000000..f204285
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.maindex;
+
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
+import java.nio.file.Path;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class MainDexClassesTest extends CompilerApiTestRunner {
+
+ public MainDexClassesTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ static class InputClass {}
+
+ interface Runner {
+ void run(ApiTest test, String[] mainDexClasses, DiagnosticsHandler handler) throws Exception;
+ }
+
+ TestDiagnosticMessagesImpl handler;
+
+ private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ String[] mainDexClasses = new String[] {InputClass.class.getName()};
+ handler = new TestDiagnosticMessagesImpl();
+ runner.run(test, mainDexClasses, handler);
+ return handler;
+ }
+
+ private Path getDexInput() throws Exception {
+ return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+ }
+
+ private static Path getCfInput() {
+ return ToolHelper.getClassFileForTestClass(InputClass.class);
+ }
+
+ @Test
+ public void testD8DexInputs() throws Exception {
+ runTest((test, mainDexClasses, handler) -> test.runD8(getDexInput(), mainDexClasses, handler))
+ .assertNoMessages();
+ }
+
+ @Test
+ public void testD8CfInputs() throws Exception {
+ try {
+ runTest((test, mainDexClasses, handler) -> test.runD8(getCfInput(), mainDexClasses, handler));
+ } catch (CompilationFailedException e) {
+ handler
+ .assertOnlyErrors()
+ .assertAllErrorsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+ return;
+ }
+ fail("Expected compilation failure");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ try {
+ runTest((test, mainDexClasses, handler) -> test.runR8(getCfInput(), mainDexClasses, handler));
+ } catch (CompilationFailedException e) {
+ handler
+ .assertOnlyErrors()
+ .assertAllErrorsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+ return;
+ }
+ fail("Expected compilation failure");
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runD8(Path programInput, String[] mainDexClassesInput, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ D8Command.Builder builder =
+ handler == null ? D8Command.builder() : D8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexClassesInput != null) {
+ builder.addMainDexClasses(mainDexClassesInput);
+ builder.addMainDexClasses(Arrays.asList(mainDexClassesInput));
+ }
+ D8.run(builder.build());
+ }
+
+ public void runR8(Path programInput, String[] mainDexClassesInput, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ R8Command.Builder builder =
+ handler == null ? R8Command.builder() : R8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ builder.addMainDexClasses(mainDexClassesInput);
+ builder.addMainDexClasses(Arrays.asList(mainDexClassesInput));
+ R8.run(builder.build());
+ }
+
+ @Test
+ public void testD8() throws CompilationFailedException {
+ runD8(null, new String[0], null);
+ }
+
+ @Test
+ public void testR8() throws CompilationFailedException {
+ runR8(null, new String[0], null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java
new file mode 100644
index 0000000..47430fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java
@@ -0,0 +1,148 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.maindex;
+
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
+import com.android.tools.r8.utils.FileUtils;
+import java.nio.file.Path;
+import java.util.Collections;
+import org.junit.Test;
+
+public class MainDexListTest extends CompilerApiTestRunner {
+
+ public MainDexListTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ static class InputClass {}
+
+ interface Runner {
+ void run(ApiTest test, Path mainDexList, DiagnosticsHandler handler) throws Exception;
+ }
+
+ TestDiagnosticMessagesImpl handler;
+
+ private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ Path mainDexListInput =
+ FileUtils.writeTextFile(
+ temp.newFile().toPath(), InputClass.class.getName().replace('.', '/') + ".class");
+ handler = new TestDiagnosticMessagesImpl();
+ runner.run(test, mainDexListInput, handler);
+ return handler;
+ }
+
+ private Path getDexInput() throws Exception {
+ return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+ }
+
+ private static Path getCfInput() {
+ return ToolHelper.getClassFileForTestClass(InputClass.class);
+ }
+
+ @Test
+ public void testD8DexInputs() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runD8(getDexInput(), mainDexList, handler))
+ .assertNoMessages();
+ }
+
+ @Test
+ public void testD8CfInputs() throws Exception {
+ try {
+ runTest((test, mainDexList, handler) -> test.runD8(getCfInput(), mainDexList, handler));
+ } catch (CompilationFailedException e) {
+ handler
+ .assertOnlyErrors()
+ .assertAllErrorsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+ return;
+ }
+ fail("Expected compilation failure");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ try {
+ runTest((test, mainDexList, handler) -> test.runR8(getCfInput(), mainDexList, handler));
+ } catch (CompilationFailedException e) {
+ handler
+ .assertOnlyErrors()
+ .assertAllErrorsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+ return;
+ }
+ fail("Expected compilation failure");
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runD8(Path programInput, Path mainDexListInput, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ D8Command.Builder builder =
+ handler == null ? D8Command.builder() : D8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexListInput != null) {
+ builder.addMainDexListFiles(mainDexListInput);
+ builder.addMainDexListFiles(Collections.singletonList(mainDexListInput));
+ }
+ D8.run(builder.build());
+ }
+
+ public void runR8(Path programInput, Path mainDexListInput, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ R8Command.Builder builder =
+ handler == null ? R8Command.builder() : R8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexListInput != null) {
+ builder.addMainDexListFiles(mainDexListInput);
+ builder.addMainDexListFiles(Collections.singletonList(mainDexListInput));
+ }
+ R8.run(builder.build());
+ }
+
+ @Test
+ public void testD8() throws CompilationFailedException {
+ runD8(null, null, null);
+ }
+
+ @Test
+ public void testR8() throws CompilationFailedException {
+ runR8(null, null, null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java
new file mode 100644
index 0000000..5701058
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.maindex;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class MainDexRulesTest extends CompilerApiTestRunner {
+
+ public MainDexRulesTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ static class InputClass {}
+
+ interface Runner {
+ void run(ApiTest test, Path mainDexList, DiagnosticsHandler handler) throws Exception;
+ }
+
+ private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ Path mainDexRulesFile = FileUtils.writeTextFile(temp.newFile().toPath(), "# empty file");
+ TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+ runner.run(test, mainDexRulesFile, handler);
+ return handler;
+ }
+
+ private Path getDexInput() throws Exception {
+ return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+ }
+
+ private static Path getCfInput() {
+ return ToolHelper.getClassFileForTestClass(InputClass.class);
+ }
+
+ @Test
+ public void testD8DexInputs() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runD8(getDexInput(), mainDexList, handler))
+ .assertNoMessages();
+ }
+
+ @Test
+ public void testD8CfInputs() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runD8(getCfInput(), mainDexList, handler))
+ .assertNoMessages();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runTest((test, mainDexList, handler) -> test.runR8(getCfInput(), mainDexList, handler))
+ .assertNoMessages();
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runD8(Path programInput, Path mainDexRulesFile, DiagnosticsHandler handler)
+ throws Exception {
+ D8Command.Builder builder =
+ handler == null ? D8Command.builder() : D8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexRulesFile != null) {
+ List<String> rules = Files.readAllLines(mainDexRulesFile);
+ builder.addMainDexRules(rules, new PathOrigin(mainDexRulesFile));
+ builder.addMainDexRulesFiles(mainDexRulesFile);
+ builder.addMainDexRulesFiles(Collections.singletonList(mainDexRulesFile));
+ }
+ D8.run(builder.build());
+ }
+
+ public void runR8(Path programInput, Path mainDexRulesFile, DiagnosticsHandler handler)
+ throws CompilationFailedException {
+ R8Command.Builder builder =
+ handler == null ? R8Command.builder() : R8Command.builder(handler);
+ builder
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (programInput != null) {
+ builder.addProgramFiles(programInput);
+ }
+ if (mainDexRulesFile != null) {
+ List<String> rules;
+ try {
+ rules = Files.readAllLines(mainDexRulesFile);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ builder.addMainDexRules(rules, new PathOrigin(mainDexRulesFile));
+ builder.addMainDexRulesFiles(mainDexRulesFile);
+ builder.addMainDexRulesFiles(Collections.singletonList(mainDexRulesFile));
+ }
+ R8.run(builder.build());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ runD8(null, null, null);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runR8(null, null, null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java
new file mode 100644
index 0000000..7a71205
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java
@@ -0,0 +1,541 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.sampleapi;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * This API test is a copy over from the old API sample test set up.
+ *
+ * <p>NOTE: Don't use this test as the basis for new API tests. Instead, use one of the simpler and
+ * more feature directed tests found in the sibling packages.
+ */
+public class D8ApiUsageSampleTest extends CompilerApiTestRunner {
+
+ public D8ApiUsageSampleTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void test() throws IOException {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ test.run(temp);
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ private static final Origin origin =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "D8ApiUsageSample";
+ }
+ };
+
+ private static final DiagnosticsHandler handler = new DiagnosticsHandler() {};
+
+ @Test
+ public void test() throws IOException {
+ run(temp);
+ }
+
+ public void run(TemporaryFolder temp) throws IOException {
+ runFromArgs(
+ new String[] {
+ "--output",
+ temp.newFolder().getAbsolutePath(),
+ "--min-api",
+ "19",
+ "--lib",
+ getAndroidJar().toString(),
+ "--classpath",
+ getAndroidJar().toString(),
+ getPathForClass(getMockClass()).toString()
+ });
+ }
+
+ public void runFromArgs(String[] args) {
+ // Parse arguments with the commandline parser to make use of its API.
+ D8Command.Builder cmd = D8Command.parse(args, origin);
+ CompilationMode mode = cmd.getMode();
+ Path temp = cmd.getOutputPath();
+ int minApiLevel = cmd.getMinApiLevel();
+ // The Builder API does not provide access to the concrete paths
+ // (everything is put into providers) so manually parse them here.
+ List<Path> libraries = new ArrayList<>(1);
+ List<Path> classpath = new ArrayList<>(args.length);
+ List<Path> inputs = new ArrayList<>(args.length);
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--lib")) {
+ libraries.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--classpath")) {
+ classpath.add(Paths.get(args[++i]));
+ } else if (isArchive(args[i]) || isClassFile(args[i])) {
+ inputs.add(Paths.get(args[i]));
+ }
+ }
+ if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+ throw new RuntimeException("Must supply a temp/output directory");
+ }
+ if (inputs.isEmpty()) {
+ throw new RuntimeException("Must supply program inputs");
+ }
+ if (libraries.isEmpty()) {
+ throw new RuntimeException("Must supply library inputs");
+ }
+ useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
+ useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
+ useProgramData(minApiLevel, libraries, classpath, inputs);
+ useProgramResourceProvider(minApiLevel, libraries, classpath, inputs);
+ useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
+ useAssertionConfig(minApiLevel, libraries, classpath, inputs);
+ useVArgVariants(minApiLevel, libraries, classpath, inputs);
+ incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
+ }
+
+ // Check API support for compiling Java class-files from the file system.
+ private static void useProgramFileList(
+ CompilationMode mode,
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMode(mode)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from byte content.
+ private static void useProgramData(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath);
+ for (ClassFileContent classfile : readClassFiles(inputs)) {
+ builder.addClassProgramData(classfile.data, classfile.origin);
+ }
+ for (Path input : inputs) {
+ if (isDexFile(input)) {
+ builder.addDexProgramData(Files.readAllBytes(input), new PathOrigin(input));
+ }
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from a program provider abstraction.
+ private static void useProgramResourceProvider(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath);
+ for (Path input : inputs) {
+ if (isArchive(input)) {
+ builder.addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(
+ input, ArchiveProgramResourceProvider::includeClassFileEntries));
+ } else if (isClassFile(input)) {
+ builder.addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources()
+ throws ResourceException {
+ return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+ }
+ });
+ } else if (isDexFile(input)) {
+ builder.addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources()
+ throws ResourceException {
+ return Collections.singleton(ProgramResource.fromFile(Kind.DEX, input));
+ }
+ });
+ }
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useLibraryAndClasspathProvider(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addProgramFiles(inputs);
+ for (Path library : libraries) {
+ builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+ }
+ for (Path path : classpath) {
+ builder.addClasspathResourceProvider(new ArchiveClassFileProvider(path));
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useAssertionConfig(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ String pkg = "com.android.tools.r8.compilerapi.sampleapi";
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
+ .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
+ .addAssertionsConfiguration(
+ b -> b.setScopePackage(pkg).setCompileTimeEnable().build())
+ .addAssertionsConfiguration(b -> b.setScopePackage(pkg).setPassthrough().build())
+ .addAssertionsConfiguration(
+ b -> b.setScopePackage(pkg).setCompileTimeDisable().build())
+ .addAssertionsConfiguration(
+ b ->
+ b.setScopeClass(pkg + ".D8ApiUsageSampleTest")
+ .setCompileTimeEnable()
+ .build())
+ .addAssertionsConfiguration(
+ b -> b.setScopeClass(pkg + ".D8ApiUsageSampleTest").setPassthrough().build())
+ .addAssertionsConfiguration(
+ b ->
+ b.setScopeClass(pkg + ".D8ApiUsageSampleTest")
+ .setCompileTimeDisable()
+ .build())
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::passthroughAllAssertions)
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for all the varg variants.
+ private static void useVArgVariants(
+ int minApiLevel, List<Path> libraries, List<Path> classpath, List<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries.get(0))
+ .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
+ .addProgramFiles(inputs.get(0))
+ .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new));
+ if (!classpath.isEmpty()) {
+ builder
+ .addClasspathFiles(classpath.get(0))
+ .addClasspathFiles(classpath.stream().skip(1).toArray(Path[]::new));
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void incrementalCompileAndMerge(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ // Compile and merge via index intermediates.
+ mergeIntermediates(
+ minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
+ // Compile and merge via per-classfile intermediates.
+ mergeIntermediates(
+ minApiLevel,
+ compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
+ }
+
+ private static Collection<byte[]> compileToIndexedIntermediates(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setIntermediate(true)
+ .setProgramConsumer(consumer)
+ .addClasspathFiles(classpath)
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .setDisableDesugaring(false)
+ .setDesugarGraphConsumer(new MyDesugarGraphConsumer())
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ return consumer.bytes;
+ }
+
+ private static Collection<byte[]> compileToPerClassFileIntermediates(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(consumer)
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ return consumer.bytes;
+ }
+
+ private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .setDisableDesugaring(true);
+ for (byte[] intermediate : intermediates) {
+ builder.addDexProgramData(intermediate, Origin.unknown());
+ }
+ try {
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected merging error", e);
+ }
+ }
+
+ // Helpers for tests.
+ // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+ // rely on it.
+
+ private static List<ClassFileContent> readClassFiles(Collection<Path> files)
+ throws IOException {
+ List<ClassFileContent> classfiles = new ArrayList<>();
+ for (Path file : files) {
+ if (isArchive(file)) {
+ Origin zipOrigin = new PathOrigin(file);
+ ZipInputStream zip =
+ new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
+ ZipEntry entry;
+ while (null != (entry = zip.getNextEntry())) {
+ String name = entry.getName();
+ if (isClassFile(name)) {
+ Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
+ classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+ }
+ }
+ } else if (isClassFile(file)) {
+ classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+ }
+ }
+ return classfiles;
+ }
+
+ private static byte[] readBytes(InputStream stream) throws IOException {
+ try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[0xffff];
+ for (int length; (length = stream.read(buffer)) != -1; ) {
+ bytes.write(buffer, 0, length);
+ }
+ return bytes.toByteArray();
+ }
+ }
+
+ private static String toLowerCase(String str) {
+ return str.toLowerCase(Locale.ROOT);
+ }
+
+ private static boolean isClassFile(Path file) {
+ return isClassFile(file.toString());
+ }
+
+ private static boolean isClassFile(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".class");
+ }
+
+ private static boolean isDexFile(Path file) {
+ return isDexFile(file.toString());
+ }
+
+ private static boolean isDexFile(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".dex");
+ }
+
+ private static boolean isArchive(Path file) {
+ return isArchive(file.toString());
+ }
+
+ private static boolean isArchive(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".zip") || file.endsWith(".jar");
+ }
+
+ private static class ClassFileContent {
+ final Origin origin;
+ final byte[] data;
+
+ public ClassFileContent(Origin origin, byte[] data) {
+ this.origin = origin;
+ this.data = data;
+ }
+ }
+
+ private static class IndexIntermediatesConsumer implements DexIndexedConsumer {
+
+ List<byte[]> bytes = new ArrayList<>();
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ bytes.add(data);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ }
+
+ private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
+
+ List<byte[]> bytes = new ArrayList<>();
+
+ @Override
+ public synchronized void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ bytes.add(data);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ }
+
+ private static class EnsureOutputConsumer implements DexIndexedConsumer {
+ boolean hasOutput = false;
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ hasOutput = true;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (!hasOutput) {
+ handler.error(new StringDiagnostic("Expected to produce output but had none"));
+ }
+ }
+ }
+
+ private static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
+
+ @Override
+ public void accept(Origin dependent, Origin dependency) {}
+
+ public void finished() {}
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java
new file mode 100644
index 0000000..b3258e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java
@@ -0,0 +1,522 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.sampleapi;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * This API test is a copy over from the old API sample test set up.
+ *
+ * <p>NOTE: Don't use this test as the basis for new API tests. Instead, use one of the simpler and
+ * more feature directed tests found in the sibling packages.
+ */
+public class R8ApiUsageSampleTest extends CompilerApiTestRunner {
+
+ public R8ApiUsageSampleTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void test() throws IOException {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ test.run(temp);
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ private static final Origin origin =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "R8ApiUsageSample";
+ }
+ };
+
+ private static final DiagnosticsHandler handler = new DiagnosticsHandler() {};
+
+ @Test
+ public void test() throws IOException {
+ run(temp);
+ }
+
+ public void run(TemporaryFolder temp) throws IOException {
+ Path pgConf = temp.getRoot().toPath().resolve("rules.conf");
+ Files.write(pgConf, getKeepMainRules(getMockClass()));
+ runFromArgs(
+ new String[] {
+ "--output",
+ temp.newFolder().getAbsolutePath(),
+ "--min-api",
+ "19",
+ "--pg-conf",
+ pgConf.toString(),
+ "--lib",
+ getAndroidJar().toString(),
+ getPathForClass(getMockClass()).toString()
+ });
+ }
+
+ public void runFromArgs(String[] args) {
+ // Check version API
+ checkVersionApi();
+ // Parse arguments with the commandline parser to make use of its API.
+ R8Command.Builder cmd = R8Command.parse(args, origin);
+ CompilationMode mode = cmd.getMode();
+ Path temp = cmd.getOutputPath();
+ int minApiLevel = cmd.getMinApiLevel();
+ // The Builder API does not provide access to the concrete paths
+ // (everything is put into providers) so manually parse them here.
+ List<Path> libraries = new ArrayList<>(1);
+ List<Path> pgConf = new ArrayList<>(1);
+ List<Path> inputs = new ArrayList<>(args.length);
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--lib")) {
+ libraries.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--pg-conf")) {
+ pgConf.add(Paths.get(args[++i]));
+ } else if (isArchive(args[i]) || isClassFile(args[i])) {
+ inputs.add(Paths.get(args[i]));
+ }
+ }
+ if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+ throw new RuntimeException("Must supply a temp/output directory");
+ }
+ if (inputs.isEmpty()) {
+ throw new RuntimeException("Must supply program inputs");
+ }
+ if (libraries.isEmpty()) {
+ throw new RuntimeException("Must supply library inputs");
+ }
+ if (pgConf.isEmpty()) {
+ throw new RuntimeException("Must supply pg-conf inputs");
+ }
+
+ useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
+ useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
+ useProgramData(minApiLevel, libraries, inputs);
+ useProgramResourceProvider(minApiLevel, libraries, inputs);
+ useLibraryResourceProvider(minApiLevel, libraries, inputs);
+ useProguardConfigFiles(minApiLevel, libraries, inputs, pgConf);
+ useProguardConfigLines(minApiLevel, libraries, inputs, pgConf);
+ useAssertionConfig(minApiLevel, libraries, inputs);
+ useVArgVariants(minApiLevel, libraries, inputs, pgConf);
+ useProguardConfigConsumers(minApiLevel, libraries, inputs, pgConf);
+ }
+
+ private static class InMemoryStringConsumer implements StringConsumer {
+
+ public String value = null;
+
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ value = string;
+ }
+ }
+
+ private static void useProguardConfigConsumers(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+ InMemoryStringConsumer usageConsumer = new InMemoryStringConsumer();
+ InMemoryStringConsumer seedsConsumer = new InMemoryStringConsumer();
+ InMemoryStringConsumer configConsumer = new InMemoryStringConsumer();
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addProguardConfigurationFiles(pgConf)
+ .setProguardUsageConsumer(usageConsumer)
+ .setProguardSeedsConsumer(seedsConsumer)
+ .setProguardConfigurationConsumer(configConsumer)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exception", e);
+ }
+ if (usageConsumer.value == null) {
+ throw new RuntimeException("Expected usage info but had none");
+ }
+ if (seedsConsumer.value == null) {
+ throw new RuntimeException("Expected seeds info but had none");
+ }
+ if (configConsumer.value == null) {
+ throw new RuntimeException("Expected config info but had none");
+ }
+ }
+
+ // Check API support for compiling Java class-files from the file system.
+ private static void useProgramFileList(
+ CompilationMode mode,
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> inputs) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMode(mode)
+ .setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from byte content.
+ private static void useProgramData(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries);
+ for (ClassFileContent classfile : readClassFiles(inputs)) {
+ builder.addClassProgramData(classfile.data, classfile.origin);
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from a program provider abstraction.
+ private static void useProgramResourceProvider(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries);
+ for (Path input : inputs) {
+ if (isArchive(input)) {
+ builder.addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(
+ input, ArchiveProgramResourceProvider::includeClassFileEntries));
+ } else {
+ builder.addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources()
+ throws ResourceException {
+ return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+ }
+ });
+ }
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useLibraryResourceProvider(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addProgramFiles(inputs);
+ for (Path library : libraries) {
+ builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useProguardConfigFiles(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addProguardConfigurationFiles(pgConf)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useProguardConfigLines(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs);
+ for (Path file : pgConf) {
+ builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useAssertionConfig(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ String pkg = "com.android.tools.r8.compilerapi.sampleapi";
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setDisableTreeShaking(true)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
+ .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
+ .addAssertionsConfiguration(
+ b -> b.setScopePackage(pkg).setCompileTimeEnable().build())
+ .addAssertionsConfiguration(b -> b.setScopePackage(pkg).setPassthrough().build())
+ .addAssertionsConfiguration(
+ b -> b.setScopePackage(pkg).setCompileTimeDisable().build())
+ .addAssertionsConfiguration(
+ b ->
+ b.setScopeClass(pkg + "R8ApiUsageSampleTest")
+ .setCompileTimeEnable()
+ .build())
+ .addAssertionsConfiguration(
+ b -> b.setScopeClass(pkg + "R8ApiUsageSampleTest").setPassthrough().build())
+ .addAssertionsConfiguration(
+ b ->
+ b.setScopeClass(pkg + "R8ApiUsageSampleTest")
+ .setCompileTimeDisable()
+ .build())
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::passthroughAllAssertions)
+ .addAssertionsConfiguration(
+ AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for all the varg variants.
+ private static void useVArgVariants(
+ int minApiLevel, List<Path> libraries, List<Path> inputs, List<Path> pgConf) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries.get(0))
+ .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
+ .addProgramFiles(inputs.get(0))
+ .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
+ .addProguardConfigurationFiles(pgConf.get(0))
+ .addProguardConfigurationFiles(pgConf.stream().skip(1).toArray(Path[]::new))
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Helpers for tests.
+ // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+ // rely on it.
+
+ private static List<ClassFileContent> readClassFiles(Collection<Path> files)
+ throws IOException {
+ List<ClassFileContent> classfiles = new ArrayList<>();
+ for (Path file : files) {
+ if (isArchive(file)) {
+ Origin zipOrigin = new PathOrigin(file);
+ ZipInputStream zip =
+ new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
+ ZipEntry entry;
+ while (null != (entry = zip.getNextEntry())) {
+ String name = entry.getName();
+ if (isClassFile(name)) {
+ Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
+ classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+ }
+ }
+ } else if (isClassFile(file)) {
+ classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+ }
+ }
+ return classfiles;
+ }
+
+ private static byte[] readBytes(InputStream stream) throws IOException {
+ try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[0xffff];
+ for (int length; (length = stream.read(buffer)) != -1; ) {
+ bytes.write(buffer, 0, length);
+ }
+ return bytes.toByteArray();
+ }
+ }
+
+ private static String toLowerCase(String str) {
+ return str.toLowerCase(Locale.ROOT);
+ }
+
+ private static boolean isClassFile(Path file) {
+ return isClassFile(file.toString());
+ }
+
+ private static boolean isClassFile(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".class");
+ }
+
+ private static boolean isArchive(Path file) {
+ return isArchive(file.toString());
+ }
+
+ private static boolean isArchive(String file) {
+ file = toLowerCase(file);
+ return file.endsWith(".zip") || file.endsWith(".jar");
+ }
+
+ private static class ClassFileContent {
+
+ final Origin origin;
+ final byte[] data;
+
+ public ClassFileContent(Origin origin, byte[] data) {
+ this.origin = origin;
+ this.data = data;
+ }
+ }
+
+ private static class EnsureOutputConsumer implements DexIndexedConsumer {
+
+ boolean hasOutput = false;
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ hasOutput = true;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (!hasOutput) {
+ handler.error(new StringDiagnostic("Expected to produce output but had none"));
+ }
+ }
+ }
+
+ private static void checkVersionApi() {
+ String labelValue;
+ int labelAccess;
+ try {
+ Field field = Version.class.getDeclaredField("LABEL");
+ labelAccess = field.getModifiers();
+ labelValue = (String) field.get(Version.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ if (!Modifier.isPublic(labelAccess)
+ || !Modifier.isStatic(labelAccess)
+ || !Modifier.isFinal(labelAccess)) {
+ throw new RuntimeException("Expected public static final LABEL");
+ }
+ if (labelValue.isEmpty()) {
+ throw new RuntimeException("Expected LABEL constant");
+ }
+ if (Version.LABEL.isEmpty()) {
+ throw new RuntimeException("Expected LABEL constant");
+ }
+ if (Version.getVersionString() == null) {
+ throw new RuntimeException("Expected getVersionString API");
+ }
+ if (Version.getMajorVersion() < -1) {
+ throw new RuntimeException("Expected getMajorVersion API");
+ }
+ if (Version.getMinorVersion() < -1) {
+ throw new RuntimeException("Expected getMinorVersion API");
+ }
+ if (Version.getPatchVersion() < -1) {
+ throw new RuntimeException("Expected getPatchVersion API");
+ }
+ if (Version.getPreReleaseString() == null && false) {
+ throw new RuntimeException("Expected getPreReleaseString API");
+ }
+ if (Version.isDevelopmentVersion() && false) {
+ throw new RuntimeException("Expected isDevelopmentVersion API");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java b/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
index e7169c3..6175552 100644
--- a/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
+++ b/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
@@ -51,7 +51,7 @@
.addOptionsModification(
internalOptions -> internalOptions.testing.limitNumberOfClassesPerDex = 2);
if (parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
- d8TestBuilder.addMainDexListClasses(A.class);
+ d8TestBuilder.addMainDexRules("-keep class " + A.class.getName());
}
Path outputDir = temp.newFolder().toPath();
d8TestBuilder
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java
new file mode 100644
index 0000000..0747d96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2024, 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.backports;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApiLevelRangeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public ApiLevelRangeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRange() {
+ ApiLevelRange rs = new ApiLevelRange(AndroidApiLevel.S, AndroidApiLevel.R);
+ ApiLevelRange op = new ApiLevelRange(AndroidApiLevel.P, AndroidApiLevel.O);
+ ApiLevelRange ir = new ApiLevelRange(AndroidApiLevel.R, AndroidApiLevel.I);
+ ApiLevelRange r = new ApiLevelRange(AndroidApiLevel.R, null);
+ ApiLevelRange i = new ApiLevelRange(AndroidApiLevel.I, null);
+
+ // Overlap with null.
+ assertTrue(i.overlap(r));
+ assertTrue(r.overlap(i));
+
+ // No overlap with null.
+ assertFalse(i.overlap(rs));
+ assertFalse(rs.overlap(i));
+
+ // No overlap.
+ assertFalse(rs.overlap(op));
+ assertFalse(op.overlap(rs));
+
+ // Partial overlap.
+ assertTrue(ir.overlap(rs));
+ assertTrue(rs.overlap(ir));
+
+ // Full overlap.
+ assertTrue(ir.overlap(op));
+ assertTrue(op.overlap(ir));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 5369238..0143e8e 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -209,12 +209,19 @@
.compile()
.writeToZip();
+ Path out3 =
+ testForD8()
+ .addProgramClasses(TestClass.class, MiniAssert.class)
+ .addClasspathClasses(CLASSES)
+ .setIntermediate(true)
+ .setMinApi(parameters)
+ .compile()
+ .writeToZip();
+
MainDexConsumer mainDexConsumer = new MainDexConsumer();
- List<Class<?>> classes = ImmutableList.of(TestClass.class, MiniAssert.class);
- List<Path> files = ImmutableList.of(out1, out2);
- GenerateMainDexListRunResult traceResult = traceMainDex(classes, files);
+ List<Path> files = ImmutableList.of(out1, out2, out3);
+ GenerateMainDexListRunResult traceResult = traceMainDex(Collections.emptyList(), files);
testForD8()
- .addProgramClasses(classes)
.addProgramFiles(files)
.setMinApi(parameters)
.addMainDexListClassReferences(traceResult.getMainDexList())
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 1071a87..3f55e91 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -87,7 +87,7 @@
@Test
public void testBackportedMethodsPerAPILevel() throws Exception {
for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
- if (apiLevel == AndroidApiLevel.MASTER) {
+ if (apiLevel == AndroidApiLevel.MAIN) {
continue;
}
if (apiLevel == AndroidApiLevel.U) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java
index 0e9fe1e..60985a1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java
@@ -81,7 +81,7 @@
testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
.addProgramFiles(INPUT_JAR)
.addKeepMainRule(MAIN_CLASS)
- .overrideLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MASTER))
+ .overrideLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MAIN))
// Missing class java.lang.StackWalker$StackFrame.
.addOptionsModification(opt -> opt.ignoreMissingClasses = true)
.run(parameters.getRuntime(), MAIN_CLASS)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/legacy/L8TestWithLegacySpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/legacy/L8TestWithLegacySpecification.java
index f150bbe..c32622e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/legacy/L8TestWithLegacySpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/legacy/L8TestWithLegacySpecification.java
@@ -46,7 +46,7 @@
Arrays.stream(AndroidApiLevel.values())
.sorted()
.filter(apiLevel -> apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L))
- .filter(apiLevel -> apiLevel.isLessThan(AndroidApiLevel.MASTER))
+ .filter(apiLevel -> apiLevel.isLessThan(AndroidApiLevel.MAIN))
.collect(Collectors.toList()),
CompilationMode.values(),
ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
index 78a7fb7..62f069e 100644
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -70,6 +70,16 @@
.build();
}
+ protected boolean hasTwrCloseResourceSupport(boolean isDesugaring) {
+ return !isDesugaring
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithTwrCloseResourceSupport());
+ }
+
+ protected boolean hasTwrCloseResourceApiOutlines() {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport());
+ }
+
protected String getZipFile() throws IOException {
return ZipUtils.ZipBuilder.builder(temp.newFile("file.zip").toPath())
// DEX VMs from 4.4 up-to 9.0 including, will fail if no entry is added.
@@ -106,10 +116,13 @@
// Throwable.addSuppressed that is still present in the original $closeResource.
// TODO(b/214329923): If the original $closeResource is pruned this will decrease.
// TODO(b/168568827): Once we support a nested addSuppressed this will increase.
- int expectedSynthetics =
- parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
- ? 3
- : 0;
+ int expectedSynthetics = 0;
+ if (!hasTwrCloseResourceSupport(true)) {
+ expectedSynthetics += 2;
+ }
+ if (hasTwrCloseResourceApiOutlines()) {
+ expectedSynthetics += 1;
+ }
assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size());
});
}
@@ -137,7 +150,7 @@
// exception is known or not, thus the synthetic methods will be 2.
Set<String> nonSyntheticClassOutput =
ImmutableSet.of(FOO.typeName(), BAR.typeName(), MAIN.typeName());
- if (parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())) {
+ if (!hasTwrCloseResourceSupport(parameters.isDexRuntime())) {
Set<String> classOutputWithSynthetics = new HashSet<>(nonSyntheticClassOutput);
classOutputWithSynthetics.add(
SyntheticItemsTestUtils.syntheticApiOutlineClass(BAR.getClassReference(), 0)
diff --git a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
index ff2afbe..9ec7c0f 100644
--- a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
+++ b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
@@ -5,22 +5,19 @@
package com.android.tools.r8.globalsynthetics;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.GlobalSyntheticsGenerator;
import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import java.nio.file.Path;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
@@ -31,91 +28,109 @@
@RunWith(Parameterized.class)
public class GlobalSyntheticsEnsureClassesOutputTest extends TestBase {
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
+ private final Backend backend;
+
+ @Parameters(name = "{0}, backend:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
}
- public GlobalSyntheticsEnsureClassesOutputTest(TestParameters parameters) {
+ public GlobalSyntheticsEnsureClassesOutputTest(TestParameters parameters, Backend backend) {
parameters.assertNoneRuntime();
+ this.backend = backend;
}
@Test
public void testNumberOfClassesOnK() throws Exception {
- Path output = temp.newFolder().toPath().resolve("output.zip");
+ GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
GlobalSyntheticsGenerator.run(
GlobalSyntheticsGeneratorCommand.builder()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
.setMinApiLevel(AndroidApiLevel.K.getLevel())
- .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+ .setGlobalSyntheticsConsumer(globalsConsumer)
+ .setClassfileDesugaringOnly(backend.isCf())
.build());
- CodeInspector inspector = new CodeInspector(output);
- assertEquals(1044, inspector.allClasses().size());
+ assertTrue(globalsConsumer.isSingleGlobal());
+ testForD8(backend)
+ .apply(
+ b ->
+ b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ .inspect(
+ inspector -> assertEquals(backend.isDex() ? 1045 : 4, inspector.allClasses().size()));
}
@Test
public void testNumberOfClassesOnLatest() throws Exception {
- Path output = temp.newFolder().toPath().resolve("output.zip");
+ GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
GlobalSyntheticsGenerator.run(
GlobalSyntheticsGeneratorCommand.builder()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
.setMinApiLevel(AndroidApiLevel.LATEST.getLevel())
- .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+ .setGlobalSyntheticsConsumer(globalsConsumer)
+ .setClassfileDesugaringOnly(backend.isCf())
.build());
+ assertTrue(globalsConsumer.isSingleGlobal());
Set<String> expectedInOutput = new HashSet<>();
// The output contains a RecordTag type that is mapped back to the original java.lang.Record by
// our codeinspector.
expectedInOutput.add("Ljava/lang/Record;");
expectedInOutput.add("Ljava/lang/invoke/VarHandle;");
expectedInOutput.add("Ljava/lang/invoke/MethodHandles$Lookup;");
- assertEquals(
- expectedInOutput,
- new CodeInspector(output)
- .allClasses().stream()
- .map(FoundClassSubject::getFinalDescriptor)
- .collect(Collectors.toSet()));
+ testForD8(backend)
+ .apply(
+ b ->
+ b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+ .setMinApi(AndroidApiLevel.LATEST)
+ .compile()
+ .inspect(
+ inspector ->
+ assertEquals(
+ expectedInOutput,
+ inspector.allClasses().stream()
+ .map(FoundClassSubject::getFinalDescriptor)
+ .collect(Collectors.toSet())));
}
@Test
public void testClassFileListOutput() throws Exception {
Set<String> generatedGlobalSynthetics = SetUtils.newConcurrentHashSet();
- Path output = temp.newFolder().toPath().resolve("output.zip");
+ GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
runGlobalSyntheticsGenerator(
GlobalSyntheticsGeneratorCommand.builder()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
.setMinApiLevel(AndroidApiLevel.K.getLevel())
- .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+ .setGlobalSyntheticsConsumer(globalsConsumer)
+ .setClassfileDesugaringOnly(backend.isCf())
.build(),
options ->
options.testing.globalSyntheticCreatedCallback =
programClass -> {
- if (programClass
- .getClassReference()
- .getDescriptor()
- .equals(DexItemFactory.varHandleDescriptorString)) {
- // We emit a desugared var handle. Rewrite it here to allow checking for final
- // type names.
- generatedGlobalSynthetics.add(
- Reference.classFromDescriptor(
- DexItemFactory.desugarVarHandleDescriptorString)
- .getTypeName());
- } else if (programClass
- .getClassReference()
- .getDescriptor()
- .equals(DexItemFactory.methodHandlesLookupDescriptorString)) {
- // We emit a desugared var handle. Rewrite it here to allow checking for final
- // type names.
- generatedGlobalSynthetics.add(
- Reference.classFromDescriptor(
- DexItemFactory.desugarMethodHandlesLookupDescriptorString)
- .getTypeName());
- } else {
- generatedGlobalSynthetics.add(programClass.getTypeName());
- }
+ String descriptor = programClass.getClassReference().getDescriptor();
+ generatedGlobalSynthetics.add(
+ descriptor
+ .replace(
+ "Ljava/lang/invoke/VarHandle",
+ "Lcom/android/tools/r8/DesugarVarHandle")
+ .replace(
+ "Ljava/lang/invoke/MethodHandles$Lookup",
+ "Lcom/android/tools/r8/DesugarMethodHandlesLookup"));
});
- Set<String> readGlobalSynthetics =
- new CodeInspector(output)
- .allClasses().stream().map(FoundClassSubject::getFinalName).collect(Collectors.toSet());
- assertEquals(generatedGlobalSynthetics, readGlobalSynthetics);
+ assertTrue(globalsConsumer.isSingleGlobal());
+ testForD8()
+ .apply(
+ b ->
+ b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ .inspect(
+ inspector -> {
+ Set<String> readGlobalSynthetics =
+ inspector.allClasses().stream()
+ .map(FoundClassSubject::getFinalDescriptor)
+ .collect(Collectors.toSet());
+ assertEquals(generatedGlobalSynthetics, readGlobalSynthetics);
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index bc561cf..4c61934 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -70,58 +70,12 @@
}
@Test
- public void testD8DebugLegacyMultidex() throws Exception {
- compileWithD8Debug(
- builder ->
- builder
- .addMainDexListFiles(base.resolve("main_dex_list.txt"))
- .setMinApi(AndroidApiLevel.K))
- .runDex2Oat(parameters.getRuntime())
- .assertNoVerificationErrors();
- }
-
- @Test
- public void testD8DebugLegacyMultidexDexOpt() throws Exception {
- compileWithD8Debug(
- builder ->
- builder
- .addMainDexListFiles(base.resolve("main_dex_list.txt"))
- .setMinApi(AndroidApiLevel.K)
- .setOptimizeMultidexForLinearAlloc())
- .runDex2Oat(parameters.getRuntime())
- .assertNoVerificationErrors();
- }
-
- @Test
public void testD8Release() throws Exception {
compileWithD8Release(ThrowableConsumer.empty())
.runDex2Oat(parameters.getRuntime())
.assertNoVerificationErrors();
}
- @Test
- public void testD8ReleaseLegacyMultidex() throws Exception {
- compileWithD8Release(
- builder ->
- builder
- .addMainDexListFiles(base.resolve("main_dex_list.txt"))
- .setMinApi(AndroidApiLevel.K))
- .runDex2Oat(parameters.getRuntime())
- .assertNoVerificationErrors();
- }
-
- @Test
- public void buildD8ReleaseLegacyMultidexDexOpt() throws Exception {
- compileWithD8Release(
- builder ->
- builder
- .addMainDexListFiles(base.resolve("main_dex_list.txt"))
- .setMinApi(AndroidApiLevel.K)
- .setOptimizeMultidexForLinearAlloc())
- .runDex2Oat(parameters.getRuntime())
- .assertNoVerificationErrors();
- }
-
private D8TestCompileResult compileWithD8Debug(ThrowableConsumer<D8TestBuilder> configuration)
throws Exception {
return compileWithD8(configuration.andThen(TestCompilerBuilder::debug));
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
deleted file mode 100644
index 4929cb2..0000000
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.internal;
-
-import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import java.nio.file.Paths;
-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 Gmail18082615TreeShakeJarVerificationTest extends GmailCompilationBase {
- private static final int MAX_SIZE = 20000000;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
- }
-
- public Gmail18082615TreeShakeJarVerificationTest(TestParameters parameters) {
- super(180826, 15);
- parameters.assertNoneRuntime();
- }
-
- @Test
- public void buildAndTreeShakeFromDeployJar() throws Exception {
- assumeTrue(isLocalDevelopment());
-
- R8TestCompileResult compileResult =
- testForR8(Backend.DEX)
- .addKeepRuleFiles(
- Paths.get(base).resolve(BASE_PG_CONF),
- Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS, PG_CONF))
- .allowDiagnosticMessages()
- .allowUnusedProguardConfigurationRules()
- .compile()
- .assertAllInfoMessagesMatch(
- anyOf(
- equalTo("Ignoring option: -optimizations"),
- containsString("Proguard configuration rule does not match anything"),
- containsString("Invalid parameter counts in MethodParameter attributes"),
- containsString("Methods with invalid MethodParameter attributes")))
- .assertAllWarningMessagesMatch(containsString("Ignoring option:"));
-
- int appSize = compileResult.app.applicationSize();
- assertTrue("Expected max size of " + MAX_SIZE+ ", got " + appSize, appSize < MAX_SIZE);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java b/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java
deleted file mode 100644
index 7332dd8..0000000
--- a/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.internal;
-
-abstract class GmailCompilationBase extends CompilationTestBase {
- static final String APK = "Gmail_release_unsigned.apk";
- static final String DEPLOY_JAR = "Gmail_release_unstripped_deploy.jar";
- static final String PG_JAR = "Gmail_release_unstripped_proguard.jar";
- static final String PG_MAP = "Gmail_release_unstripped_proguard.map";
- static final String BASE_PG_CONF = "Gmail_release_unstripped_proguard.config";
- static final String PG_CONF = "Gmail_proguard.config";
-
- final String base;
-
- GmailCompilationBase(int majorVersion, int minorVersion) {
- this.base = "third_party/gmail/gmail_android_" + majorVersion + "." + minorVersion + "/";
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java b/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java
new file mode 100644
index 0000000..cd41b8d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java
@@ -0,0 +1,319 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ComposeBinopTest extends TestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "12345678",
+ "12345678",
+ "0",
+ "0",
+ "76532",
+ "76532",
+ "0",
+ "0",
+ "-2147483648",
+ "-2147483648",
+ "0",
+ "0",
+ "74",
+ "0",
+ "12345838",
+ "12345854",
+ "240",
+ "160",
+ "76532",
+ "76542",
+ "0",
+ "0",
+ "-2147483488",
+ "-2147483398",
+ "8260",
+ "0",
+ "12345678",
+ "-2135069698",
+ "8260",
+ "0",
+ "76532",
+ "-2135069698",
+ "0",
+ "0",
+ "-2147475388",
+ "-2135069698",
+ "0",
+ "0",
+ "3948544",
+ "1572864",
+ "0",
+ "0",
+ "0",
+ "0",
+ "252706816",
+ "100663296",
+ "0",
+ "0",
+ "255",
+ "80",
+ "241",
+ "96",
+ "243",
+ "96",
+ "1551743",
+ "1032",
+ "99311600",
+ "66080",
+ "1551743",
+ "1032",
+ "-266892247",
+ "0",
+ "98765424",
+ "0",
+ "269978665",
+ "0",
+ "-268425890",
+ "0",
+ "612256",
+ "0",
+ "268445022",
+ "0",
+ "12413950",
+ "8260",
+ "12413950",
+ "8260",
+ "12413950",
+ "8260",
+ "-131068",
+ "0",
+ "1253900288",
+ "0",
+ "131076",
+ "0",
+ "-2037",
+ "0",
+ "350224384",
+ "0",
+ "2059",
+ "0",
+ "41",
+ "350",
+ "0");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ComposeBinopTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject mainClass = inspector.clazz(Main.class);
+ assertTrue(mainClass.isPresent());
+
+ assertTrue(
+ mainClass
+ .uniqueMethodWithOriginalName("bitSameInput")
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isIntLogicalBinop));
+ assertEquals(
+ 6,
+ mainClass
+ .uniqueMethodWithOriginalName("shareShiftCstLeft")
+ .streamInstructions()
+ .filter(InstructionSubject::isIntLogicalBinop)
+ .count());
+ assertEquals(
+ 12,
+ mainClass
+ .uniqueMethodWithOriginalName("shareShiftCstRight")
+ .streamInstructions()
+ .filter(InstructionSubject::isIntLogicalBinop)
+ .count());
+ assertEquals(
+ 12,
+ mainClass
+ .uniqueMethodWithOriginalName("shareShiftVar")
+ .streamInstructions()
+ .filter(InstructionSubject::isIntLogicalBinop)
+ .count());
+ assertEquals(
+ 4,
+ mainClass
+ .uniqueMethodWithOriginalName("andOrCompositionCst")
+ .streamInstructions()
+ .filter(InstructionSubject::isIntLogicalBinop)
+ .count());
+ assertEquals(
+ 8,
+ mainClass
+ .uniqueMethodWithOriginalName("andOrCompositionVar")
+ .streamInstructions()
+ .filter(InstructionSubject::isIntLogicalBinop)
+ .count());
+ assertEquals(
+ 2,
+ mainClass
+ .uniqueMethodWithOriginalName("composeTests")
+ .streamInstructions()
+ .filter(InstructionSubject::isIntLogicalBinop)
+ .count());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ int i1 = System.currentTimeMillis() > 0 ? 12345678 : 1;
+ int i2 = System.currentTimeMillis() > 0 ? 76532 : 1;
+ int i3 = System.currentTimeMillis() > 0 ? Integer.MIN_VALUE : 1;
+
+ bitSameInput(i1);
+ bitSameInput(i2);
+ bitSameInput(i3);
+
+ andOrCompositionCst(i1);
+ andOrCompositionCst(i2);
+ andOrCompositionCst(i3);
+
+ andOrCompositionVar(i1, i2, i3);
+ andOrCompositionVar(i2, i3, i1);
+ andOrCompositionVar(i3, i1, i2);
+
+ shareShiftCstLeft(i1);
+ shareShiftCstLeft(i2);
+ shareShiftCstLeft(i3);
+
+ shareShiftCstRight(i1, i2);
+ shareShiftCstRight(i1, i3);
+ shareShiftCstRight(i2, i3);
+
+ shareShiftVar(i1, i2, i3);
+ shareShiftVar(i2, i3, i1);
+ shareShiftVar(i3, i1, i2);
+
+ composeTests(i1);
+ composeTests(i2);
+ composeTests(i3);
+ }
+
+ @NeverInline
+ private static void bitSameInput(int a) {
+ // a & a => a, a | a => a.
+ System.out.println(a & a);
+ System.out.println(a | a);
+ // a ^ a => 0, a - a => 0
+ System.out.println(a ^ a);
+ System.out.println(a - a);
+ }
+
+ @NeverInline
+ private static void shareShiftCstRight(int a, int b) {
+ // (x shift: val) | (y shift: val) => (x | y) shift: val.
+ // (x shift: val) & (y shift: val) => (x & y) shift: val.
+ System.out.println((a >> 3) | (b >> 3));
+ System.out.println((a >> 3) & (b >> 3));
+ System.out.println((a << 3) | (b << 3));
+ System.out.println((a << 3) & (b << 3));
+ System.out.println((a >>> 3) | (b >>> 3));
+ System.out.println((a >>> 3) & (b >>> 3));
+ }
+
+ @NeverInline
+ private static void shareShiftCstLeft(int a) {
+ // (x shift: val) | (y shift: val) => (x | y) shift: val.
+ // (x shift: val) & (y shift: val) => (x & y) shift: val.
+ System.out.println((111 >> a) | (222 >> a));
+ System.out.println((112 >> a) & (223 >> a));
+ System.out.println((113 << a) | (224 << a));
+ System.out.println((114 << a) & (225 << a));
+ System.out.println((115 >>> a) | (226 >>> a));
+ System.out.println((116 >>> a) & (227 >>> a));
+ }
+
+ @NeverInline
+ private static void shareShiftVar(int a, int b, int c) {
+ // (x shift: val) | (y shift: val) => (x | y) shift: val.
+ // (x shift: val) & (y shift: val) => (x & y) shift: val.
+ System.out.println((a >> c) | (b >> c));
+ System.out.println((a >> c) & (b >> c));
+ System.out.println((a << c) | (b << c));
+ System.out.println((a << c) & (b << c));
+ System.out.println((a >>> c) | (b >>> c));
+ System.out.println((a >>> c) & (b >>> c));
+ }
+
+ @NeverInline
+ private static void andOrCompositionCst(int a) {
+ // For all permutations of & and |, represented by &| and |&.
+ // (x &| a) |& (x &| b) => x &| (a |& b).
+ // Note that here a and b are constants therefore should be resolved at compile-time.
+ System.out.println((a & 0b10101010) | (a & 0b11110000));
+ System.out.println((a & 0b10101010) & (a & 0b11110000));
+ System.out.println((a | 0b10101010) & (a | 0b11110000));
+ System.out.println((a | 0b10101010) | (a | 0b11110000));
+ }
+
+ @NeverInline
+ private static void andOrCompositionVar(int a, int b, int c) {
+ // For all permutations of & and |.
+ // (x &| a) |& (x &| b) => x &| (a |& b).
+ System.out.println((a & b) | (a & c));
+ System.out.println((a & b) & (a & c));
+ System.out.println((a | b) & (a | c));
+ System.out.println((a | b) | (a | c));
+ }
+
+ @NeverInline
+ private static void composeTests(int dirty) {
+ // This is rewritten to ((0b0001111111111110 & dirty) >> 3).
+ System.out.println(
+ ((0b1110 & dirty) >> 3)
+ | ((0b01110000 & dirty) >> 3)
+ | ((0b001110000000 & dirty) >> 3)
+ | ((0b0001110000000000 & dirty) >> 3));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingBoolTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingBoolTest.java
new file mode 100644
index 0000000..cda7825
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingBoolTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+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 IdentityAbsorbingBoolTest extends TestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "true", "true", "true", "true", "true", "true", "true", "true", "true", "true", "true",
+ "true", "true", "true", "true", "false", "false", "false", "true", "true", "true",
+ "false", "false", "true", "true", "false", "false", "false", "false", "false", "false",
+ "false", "false", "false", "false", "false", "false", "false", "false", "false", "false",
+ "false", "false", "true", "true", "true", "false", "false", "true", "true");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public IdentityAbsorbingBoolTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ inspector
+ .clazz(Main.class)
+ .forAllMethods(
+ m ->
+ assertTrue(
+ m.streamInstructions()
+ .noneMatch(i -> i.isIntLogicalBinop() || i.isIntArithmeticBinop())));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ boolTests(System.currentTimeMillis() > 0);
+ boolTests(System.currentTimeMillis() < 0);
+ }
+
+ private static void boolTests(boolean val) {
+ identityDoubleBoolTest(val);
+ identityBoolTest(val);
+ absorbingDoubleBoolTest(val);
+ absorbingBoolTest(val);
+ }
+
+ @NeverInline
+ private static void identityDoubleBoolTest(boolean val) {
+ System.out.println(val & true & true);
+ System.out.println(true & val & true);
+ System.out.println(true & true & val);
+ System.out.println(val | false | false);
+ System.out.println(false | val | false);
+ System.out.println(false | false | val);
+ System.out.println(val ^ false ^ false);
+ System.out.println(false ^ val ^ false);
+ System.out.println(false ^ false ^ val);
+ }
+
+ @NeverInline
+ private static void identityBoolTest(boolean val) {
+ System.out.println(val & true);
+ System.out.println(true & val);
+ System.out.println(val | false);
+ System.out.println(false | val);
+ System.out.println(val ^ false);
+ System.out.println(false ^ val);
+ }
+
+ @NeverInline
+ private static void absorbingDoubleBoolTest(boolean val) {
+ System.out.println(false & false & val);
+ System.out.println(false & val & false);
+ System.out.println(val & false & false);
+ System.out.println(true | true | val);
+ System.out.println(true | val | true);
+ System.out.println(val | true | true);
+ }
+
+ @NeverInline
+ private static void absorbingBoolTest(boolean val) {
+ System.out.println(false & val);
+ System.out.println(val & false);
+ System.out.println(true | val);
+ System.out.println(val | true);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
index b9c3c87..551e1b0 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
@@ -99,7 +99,12 @@
TestConsumer programConsumer = new TestConsumer();
testForD8()
.setMinApi(AndroidApiLevel.L)
- .addProgramClasses(ImmutableList.of(TestClassA.class, TestClassB.class))
+ .addProgramFiles(
+ testForD8()
+ .addProgramClasses(TestClassA.class, TestClassB.class)
+ .setMinApi(AndroidApiLevel.L)
+ .compile()
+ .writeToZip())
.addMainDexListClasses(TestClassB.class)
.setProgramConsumer(programConsumer)
.compile();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
index 7576780..29f2f79 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
@@ -80,19 +80,6 @@
});
}
- // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
- @Test
- public void testMainDexClasses() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
- runTest(
- r8FullTestBuilder ->
- r8FullTestBuilder
- .addMainDexListClasses(I.class, Provider.class, Main.class)
- .allowDiagnosticWarningMessages(),
- this::inspect);
- }
-
@Test
public void testMainDexTracing() throws Exception {
assumeTrue(parameters.isDexRuntime());
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
deleted file mode 100644
index 0b04ddc..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-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.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableSet;
-import java.util.List;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class MainDexListFromGenerateMainDexInliningSpuriousRootTest extends TestBase {
-
- private static List<ClassReference> mainDexList;
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters()
- .withDexRuntimes()
- .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
- .build();
- }
-
- @BeforeClass
- public static void setup() throws Exception {
- mainDexList =
- testForMainDexListGenerator(getStaticTemp())
- .addInnerClasses(MainDexListFromGenerateMainDexInliningSpuriousRootTest.class)
- .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
- .addMainDexRules(
- "-keep class " + Main.class.getTypeName() + " {",
- " public static void main(java.lang.String[]);",
- "}")
- .run()
- .getMainDexList();
- }
-
- public MainDexListFromGenerateMainDexInliningSpuriousRootTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
- @Test
- public void test() throws Exception {
- // The generated main dex list should contain Main (which is a root) and A (which is a direct
- // dependency of Main).
- assertEquals(2, mainDexList.size());
- assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
- assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addInliningAnnotations()
- .addKeepClassAndMembersRules(Main.class)
- .addKeepMainRule(Main2.class)
- .addMainDexListClassReferences(mainDexList)
- .addMainDexRules(
- "-keep class " + Main2.class.getTypeName() + " {",
- " public static void main(java.lang.String[]);",
- "}")
- .collectMainDexClasses()
- .enableInliningAnnotations()
- .enableNoHorizontalClassMergingAnnotations()
- .setMinApi(parameters)
- .addDontObfuscate()
- .allowDiagnosticMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
- CodeInspector inspector = compileResult.inspector();
- ClassSubject mainClassSubject = inspector.clazz(Main.class);
- assertThat(mainClassSubject, isPresent());
- MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithOriginalName("foo");
- assertThat(fooMethodSubject, isPresent());
- ClassSubject main2ClassSubject = inspector.clazz(Main2.class);
- assertThat(main2ClassSubject, isPresent());
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
- MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithOriginalName("bar");
- assertThat(barMethodSubject, isPresent());
- ClassSubject bClassSubject = inspector.clazz(B.class);
- assertThat(bClassSubject, isPresent());
- MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithOriginalName("baz");
- assertThat(bazMethodSubject, isPresent());
- assertThat(fooMethodSubject, invokesMethod(barMethodSubject));
- assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
- assertEquals(
- ImmutableSet.of(
- mainClassSubject.getFinalName(),
- main2ClassSubject.getFinalName(),
- aClassSubject.getFinalName()),
- compileResult.getMainDexClasses());
- }
-
- static class Main {
-
- public static void main(String[] args) {
- System.out.println("Main.main()");
- }
-
- static void foo() {
- A.bar();
- }
- }
-
- static class Main2 {
-
- public static void main(String[] args) {
- if (getFalse()) {
- A.bar();
- }
- }
-
- static boolean getFalse() {
- return false;
- }
- }
-
- @NoHorizontalClassMerging
- static class A {
- // Must not be inlined into Main.foo(), since that would cause B to become direct dependence of
- // Main without ending up in the main dex.
- static void bar() {
- B.baz();
- }
- }
-
- @NoHorizontalClassMerging
- static class B {
-
- @NeverInline
- static void baz() {
- System.out.println("B.baz");
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
deleted file mode 100644
index 5c61a78..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-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.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableSet;
-import java.util.List;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class MainDexListFromGenerateMainDexInliningTest extends TestBase {
-
- private static List<ClassReference> mainDexList;
-
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters()
- .withDexRuntimes()
- .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
- .build();
- }
-
- @BeforeClass
- public static void setup() throws Exception {
- mainDexList =
- testForMainDexListGenerator(getStaticTemp())
- .addInnerClasses(MainDexListFromGenerateMainDexInliningTest.class)
- .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
- .addMainDexRules(
- "-keep class " + Main.class.getTypeName() + " {",
- " public static void main(java.lang.String[]);",
- "}")
- .run()
- .getMainDexList();
- }
-
- public MainDexListFromGenerateMainDexInliningTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
- @Test
- public void test() throws Exception {
- // The generated main dex list should contain Main (which is a root) and A (which is a direct
- // dependency of Main).
- assertEquals(2, mainDexList.size());
- assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
- assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
-
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addInliningAnnotations()
- .addKeepClassAndMembersRules(Main.class)
- .addMainDexListClassReferences(mainDexList)
- .collectMainDexClasses()
- .enableInliningAnnotations()
- .enableNoHorizontalClassMergingAnnotations()
- .enableNoHorizontalClassMergingAnnotations()
- .setMinApi(parameters)
- .allowDiagnosticMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-
- CodeInspector inspector = compileResult.inspector();
- ClassSubject mainClassSubject = inspector.clazz(Main.class);
- assertThat(mainClassSubject, isPresent());
-
- MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithOriginalName("foo");
- assertThat(fooMethodSubject, isPresent());
-
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
-
- MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithOriginalName("bar");
- assertThat(barMethodSubject, isPresent());
-
- ClassSubject bClassSubject = inspector.clazz(B.class);
- assertThat(bClassSubject, isPresent());
-
- MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithOriginalName("baz");
- assertThat(bazMethodSubject, isPresent());
-
- assertThat(fooMethodSubject, invokesMethod(barMethodSubject));
- assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
-
- // The main dex classes should be the same as the input main dex list.
- assertEquals(
- ImmutableSet.of(mainClassSubject.getFinalName(), aClassSubject.getFinalName()),
- compileResult.getMainDexClasses());
- }
-
- static class Main {
-
- public static void main(String[] args) {
- System.out.println("Main.main()");
- }
-
- static void foo() {
- // Should not allow inlining bar into foo(), since that adds B as a direct
- // dependence, and we don't include the direct dependencies of main dex list classes.
- A.bar();
- }
- }
-
- @NoHorizontalClassMerging
- static class A {
-
- static void bar() {
- B.baz();
- }
- }
-
- @NoHorizontalClassMerging
- static class B {
-
- @NeverInline
- static void baz() {
- System.out.println("B.baz");
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
deleted file mode 100644
index 5a4d15c..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-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.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableSet;
-import java.util.List;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class MainDexListFromGenerateMainDexInliningWithTracingTest extends TestBase {
-
- private static List<ClassReference> mainDexList;
-
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters()
- .withDexRuntimes()
- .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
- .build();
- }
-
- @BeforeClass
- public static void setup() throws Exception {
- mainDexList =
- testForMainDexListGenerator(getStaticTemp())
- .addInnerClasses(MainDexListFromGenerateMainDexInliningWithTracingTest.class)
- .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
- .addMainDexRules(
- "-keep class " + Main.class.getTypeName() + " {",
- " public static void main(java.lang.String[]);",
- "}")
- .run()
- .getMainDexList();
- }
-
- public MainDexListFromGenerateMainDexInliningWithTracingTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
- @Test
- public void test() throws Exception {
- // The generated main dex list should contain Main (which is a root) and A (which is a direct
- // dependency of Main).
- assertEquals(2, mainDexList.size());
- assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
- assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
-
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addInliningAnnotations()
- .addKeepClassAndMembersRules(Main.class)
- .addMainDexListClassReferences(mainDexList)
- .addMainDexRules(
- "-keep class " + Main.class.getTypeName() + " {",
- " public static void foo(java.lang.String[]);",
- "}")
- .collectMainDexClasses()
- .enableInliningAnnotations()
- .enableNoHorizontalClassMergingAnnotations()
- .addDontObfuscate()
- .setMinApi(parameters)
- .allowDiagnosticMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-
- CodeInspector inspector = compileResult.inspector();
- ClassSubject mainClassSubject = inspector.clazz(Main.class);
- assertThat(mainClassSubject, isPresent());
-
- MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithOriginalName("foo");
- assertThat(fooMethodSubject, isPresent());
-
- MethodSubject notCalledAtStartupMethodSubject =
- mainClassSubject.uniqueMethodWithOriginalName("notCalledAtStartup");
- assertThat(notCalledAtStartupMethodSubject, isPresent());
-
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
-
- MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithOriginalName("bar");
- assertThat(barMethodSubject, isPresent());
-
- ClassSubject bClassSubject = inspector.clazz(B.class);
- assertThat(bClassSubject, isPresent());
-
- MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithOriginalName("baz");
- assertThat(bazMethodSubject, isPresent());
-
- assertThat(notCalledAtStartupMethodSubject, invokesMethod(barMethodSubject));
- assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
-
- // The main dex classes should be the same as the input main dex list.
- assertEquals(
- ImmutableSet.of(mainClassSubject.getFinalName(), aClassSubject.getFinalName()),
- compileResult.getMainDexClasses());
- }
-
- static class Main {
-
- public static void main(String[] args) {
- System.out.println("Main.main()");
- }
-
- public static void notCalledAtStartup() {
- // Should not allow inlining bar into notCalledAtStartup(), since that adds B as a direct
- // dependence, and we don't include the direct dependencies of main dex list classes.
- new A().bar();
- }
-
- // This method is traced for main dex when running with R8, to add A as a dependency.
- static A foo(A a) {
- if (a != null) {
- System.out.println("Hello World");
- }
- return a;
- }
- }
-
- @NoHorizontalClassMerging
- static class A {
-
- static void bar() {
- B.baz();
- }
- }
-
- @NoHorizontalClassMerging
- static class B {
-
- @NeverInline
- static void baz() {
- System.out.println("B.baz");
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
index 97d9c79..0125e03 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
@@ -38,7 +38,6 @@
@RunWith(Parameterized.class)
public class MainDexListFromGenerateMainHorizontalMergingTest extends TestBase {
- private static List<ClassReference> mainDexList;
private final TestParameters parameters;
@Parameters(name = "{0}")
@@ -49,36 +48,10 @@
.build();
}
- @BeforeClass
- public static void setup() throws Exception {
- mainDexList =
- testForMainDexListGenerator(getStaticTemp())
- .addInnerClasses(MainDexListFromGenerateMainHorizontalMergingTest.class)
- .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
- .addMainDexRules(
- "-keep class " + Main.class.getTypeName() + " { public static void foo(); }")
- .run()
- .getMainDexList();
- }
-
public MainDexListFromGenerateMainHorizontalMergingTest(TestParameters parameters) {
this.parameters = parameters;
}
- // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
- @Test
- public void testMainDexList() throws Exception {
- assertEquals(3, mainDexList.size());
- Set<String> mainDexReferences =
- mainDexList.stream().map(TypeReference::getTypeName).collect(Collectors.toSet());
- assertTrue(mainDexReferences.contains(A.class.getTypeName()));
- assertTrue(mainDexReferences.contains(B.class.getTypeName()));
- assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
- runTest(
- builder ->
- builder.addMainDexListClassReferences(mainDexList).allowDiagnosticWarningMessages());
- }
-
@Test
public void testMainDexTracing() throws Exception {
runTest(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
index 4dba7f0..6ad455d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
@@ -37,8 +37,6 @@
@RunWith(Parameterized.class)
public class MainDexListFromGenerateMainVerticalMergingTest extends TestBase {
- private static List<ClassReference> mainDexList;
-
private final TestParameters parameters;
@Parameters(name = "{0}")
@@ -49,36 +47,10 @@
.build();
}
- @BeforeClass
- public static void setup() throws Exception {
- mainDexList =
- testForMainDexListGenerator(getStaticTemp())
- .addInnerClasses(MainDexListFromGenerateMainVerticalMergingTest.class)
- .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
- .addMainDexRules(
- "-keep class " + Main.class.getTypeName() + " { public static void foo(); }")
- .run()
- .getMainDexList();
- }
-
public MainDexListFromGenerateMainVerticalMergingTest(TestParameters parameters) {
this.parameters = parameters;
}
- // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
- @Test
- public void testMainDexList() throws Exception {
- assertEquals(3, mainDexList.size());
- Set<String> mainDexReferences =
- mainDexList.stream().map(TypeReference::getTypeName).collect(Collectors.toSet());
- assertTrue(mainDexReferences.contains(A.class.getTypeName()));
- assertTrue(mainDexReferences.contains(B.class.getTypeName()));
- assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
- runTest(
- builder ->
- builder.addMainDexListClassReferences(mainDexList).allowDiagnosticWarningMessages());
- }
-
@Test
public void testMainTracing() throws Exception {
runTest(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
deleted file mode 100644
index 5e24bcf..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-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 MainDexListInliningTest extends TestBase {
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters()
- .withDexRuntimes()
- .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
- .build();
- }
-
- public MainDexListInliningTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- // TODO(b/181858113): This test should be converted to a main-dex-rules test.
- @Test
- public void test() throws Exception {
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addKeepMainRule(Main.class)
- .addMainDexListClasses(Main.class)
- .collectMainDexClasses()
- .enableNoHorizontalClassMergingAnnotations()
- .setMinApi(parameters)
- .allowDiagnosticMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-
- CodeInspector inspector = compileResult.inspector();
-
- ClassSubject mainClassSubject = inspector.clazz(Main.class);
- assertThat(mainClassSubject, isPresent());
-
- // A is not allowed to be inlined and is therefore present.
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
-
- // B should be referenced from Main.main.
- ClassSubject bClassSubject = inspector.clazz(B.class);
- assertThat(bClassSubject, isPresent());
-
- compileResult.inspectMainDexClasses(
- mainDexClasses -> {
- assertTrue(mainDexClasses.contains(mainClassSubject.getFinalName()));
- // Since we passed a main-dex list the traced references A and B are not automagically
- // included.
- assertFalse(mainDexClasses.contains(aClassSubject.getFinalName()));
- assertFalse(mainDexClasses.contains(bClassSubject.getFinalName()));
- });
- }
-
- static class Main {
-
- public static void main(String[] args) {
- // Should be inlined.
- A.m();
- }
- }
-
- @NoHorizontalClassMerging
- static class A {
-
- public static void m() {
- System.out.println(B.class);
- }
- }
-
- @NoHorizontalClassMerging
- static class B {}
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
deleted file mode 100644
index a26fe55..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-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 MainDexListNoDirectDependenciesTest extends TestBase {
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters()
- .withDexRuntimes()
- .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
- .build();
- }
-
- public MainDexListNoDirectDependenciesTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
- @Test
- public void test() throws Exception {
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addMainDexListClasses(A.class)
- .addMainDexKeepClassRules(B.class)
- .collectMainDexClasses()
- .noTreeShaking()
- .setMinApi(parameters)
- .allowDiagnosticMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-
- CodeInspector inspector = compileResult.inspector();
-
- ClassSubject aClassSubject = inspector.clazz(A.class);
- ClassSubject referencedFromAClassSubject = inspector.clazz(ReferencedFromA.class);
- ClassSubject bClassSubject = inspector.clazz(B.class);
- ClassSubject referencedFromBClassSubject = inspector.clazz(ReferencedFromB.class);
-
- compileResult.inspectMainDexClasses(
- mainDexClasses -> {
- assertTrue(mainDexClasses.contains(aClassSubject.getFinalName()));
- // It is assumed that the provided main dex list includes its direct dependencies.
- // Therefore, we explicitly do not include the direct dependencies of the main dex list
- // classes in the final main dex, since this would lead to the dependencies of the
- // dependencies being included in the main dex.
- assertFalse(mainDexClasses.contains(referencedFromAClassSubject.getFinalName()));
- assertTrue(mainDexClasses.contains(bClassSubject.getFinalName()));
- assertTrue(mainDexClasses.contains(referencedFromBClassSubject.getFinalName()));
- });
- }
-
- static class A {
-
- public void m() {
- System.out.println(ReferencedFromA.class);
- }
- }
-
- static class ReferencedFromA {}
-
- static class B {
-
- public void m() {
- System.out.println(ReferencedFromB.class);
- }
- }
-
- static class ReferencedFromB {}
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 0da2423..98a3739 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -10,16 +10,14 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.D8;
import com.android.tools.r8.D8Command;
+import com.android.tools.r8.D8TestBuilder;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.DiagnosticsMatcher;
import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.StringResource;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -566,16 +564,14 @@
originalInspector.clazz(clazz).isPresent());
}
Path outDir = temp.newFolder().toPath();
- R8Command.Builder builder =
- R8Command.builder(handler)
+ D8Command.Builder builder =
+ D8Command.builder(handler)
.addProgramFiles(app)
.setMode(
minimalMainDex && mainDex.size() > 0
? CompilationMode.DEBUG
: CompilationMode.RELEASE)
- .setOutput(outDir, OutputMode.DexIndexed)
- .setDisableTreeShaking(true)
- .setDisableMinification(true);
+ .setOutput(outDir, OutputMode.DexIndexed);
switch (testMode) {
case SINGLE_FILE:
@@ -619,7 +615,7 @@
}
}
- ToolHelper.runR8(builder.build());
+ D8.run(builder.build());
if (!singleDexApp && !minimalMainDex) {
assertTrue("Output run only produced one dex file.",
1 < Files.list(outDir).filter(FileUtils::isDexFile).count());
@@ -671,7 +667,9 @@
private static void generateApplication(Path output, List<String> classes, int methodCount)
throws IOException {
- ArchiveConsumer consumer = new ArchiveConsumer(output);
+ // Translate and compile the app to DEX code as main-dex list requires DEX inputs.
+ D8TestBuilder builder =
+ testForD8(getStaticTemp(), Backend.DEX).setOutputMode(OutputMode.DexFilePerClass);
for (String typename : classes) {
String descriptor = DescriptorUtils.javaTypeToDescriptor(typename);
byte[] bytes =
@@ -702,9 +700,13 @@
}
})
.transform();
- consumer.accept(ByteDataView.of(bytes), descriptor, null);
+ builder.addProgramClassFileData(bytes);
}
- consumer.finished(null);
+ try {
+ builder.compile().writeToZip(output);
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException(e);
+ }
}
// Simple stub/template for generating the input classes.
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
index a5a07c4..8ce0413 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
@@ -48,15 +48,6 @@
testMainDex(builder -> {}, Assert::assertNull);
}
- // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
- @Test
- public void testMainDexClassesList() throws Exception {
- assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
- testMainDex(
- builder -> builder.addMainDexListClasses(Main.class).allowDiagnosticWarningMessages(),
- mainDexClasses -> assertEquals(ImmutableSet.of(Main.class.getTypeName()), mainDexClasses));
- }
-
@Test
public void testMainDexTracing() throws Exception {
assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java
deleted file mode 100644
index ae91c3d..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java
+++ /dev/null
@@ -1,76 +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.maindexlist;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class MainDexRulesAndListD8 extends TestBase {
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().withApiLevelsWithoutNativeMultiDex().build();
- }
-
- public MainDexRulesAndListD8(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- private static Path testDir;
- private static Path mainDexRules;
- private static Path mainDexList;
-
- @BeforeClass
- public static void setUp() throws Exception {
- testDir = getStaticTemp().newFolder().toPath();
- mainDexRules = testDir.resolve("main-dex-rules");
- mainDexList = testDir.resolve("main-dex-list");
- FileUtils.writeTextFile(mainDexRules, ImmutableList.of("-keep class " + A.class.getTypeName()));
- FileUtils.writeTextFile(
- mainDexList, ImmutableList.of(B.class.getTypeName().replace('.', '/') + ".class"));
- }
-
- @Test
- public void test() throws Exception {
- Path result =
- testForD8(parameters.getBackend())
- .setMinApi(parameters)
- .addInnerClasses(getClass())
- .addMainDexRulesFiles(mainDexRules)
- .addMainDexListFiles(mainDexList)
- .debug()
- .compile()
- .writeToZip();
- List<Path> dexFiles =
- ZipUtils.unzip(result, testDir).stream().sorted().collect(Collectors.toList());
- assertEquals(
- classNamesFromDexFile(dexFiles.get(0)).stream().sorted().collect(Collectors.toList()),
- ImmutableList.of(A.class.getTypeName(), B.class.getTypeName()));
- assertEquals(
- classNamesFromDexFile(dexFiles.get(1)).stream().sorted().collect(Collectors.toList()),
- ImmutableList.of(C.class.getTypeName()));
- }
-
- static class A {}
-
- static class B {}
-
- static class C {}
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index 1f980f3..2cc6ad2 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -6,12 +6,13 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static org.hamcrest.CoreMatchers.allOf;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestDiagnosticMessages;
@@ -125,72 +126,35 @@
* determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
* included in the main-dex file.
*
- * <p>TODO(b/181858113): Update to assert an error is raised once deprecated period is over.
+ * <p>Now that b/181858113 is resolved to disallow this API usage, this test just checks the error
+ * is reported.
*/
@Test
public void testDeprecatedSyntheticsFromMainDexListD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
Path mainDexFile = temp.newFile("maindex.list").toPath();
FileUtils.writeTextFile(mainDexFile, binaryName(A.class) + ".class");
- D8TestCompileResult compileResult =
- testForD8()
- .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
- .addMainDexListClasses(TestClass.class)
- .addMainDexListFiles(mainDexFile)
- .setMinApi(parameters)
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- // The "classes" addition has no origin.
- allOf(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
- diagnosticOrigin(Origin.unknown())),
- // The "file" addition must have the file origin.
- allOf(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
- diagnosticOrigin(new PathOrigin(mainDexFile)))));
- checkCompilationResult(compileResult);
- }
-
- /**
- * This test checks for maintained support of including synthetics from main-dex-list entries in
- * the main-dex file. This test simulates that the tracing done at the class-file level has
- * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
- * included in the main-dex file.
- *
- * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed.
- */
- @Test
- public void testDeprecatedSyntheticsFromMainDexListR8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- Path mainDexFile = temp.newFile("maindex.list").toPath();
- FileUtils.writeTextFile(mainDexFile, binaryName(A.class) + ".class");
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
- .setMinApi(parameters)
- .addOptionsModification(o -> o.minimalMainDex = true)
- .addMainDexListClasses(TestClass.class)
- .addMainDexListFiles(mainDexFile)
- .addDontObfuscate()
- .noTreeShaking()
- .allowDiagnosticWarningMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- // The "classes" addition has no origin.
- allOf(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
- diagnosticOrigin(Origin.unknown())),
- // The "file" addition must have the file origin.
- allOf(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
- diagnosticOrigin(new PathOrigin(mainDexFile)))));
- checkCompilationResult(compileResult, compileResult.app);
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8()
+ .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+ .addMainDexListClasses(TestClass.class)
+ .addMainDexListFiles(mainDexFile)
+ .setMinApi(parameters)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(
+ // The "classes" addition has no origin.
+ allOf(
+ diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
+ diagnosticOrigin(Origin.unknown())),
+ // The "file" addition must have the file origin.
+ allOf(
+ diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
+ diagnosticOrigin(new PathOrigin(mainDexFile))))));
}
private void checkCompilationResult(D8TestCompileResult compileResult) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
index cb7e981..ecd91e7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
@@ -4,13 +4,19 @@
package com.android.tools.r8.maindexlist.warnings;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
@@ -61,7 +67,7 @@
.assertNoMessages();
}
- @Test
+ @Test(expected = CompilationFailedException.class)
public void testWarningFromManualMainDexList() throws Exception {
testForR8(parameters.getBackend())
.setMinApi(AndroidApiLevel.K)
@@ -72,15 +78,21 @@
.addMainDexListClasses(Static.class)
.allowDiagnosticWarningMessages()
.setMinApi(parameters)
- .compile()
- .inspect(this::classStaticGone)
- .assertOnlyWarnings()
- .assertWarningMessageThatMatches(containsString("Application does not contain"))
- .assertWarningMessageThatMatches(containsString(Static.class.getTypeName()))
- .assertWarningMessageThatMatches(containsString("as referenced in main-dex-list"));
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics
+ .assertNoInfos()
+ .assertErrorsMatch(diagnosticType(UnsupportedMainDexListUsageDiagnostic.class))
+ .assertWarningsMatch(
+ allOf(
+ diagnosticMessage(containsString("Application does not contain")),
+ diagnosticMessage(containsString(Static.class.getTypeName())),
+ diagnosticMessage(containsString("as referenced in main-dex-list")),
+ not(diagnosticMessage(containsString(Static2.class.getTypeName())))));
+ });
}
- @Test
+ @Test(expected = CompilationFailedException.class)
public void testWarningFromManualMainDexListWithRuleAsWell() throws Exception {
testForR8(parameters.getBackend())
.setMinApi(AndroidApiLevel.K)
@@ -93,13 +105,18 @@
.addDontWarn(Static.class)
.allowDiagnosticWarningMessages()
.setMinApi(parameters)
- .compile()
- .inspect(this::classStaticGone)
- .assertOnlyWarnings()
- .assertWarningMessageThatMatches(containsString("Application does not contain"))
- .assertWarningMessageThatMatches(containsString(Static.class.getTypeName()))
- .assertWarningMessageThatMatches(containsString("as referenced in main-dex-list"))
- .assertNoWarningMessageThatMatches(containsString(Static2.class.getTypeName()));
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics
+ .assertNoInfos()
+ .assertErrorsMatch(diagnosticType(UnsupportedMainDexListUsageDiagnostic.class))
+ .assertWarningsMatch(
+ allOf(
+ diagnosticMessage(containsString("Application does not contain")),
+ diagnosticMessage(containsString(Static.class.getTypeName())),
+ diagnosticMessage(containsString("as referenced in main-dex-list")),
+ not(diagnosticMessage(containsString(Static2.class.getTypeName())))));
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
index 584814b..acd0d3f 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
@@ -49,8 +49,8 @@
: parameters.getRuntime().maxSupportedApiLevel();
}
- public boolean isLibraryClassAlwaysPresent(boolean isDesugaring) {
- return !isDesugaring || parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
+ public boolean isLibraryClassAlwaysPresent(boolean isApiOutlining) {
+ return !isApiOutlining || parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
}
public boolean isLibraryClassPresentInCurrentRuntime() {
@@ -114,7 +114,7 @@
private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector)
throws Exception {
- inspect(profileInspector, inspector, isLibraryClassAlwaysPresent(true));
+ inspect(profileInspector, inspector, isLibraryClassAlwaysPresent(parameters.isDexRuntime()));
}
private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector)
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
index 8554ae3..b8db6f5 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.desugar.LibraryFilesHelper.getJdk11LibraryFiles;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -111,11 +112,6 @@
.build();
}
- private boolean hasTwrCloseResourceSupport(boolean isDesugaring) {
- return !isDesugaring
- || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithTwrCloseResourceSupport());
- }
-
private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) {
inspect(profileInspector, inspector, hasTwrCloseResourceSupport(true));
}
@@ -128,10 +124,17 @@
ArtProfileInspector profileInspector,
CodeInspector inspector,
boolean hasTwrCloseResourceSupport) {
+ int expectedClassCount = 3;
+ if (!hasTwrCloseResourceSupport) {
+ expectedClassCount += 8;
+ }
+ if (hasTwrCloseResourceApiOutlines()) {
+ expectedClassCount += 4;
+ }
inspector
.allClasses()
.forEach(c -> System.out.println(c.getDexProgramClass().toSourceString()));
- assertEquals(hasTwrCloseResourceSupport ? 3 : 15, inspector.allClasses().size());
+ assertEquals(expectedClassCount, inspector.allClasses().size());
assertThat(inspector.clazz(MAIN.typeName()), isPresent());
// Class Foo has two methods foo() and $closeResource().
@@ -167,49 +170,60 @@
ClassSubject syntheticApiOutlineClassSubject0 =
inspector.clazz(
SyntheticItemsTestUtils.syntheticApiOutlineClass(clazz.getClassReference(), 0));
- assertThat(syntheticApiOutlineClassSubject0, notIf(isPresent(), hasTwrCloseResourceSupport));
+ assertThat(syntheticApiOutlineClassSubject0, isPresentIf(hasTwrCloseResourceApiOutlines()));
ClassSubject syntheticApiOutlineClassSubject1 =
inspector.clazz(
SyntheticItemsTestUtils.syntheticApiOutlineClass(clazz.getClassReference(), 1));
- assertThat(syntheticApiOutlineClassSubject1, notIf(isPresent(), hasTwrCloseResourceSupport));
+ assertThat(syntheticApiOutlineClassSubject1, isPresentIf(hasTwrCloseResourceApiOutlines()));
+
+ int initialSyntheticId = hasTwrCloseResourceApiOutlines() ? 2 : 0;
ClassSubject syntheticBackportClassSubject =
inspector.clazz(
- SyntheticItemsTestUtils.syntheticBackportClass(clazz.getClassReference(), 2));
+ SyntheticItemsTestUtils.syntheticBackportClass(
+ clazz.getClassReference(), initialSyntheticId));
assertThat(syntheticBackportClassSubject, notIf(isPresent(), hasTwrCloseResourceSupport));
ClassSubject syntheticTwrCloseResourceClassSubject3 =
inspector.clazz(
- SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(clazz.getClassReference(), 3));
+ SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(
+ clazz.getClassReference(), initialSyntheticId + 1));
assertThat(
syntheticTwrCloseResourceClassSubject3, notIf(isPresent(), hasTwrCloseResourceSupport));
ClassSubject syntheticTwrCloseResourceClassSubject4 =
inspector.clazz(
- SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(clazz.getClassReference(), 4));
+ SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(
+ clazz.getClassReference(), initialSyntheticId + 2));
assertThat(
syntheticTwrCloseResourceClassSubject4, notIf(isPresent(), hasTwrCloseResourceSupport));
ClassSubject syntheticTwrCloseResourceClassSubject5 =
inspector.clazz(
- SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(clazz.getClassReference(), 5));
+ SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(
+ clazz.getClassReference(), initialSyntheticId + 3));
assertThat(
syntheticTwrCloseResourceClassSubject5, notIf(isPresent(), hasTwrCloseResourceSupport));
profileInspector.applyIf(
+ hasTwrCloseResourceApiOutlines(),
+ i ->
+ i.assertContainsClassRules(
+ syntheticApiOutlineClassSubject0, syntheticApiOutlineClassSubject1)
+ .assertContainsMethodRules(
+ syntheticApiOutlineClassSubject0.uniqueMethod(),
+ syntheticApiOutlineClassSubject1.uniqueMethod()));
+
+ profileInspector.applyIf(
!hasTwrCloseResourceSupport,
i ->
i.assertContainsClassRules(
- syntheticApiOutlineClassSubject0,
- syntheticApiOutlineClassSubject1,
syntheticBackportClassSubject,
syntheticTwrCloseResourceClassSubject3,
syntheticTwrCloseResourceClassSubject4,
syntheticTwrCloseResourceClassSubject5)
.assertContainsMethodRules(
- syntheticApiOutlineClassSubject0.uniqueMethod(),
- syntheticApiOutlineClassSubject1.uniqueMethod(),
syntheticBackportClassSubject.uniqueMethod(),
syntheticTwrCloseResourceClassSubject3.uniqueMethod(),
syntheticTwrCloseResourceClassSubject4.uniqueMethod(),
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
deleted file mode 100644
index 170708d..0000000
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.repackage;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
-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.OutputMode;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-// TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-@RunWith(Parameterized.class)
-public class RepackageWithMainDexListTest extends RepackageTestBase {
-
- @Parameters(name = "{1}, kind: {0}")
- public static List<Object[]> data() {
- return buildParameters(
- ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
- getTestParameters()
- .withDexRuntimes()
- .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
- .build());
- }
-
- public RepackageWithMainDexListTest(
- String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
- super(flattenPackageHierarchyOrRepackageClasses, parameters);
- }
-
- @Test
- public void test() throws Exception {
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- // -keep,allowobfuscation does not prohibit repackaging.
- .addKeepClassRulesWithAllowObfuscation(TestClass.class, OtherTestClass.class)
- .addKeepRules(
- "-keepclassmembers class " + TestClass.class.getTypeName() + " { <methods>; }")
- // Add a class that will be repackaged to the main dex list.
- .addMainDexListClasses(TestClass.class)
- .apply(this::configureRepackaging)
- // Debug mode to enable minimal main dex.
- .debug()
- .setMinApi(parameters)
- .allowDiagnosticMessages()
- .compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics
- .assertOnlyWarnings()
- .assertWarningsMatch(
- diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)))
- .apply(this::checkCompileResult)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello world!");
- }
-
- private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
- Path out = temp.newFolder().toPath();
- compileResult.app.writeToDirectory(out, OutputMode.DexIndexed);
- Path classes = out.resolve("classes.dex");
- Path classes2 = out.resolve("classes2.dex");
- inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()));
- inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
- }
-
- private void inspectMainDex(CodeInspector inspector) {
- assertThat(inspector.clazz(TestClass.class), isPresent());
- assertThat(inspector.clazz(OtherTestClass.class), not(isPresent()));
- }
-
- private void inspectSecondaryDex(CodeInspector inspector) {
- assertThat(inspector.clazz(TestClass.class), not(isPresent()));
- assertThat(inspector.clazz(OtherTestClass.class), isPresent());
- }
-
- static class TestClass {
-
- public static void main(String[] args) {
- System.out.println("Hello world!");
- }
- }
-
- static class OtherTestClass {}
-}
diff --git a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
index 0423904..da2771f 100644
--- a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.debug.CfDebugTestConfig;
-import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -199,7 +198,7 @@
}
public JvmTestBuilder addAndroidBuildVersion() {
- return addAndroidBuildVersion(AndroidApiLevel.MASTER);
+ return addAndroidBuildVersion(AndroidApiLevel.MAIN);
}
public JvmTestBuilder addAndroidBuildVersion(AndroidApiLevel apiLevel) {
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
index 3993296..4caf75f 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -130,6 +130,10 @@
public T addAndroidBuildVersion(AndroidApiLevel specifiedApiLevel) {
addProgramClasses(AndroidBuildVersion.class);
+ return markAndroidBuildVersionAsActive(specifiedApiLevel);
+ }
+
+ public T markAndroidBuildVersionAsActive(AndroidApiLevel specifiedApiLevel) {
isAndroidBuildVersionAdded =
Optional.ofNullable(specifiedApiLevel == null ? null : specifiedApiLevel.getLevel());
return self();
@@ -619,7 +623,10 @@
GlobalSyntheticsGeneratorCommand.builder()
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
.setMinApiLevel(minApiLevel)
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setGlobalSyntheticsConsumer(
+ (data, context, handler) -> {
+ // Ignore the data and context, callback is hit below.
+ })
.build();
InternalOptions internalOptions = command.getInternalOptions();
internalOptions.testing.globalSyntheticCreatedCallback =
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index d3481a6..9eea7dd 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -211,9 +211,6 @@
public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.6.jar";
public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.6.jar";
- public static final Path API_SAMPLE_JAR =
- Paths.get(getProjectRoot(), "tests", "r8_api_usage_sample.jar");
-
public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
public static final String CLASSPATH_SEPARATOR = File.pathSeparator;
@@ -1230,8 +1227,8 @@
}
private static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
- if (apiLevel == AndroidApiLevel.MASTER) {
- return Paths.get(THIRD_PARTY_DIR + "android_jar/lib-master/android.jar");
+ if (apiLevel == AndroidApiLevel.MAIN) {
+ return Paths.get(THIRD_PARTY_DIR + "android_jar/lib-main/android.jar");
}
String jar = String.format(
ANDROID_JAR_PATTERN,
@@ -1401,7 +1398,7 @@
public static AndroidApiLevel getMinApiLevelForDexVm(DexVm dexVm) {
switch (dexVm.version) {
case MASTER:
- return AndroidApiLevel.MASTER;
+ return AndroidApiLevel.MAIN;
case V14_0_0:
return AndroidApiLevel.U;
case V13_0_0:
@@ -1433,7 +1430,7 @@
public static DexVm.Version getDexVersionForApiLevel(AndroidApiLevel apiLevel) {
switch (apiLevel) {
- case MASTER:
+ case MAIN:
return DexVm.Version.MASTER;
case U:
return DexVm.Version.V14_0_0;
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
deleted file mode 100644
index 61fd5f8..0000000
--- a/tests/r8_api_usage_sample.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/android_jar/lib-main.tar.gz.sha1 b/third_party/android_jar/lib-main.tar.gz.sha1
new file mode 100644
index 0000000..7ef1faa
--- /dev/null
+++ b/third_party/android_jar/lib-main.tar.gz.sha1
@@ -0,0 +1 @@
+9cff91a12817179c3058b79e22934b3ef71971fc
\ No newline at end of file
diff --git a/third_party/android_jar/lib-master.tar.gz.sha1 b/third_party/android_jar/lib-master.tar.gz.sha1
deleted file mode 100644
index 717d805..0000000
--- a/third_party/android_jar/lib-master.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a759a6727431a73b4f461ed40aa5bfbc79ebc172
\ No newline at end of file
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index 009f4e4..32f7368 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-9de592304071fa794e07e9e8d955aa4ae62cdef1
\ No newline at end of file
+34436ff2aee451c7d8e5c0f28193134822238d4b
\ No newline at end of file
diff --git a/third_party/gmail/gmail_android_180826.15.tar.gz.sha1 b/third_party/gmail/gmail_android_180826.15.tar.gz.sha1
deleted file mode 100644
index 3789746..0000000
--- a/third_party/gmail/gmail_android_180826.15.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f3770f048210068c1920b10ab5d09b19da3a8ea7
\ No newline at end of file
diff --git a/tools/compare_apk_sizes.py b/tools/compare_apk_sizes.py
index 6349689..5ed768b 100755
--- a/tools/compare_apk_sizes.py
+++ b/tools/compare_apk_sizes.py
@@ -112,7 +112,7 @@
entry.set_size(False)
file_info_map[file_path] = entry
threads = []
- file_infos = file_info_map.values() if options.use_code_size else []
+ file_infos = list(file_info_map.values()) if options.use_code_size else []
while len(file_infos) > 0 or len(threads) > 0:
for t in threads:
if not t.is_alive():
diff --git a/tools/d8.py b/tools/d8.py
index aab63ed..636b7e7 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -23,6 +23,11 @@
help='Print the line \'<BENCHMARKNAME>(RunTimeRaw):' +
' <elapsed> ms\' at the end where <elapsed> is' +
' the elapsed time in milliseconds.')
+ parser.add_option('--no-build',
+ '--no_build',
+ help='Do not build D8',
+ default=False,
+ action='store_true')
parser.add_option('--version', help='Version of D8 to use.', default=None)
parser.add_option('--tag', help='Tag of D8 to use.', default=None)
return parser.parse_args(argv)
@@ -34,6 +39,7 @@
time_consumer = lambda duration: print_duration(duration, options)
return toolhelper.run('d8',
d8_args,
+ build=not options.no_build,
jar=utils.find_r8_jar_from_options(options),
main='com.android.tools.r8.D8',
time_consumer=time_consumer)
diff --git a/tools/gmail_data.py b/tools/gmail_data.py
deleted file mode 100644
index 6170a66..0000000
--- a/tools/gmail_data.py
+++ /dev/null
@@ -1,47 +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.
-
-import glob
-import os
-import utils
-
-ANDROID_L_API = '21'
-BASE = os.path.join(utils.THIRD_PARTY, 'gmail')
-
-V180826_15_BASE = os.path.join(BASE, 'gmail_android_180826.15')
-V180826_15_PREFIX = os.path.join(V180826_15_BASE, 'Gmail_release_unstripped')
-
-# NOTE: We always use android.jar for SDK v25 for now.
-ANDROID_JAR = utils.get_android_jar(25)
-
-VERSIONS = {
- '180826.15': {
- 'dex': {
- 'flags': '--no-desugaring',
- 'inputs': [
- os.path.join(V180826_15_BASE, 'Gmail_release_unsigned.apk')
- ],
- 'main-dex-list': os.path.join(V180826_15_BASE, 'main_dex_list.txt'),
- 'pgmap': '%s_proguard.map' % V180826_15_PREFIX,
- 'libraries': [ANDROID_JAR],
- },
- 'deploy': {
- 'flags': '--no-desugaring',
- 'inputs': ['%s_deploy.jar' % V180826_15_PREFIX],
- 'pgconf': [
- '%s_proguard.config' % V180826_15_PREFIX,
- '%s/proguardsettings/Gmail_proguard.config' % utils.THIRD_PARTY,
- utils.IGNORE_WARNINGS_RULES
- ],
- 'min-api': ANDROID_L_API,
- 'allow-type-errors': 1,
- },
- 'proguarded': {
- 'flags': '--no-desugaring',
- 'inputs': ['%s_proguard.jar' % V180826_15_PREFIX],
- 'main-dex-list': os.path.join(V180826_15_BASE, 'main_dex_list.txt'),
- 'pgmap': '%s_proguard.map' % V180826_15_PREFIX,
- }
- },
-}
diff --git a/tools/historic_memory_usage.py b/tools/historic_memory_usage.py
index a40cf2a..2256e28 100755
--- a/tools/historic_memory_usage.py
+++ b/tools/historic_memory_usage.py
@@ -17,7 +17,7 @@
import sys
import utils
-APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome']
+APPS = ['gmscore', 'nest', 'youtube', 'chrome']
COMPILERS = ['d8', 'r8']
@@ -29,7 +29,6 @@
choices=COMPILERS)
result.add_option('--app',
help='What app to run on',
- default='gmail',
choices=APPS)
result.add_option('--top',
default=historic_run.top_or_default(),
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 3aaa40d..c3f8307 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -14,7 +14,6 @@
import archive
import gradle
-import gmail_data
import nest_data
from sanitize_libraries import SanitizeLibraries, SanitizeLibrariesInPgconf
import thread_utils
@@ -26,7 +25,7 @@
import chrome_data
TYPES = ['dex', 'deploy', 'proguarded']
-APPS = ['nest', 'youtube', 'gmail', 'chrome']
+APPS = ['nest', 'youtube', 'chrome']
COMPILERS = ['d8', 'r8']
COMPILER_BUILDS = ['full', 'lib']
@@ -230,7 +229,6 @@
'nest': nest_data,
'youtube': youtube_data,
'chrome': chrome_data,
- 'gmail': gmail_data,
}
# Check to ensure that we add all variants here.
assert len(APPS) == len(data_providers)
@@ -431,9 +429,6 @@
elif options.app == 'chrome':
version = options.version or '180917'
data = chrome_data
- elif options.app == 'gmail':
- version = options.version or '170604.16'
- data = gmail_data
else:
raise Exception("You need to specify '--app={}'".format('|'.join(APPS)))
return version, data