Merge commit '39d8dd70184799d651d776eaae41ff7c980e78d6' into dev-release
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
index 6107e67..c20de96 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
@@ -92,9 +92,9 @@
return;
}
if (legacy == LEGACY
- && (entry.getName().contains("java.nio.file")
+ && (entry.getName().contains("java/nio/file")
|| entry.getName().contains("ApiFlips")
- || entry.getName().contains("java.adapter"))) {
+ || entry.getName().contains("java/adapter"))) {
return;
}
final byte[] bytes = ByteStreams.toByteArray(input);
diff --git a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
index 31e0dbd..f10cf07 100644
--- a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
@@ -38,9 +38,12 @@
*/
@Keep
public class ArchiveClassFileProvider implements ClassFileResourceProvider, Closeable {
+ private final Path archive;
private final Origin origin;
- private final ZipFile zipFile;
- private final Set<String> descriptors = new HashSet<>();
+ private final Predicate<String> include;
+
+ private ZipFile lazyZipFile = null;
+ private Set<String> lazyDescriptors = null;
/**
* Creates a lazy class-file program-resource provider.
@@ -59,36 +62,23 @@
*/
public ArchiveClassFileProvider(Path archive, Predicate<String> include) throws IOException {
assert isArchive(archive);
+ this.archive = archive;
+ this.include = include;
origin = new PathOrigin(archive);
- try {
- zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8);
- } catch (IOException e) {
- if (!Files.exists(archive)) {
- throw new NoSuchFileException(archive.toString());
- } else {
- throw e;
- }
- }
- final Enumeration<? extends ZipEntry> entries = zipFile.entries();
- while (entries.hasMoreElements()) {
- ZipEntry entry = entries.nextElement();
- String name = entry.getName();
- if (ZipUtils.isClassFile(name) && include.test(name)) {
- descriptors.add(DescriptorUtils.guessTypeDescriptor(name));
- }
- }
+ ensureZipFile();
}
@Override
public Set<String> getClassDescriptors() {
- return Collections.unmodifiableSet(descriptors);
+ return ensureDescriptors();
}
@Override
public ProgramResource getProgramResource(String descriptor) {
- if (!descriptors.contains(descriptor)) {
+ if (!ensureDescriptors().contains(descriptor)) {
return null;
}
+ ZipFile zipFile = ensureZipFile();
ZipEntry zipEntry = getZipEntryFromDescriptor(descriptor);
try (InputStream inputStream = zipFile.getInputStream(zipEntry)) {
return ProgramResource.fromBytes(
@@ -102,17 +92,60 @@
}
@Override
- protected void finalize() throws Throwable {
+ public void finished(DiagnosticsHandler handler) throws IOException {
close();
- super.finalize();
}
@Override
public void close() throws IOException {
- zipFile.close();
+ if (lazyZipFile != null) {
+ lazyZipFile.close();
+ }
+ lazyZipFile = null;
+ lazyDescriptors = null;
+ }
+
+ private void reopenZipFile() throws IOException {
+ assert lazyZipFile == null;
+ assert lazyDescriptors == null;
+ try {
+ lazyZipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ if (!Files.exists(archive)) {
+ throw new NoSuchFileException(archive.toString());
+ } else {
+ throw e;
+ }
+ }
+ lazyDescriptors = new HashSet<>();
+ final Enumeration<? extends ZipEntry> entries = lazyZipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (ZipUtils.isClassFile(name) && include.test(name)) {
+ lazyDescriptors.add(DescriptorUtils.guessTypeDescriptor(name));
+ }
+ }
+ }
+
+ private ZipFile ensureZipFile() {
+ if (lazyZipFile == null) {
+ try {
+ reopenZipFile();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return lazyZipFile;
+ }
+
+ private Set<String> ensureDescriptors() {
+ ensureZipFile();
+ return Collections.unmodifiableSet(lazyDescriptors);
}
private ZipEntry getZipEntryFromDescriptor(String descriptor) {
- return zipFile.getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION);
+ return ensureZipFile()
+ .getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION);
}
}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 7e93145..08c7a84 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -245,7 +245,7 @@
private List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>();
private List<Consumer<Inspector>> outputInspections = new ArrayList<>();
protected StringConsumer proguardMapConsumer = null;
- private DumpInputFlags dumpInputFlags = DumpInputFlags.noDump();
+ private DumpInputFlags dumpInputFlags = DumpInputFlags.getDefault();
private MapIdProvider mapIdProvider = null;
private SourceFileProvider sourceFileProvider = null;
private boolean isAndroidPlatformBuild = false;
@@ -664,6 +664,15 @@
return isAndroidPlatformBuild;
}
+ /**
+ * Allow to skip to dump into file and dump into directory instruction, this is primarily used
+ * for chained compilation in L8 so there are no duplicated dumps.
+ */
+ B skipDump() {
+ dumpInputFlags = DumpInputFlags.noDump();
+ return self();
+ }
+
B dumpInputToFile(Path file) {
dumpInputFlags = DumpInputFlags.dumpToFile(file);
return self();
diff --git a/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java b/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
index db0a9f8..ab6a79d 100644
--- a/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import java.io.IOException;
import java.util.Set;
/**
@@ -33,4 +34,18 @@
* calls from different threads.
*/
ProgramResource getProgramResource(String descriptor);
+
+ /**
+ * Callback signifying that a given compilation unit is done using the resource provider.
+ *
+ * <p>This can be used to clean-up resources once it is guaranteed that the compiler will no
+ * longer request them. If a client shares a resource provider among multiple compilation units
+ * then the provider should be sure to either retain the resources or support reloading them on
+ * demand.
+ *
+ * <p>Providers should make sure finished can be safely called multiple times.
+ */
+ default void finished(DiagnosticsHandler handler) throws IOException {
+ // Do nothing by default.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 9a340b8..34f4de6 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -208,6 +208,7 @@
}
Timing timing = Timing.create("D8", options);
try {
+ timing.begin("Pre conversion");
// Synthetic assertion to check that testing assertions works and can be enabled.
assert forTesting(options, () -> !options.testing.testEnableTestAssertions);
@@ -246,9 +247,9 @@
if (options.testing.enableD8ResourcesPassThrough) {
appView.setAppServices(AppServices.builder(appView).build());
}
-
+ timing.end();
new IRConverter(appView, timing, printer).convert(appView, executor);
-
+ timing.begin("Post conversion");
if (options.printCfg) {
if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
System.out.print(printer.toString());
@@ -286,9 +287,22 @@
}
Marker.checkCompatibleDesugaredLibrary(markers, options.reporter);
- InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
- appView.setNamingLens(PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView));
- appView.setNamingLens(RecordRewritingNamingLens.createRecordRewritingNamingLens(appView));
+ timing.time(
+ "Run inspections",
+ () ->
+ InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes()));
+
+ timing.time(
+ "Create prefix rewriting lens",
+ () ->
+ appView.setNamingLens(
+ PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView)));
+
+ timing.time(
+ "Create record rewriting lens",
+ () ->
+ appView.setNamingLens(
+ RecordRewritingNamingLens.createRecordRewritingNamingLens(appView)));
if (options.isGeneratingDex()
&& hasDexResources
@@ -313,20 +327,34 @@
// Since tracing is not lens aware, this needs to be done prior to synthetic finalization
// which will construct a graph lens.
if (options.isGeneratingDex() && !options.mainDexKeepRules.isEmpty()) {
+ timing.begin("Generate main-dex list");
appView.dexItemFactory().clearTypeElementsCache();
MainDexInfo mainDexInfo =
new GenerateMainDexList(options)
.traceMainDex(
executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
+ timing.end();
}
- finalizeApplication(appView, executor);
+ timing.time("Finalize synthetics", () -> finalizeApplication(appView, executor, timing));
- HorizontalClassMerger.createForD8ClassMerging(appView).runIfNecessary(executor, timing);
+ timing.time(
+ "Horizontal merger",
+ () ->
+ HorizontalClassMerger.createForD8ClassMerging(appView)
+ .runIfNecessary(executor, timing));
- new GenericSignatureRewriter(appView).runForD8(appView.appInfo().classes(), executor);
- new KotlinMetadataRewriter(appView).runForD8(executor);
+ timing.time(
+ "Signature rewriter",
+ () ->
+ new GenericSignatureRewriter(appView)
+ .runForD8(appView.appInfo().classes(), executor));
+
+ timing.time(
+ "Kotlin metadata rewriter", () -> new KotlinMetadataRewriter(appView).runForD8(executor));
+
+ timing.end(); // post-converter
if (options.isGeneratingClassFiles()) {
new CfApplicationWriter(appView, marker).write(options.getClassFileConsumer(), inputApp);
@@ -341,6 +369,7 @@
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
} finally {
+ inputApp.signalFinishedToProviders(options.reporter);
options.signalFinishedToConsumers();
// Dump timings.
if (options.printTimes) {
@@ -365,9 +394,10 @@
appView.setAssumeInfoCollection(assumeInfoCollectionBuilder.build());
}
- private static void finalizeApplication(AppView<AppInfo> appView, ExecutorService executorService)
+ private static void finalizeApplication(
+ AppView<AppInfo> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
- SyntheticFinalization.finalize(appView, executorService);
+ SyntheticFinalization.finalize(appView, timing, executorService);
}
private static DexApplication rewriteNonDexInputs(
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 7ea383c..76c2966 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -90,7 +90,6 @@
private String synthesizedClassPrefix = "";
private boolean enableMainDexListCheck = true;
private boolean minimalMainDex = false;
- private boolean skipDump = false;
private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
private boolean enableMissingLibraryApiModeling = false;
@@ -265,15 +264,6 @@
return self();
}
- /**
- * Allow to skip to dump into file and dump into directory instruction, this is primarily used
- * for chained compilation in L8 so there are no duplicated dumps.
- */
- Builder skipDump() {
- skipDump = true;
- return self();
- }
-
@Override
Builder self() {
return this;
@@ -427,7 +417,6 @@
getAssertionsConfiguration(),
getOutputInspections(),
synthesizedClassPrefix,
- skipDump,
enableMainDexListCheck,
minimalMainDex,
mainDexKeepRules,
@@ -447,7 +436,6 @@
private final StringConsumer desugaredLibraryKeepRuleConsumer;
private final DesugaredLibrarySpecification desugaredLibrarySpecification;
private final String synthesizedClassPrefix;
- private final boolean skipDump;
private final boolean enableMainDexListCheck;
private final boolean minimalMainDex;
private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
@@ -519,7 +507,6 @@
List<AssertionsConfiguration> assertionsConfiguration,
List<Consumer<Inspector>> outputInspections,
String synthesizedClassPrefix,
- boolean skipDump,
boolean enableMainDexListCheck,
boolean minimalMainDex,
ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
@@ -554,7 +541,6 @@
this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
this.desugaredLibrarySpecification = desugaredLibrarySpecification;
this.synthesizedClassPrefix = synthesizedClassPrefix;
- this.skipDump = skipDump;
this.enableMainDexListCheck = enableMainDexListCheck;
this.minimalMainDex = minimalMainDex;
this.mainDexKeepRules = mainDexKeepRules;
@@ -571,7 +557,6 @@
desugaredLibraryKeepRuleConsumer = null;
desugaredLibrarySpecification = null;
synthesizedClassPrefix = null;
- skipDump = false;
enableMainDexListCheck = true;
minimalMainDex = false;
mainDexKeepRules = null;
@@ -669,7 +654,7 @@
internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
- internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
+ internal.setDumpInputFlags(getDumpInputFlags());
internal.dumpOptions = dumpOptions();
return internal;
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index b8163c7..ad3bf13 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -108,6 +108,7 @@
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
} finally {
+ inputApp.signalFinishedToProviders(options.reporter);
options.signalFinishedToConsumers();
}
} finally {
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 1bb6dc5..1dddf3f 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -137,7 +137,7 @@
new IRConverter(appView, timing).convert(appView, executor);
- SyntheticFinalization.finalize(appView, executor);
+ SyntheticFinalization.finalize(appView, timing, executor);
appView.setNamingLens(PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView));
new GenericSignatureRewriter(appView).run(appView.appInfo().classes(), executor);
@@ -148,6 +148,7 @@
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
} finally {
+ inputApp.signalFinishedToProviders(options.reporter);
options.signalFinishedToConsumers();
// Dump timings.
if (options.printTimes) {
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index dce9604..b9fe11b 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -217,7 +217,7 @@
internal.apiModelingOptions().disableApiCallerIdentification();
internal.apiModelingOptions().disableMissingApiModeling();
- internal.setDumpInputFlags(getDumpInputFlags(), false);
+ internal.setDumpInputFlags(getDumpInputFlags());
internal.dumpOptions = dumpOptions();
return internal;
diff --git a/src/main/java/com/android/tools/r8/ProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
index d4b8a87..dd2bea8 100644
--- a/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ProgramResourceProvider.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import java.io.IOException;
import java.util.Collection;
/** Program resource provider. */
@@ -14,4 +15,18 @@
default DataResourceProvider getDataResourceProvider() {
return null;
}
+
+ /**
+ * Callback signifying that a given compilation unit is done using the resource provider.
+ *
+ * <p>This can be used to clean-up resources once it is guaranteed that the compiler will no
+ * longer request them. If a client shares a resource provider among multiple compilation units
+ * then the provider should be sure to either retain the resources or support reloading them on
+ * demand.
+ *
+ * <p>Providers should make sure finished can be safely called multiple times.
+ */
+ default void finished(DiagnosticsHandler handler) throws IOException {
+ // Do nothing by default.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4cc8c25..b781d3a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -67,7 +67,7 @@
import com.android.tools.r8.naming.ProguardMapMinifier;
import com.android.tools.r8.naming.RecordRewritingNamingLens;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
-import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
+import com.android.tools.r8.optimize.AccessModifier;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
import com.android.tools.r8.optimize.VisibilityBridgeRemover;
@@ -462,7 +462,7 @@
if (options.getProguardConfiguration().isAccessModificationAllowed()) {
SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
GraphLens publicizedLens =
- ClassAndMemberPublicizer.run(
+ AccessModifier.run(
executorService,
timing,
appViewWithLiveness.appInfo().app(),
@@ -718,9 +718,9 @@
appView.setGraphLens(MemberRebindingIdentityLensFactory.create(appView, executorService));
if (appView.appInfo().hasLiveness()) {
- SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService);
+ SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService, timing);
} else {
- SyntheticFinalization.finalizeWithClassHierarchy(appView, executorService);
+ SyntheticFinalization.finalizeWithClassHierarchy(appView, executorService, timing);
}
// Read any -applymapping input to allow for repackaging to not relocate the classes.
@@ -845,6 +845,7 @@
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
} finally {
+ inputApp.signalFinishedToProviders(options.reporter);
options.signalFinishedToConsumers();
// Dump timings.
if (options.printTimes) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 1496f40..7498045 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -114,11 +114,12 @@
private InputDependencyGraphConsumer inputDependencyGraphConsumer = null;
private final List<FeatureSplit> featureSplits = new ArrayList<>();
private String synthesizedClassPrefix = "";
- private boolean skipDump = false;
private boolean enableMissingLibraryApiModeling = false;
private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder =
ProguardConfigurationParserOptions.builder().readEnvironment();
+ private final boolean allowDexInArchive =
+ System.getProperty("com.android.tools.r8.allowDexInputToR8") != null;
// TODO(zerny): Consider refactoring CompatProguardCommandBuilder to avoid subclassing.
Builder() {
@@ -127,17 +128,17 @@
Builder(DiagnosticsHandler diagnosticsHandler) {
super(diagnosticsHandler);
- setIgnoreDexInArchive(true);
+ setIgnoreDexInArchive(!allowDexInArchive);
}
private Builder(AndroidApp app) {
super(app);
- setIgnoreDexInArchive(true);
+ setIgnoreDexInArchive(!allowDexInArchive);
}
private Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
super(app, diagnosticsHandler);
- setIgnoreDexInArchive(true);
+ setIgnoreDexInArchive(!allowDexInArchive);
}
// Internal
@@ -286,15 +287,6 @@
}
/**
- * Allow to skip to dump into file and dump into directory instruction, this is primarily used
- * for chained compilation in L8 so there are no duplicated dumps.
- */
- Builder skipDump() {
- skipDump = true;
- return self();
- }
-
- /**
* Set a consumer for receiving the proguard usage information.
*
* <p>Note that any subsequent calls to this method will replace the previous setting.
@@ -632,7 +624,6 @@
getAssertionsConfiguration(),
getOutputInspections(),
synthesizedClassPrefix,
- skipDump,
getThreadCount(),
getDumpInputFlags(),
getMapIdProvider(),
@@ -734,7 +725,6 @@
private final DesugaredLibrarySpecification desugaredLibrarySpecification;
private final FeatureSplitConfiguration featureSplitConfiguration;
private final String synthesizedClassPrefix;
- private final boolean skipDump;
private final boolean enableMissingLibraryApiModeling;
/** Get a new {@link R8Command.Builder}. */
@@ -820,7 +810,6 @@
List<AssertionsConfiguration> assertionsConfiguration,
List<Consumer<Inspector>> outputInspections,
String synthesizedClassPrefix,
- boolean skipDump,
int threadCount,
DumpInputFlags dumpInputFlags,
MapIdProvider mapIdProvider,
@@ -865,7 +854,6 @@
this.desugaredLibrarySpecification = desugaredLibrarySpecification;
this.featureSplitConfiguration = featureSplitConfiguration;
this.synthesizedClassPrefix = synthesizedClassPrefix;
- this.skipDump = skipDump;
this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
}
@@ -889,7 +877,6 @@
desugaredLibrarySpecification = null;
featureSplitConfiguration = null;
synthesizedClassPrefix = null;
- skipDump = false;
enableMissingLibraryApiModeling = false;
}
@@ -1053,7 +1040,7 @@
internal.threadCount = getThreadCount();
}
- internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
+ internal.setDumpInputFlags(getDumpInputFlags());
internal.dumpOptions = dumpOptions();
return internal;
diff --git a/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java b/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java
index bd4a06f..5b1d74f 100644
--- a/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java
+++ b/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java
@@ -36,7 +36,12 @@
appView.setAppServices(AppServices.builder(appView).build());
for (DexProgramClass clazz : appView.appInfo().classes()) {
clazz.forEachProgramMethod(
- method -> method.getDefinition().getCode().asCfCode().verifyFrames(method, appView));
+ method ->
+ method
+ .getDefinition()
+ .getCode()
+ .asCfCode()
+ .getOrComputeStackMapStatus(method, appView));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index d52f83c..4bff728 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -14,10 +14,10 @@
import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.graph.JarApplicationReader;
@@ -34,10 +34,11 @@
import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.ArrayList;
+import java.util.List;
import java.util.ListIterator;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
public class CfConstDynamic extends CfInstruction implements CfTypeInstruction {
@@ -47,12 +48,12 @@
DexString name,
DexType type,
DexMethodHandle bootstrapMethod,
- Object[] bootstrapMethodArguments) {
+ List<DexValue> bootstrapMethodArguments) {
assert name != null;
assert type != null;
assert bootstrapMethod != null;
assert bootstrapMethodArguments != null;
- assert bootstrapMethodArguments.length == 0;
+ assert bootstrapMethodArguments.isEmpty();
reference = new ConstantDynamicReference(name, type, bootstrapMethod, bootstrapMethodArguments);
}
@@ -79,7 +80,7 @@
return reference.getBootstrapMethod();
}
- public Object[] getBootstrapMethodArguments() {
+ public List<DexValue> getBootstrapMethodArguments() {
return reference.getBootstrapMethodArguments();
}
@@ -87,53 +88,20 @@
ConstantDynamic insn, JarApplicationReader application, DexType clazz) {
String constantName = insn.getName();
String constantDescriptor = insn.getDescriptor();
- // TODO(b/178172809): Handle bootstrap arguments.
- if (insn.getBootstrapMethodArgumentCount() > 0) {
- throw new CompilationError(
- "Unsupported dynamic constant (has arguments to bootstrap method)");
- }
- if (insn.getBootstrapMethod().getTag() != Opcodes.H_INVOKESTATIC) {
- throw new CompilationError("Unsupported dynamic constant (not invoke static)");
- }
- if (insn.getBootstrapMethod().getOwner().equals("java/lang/invoke/ConstantBootstraps")) {
- throw new CompilationError(
- "Unsupported dynamic constant (runtime provided bootstrap method)");
- }
- if (application.getTypeFromName(insn.getBootstrapMethod().getOwner()) != clazz) {
- throw new CompilationError("Unsupported dynamic constant (different owner)");
- }
- // Resolve the bootstrap method.
DexMethodHandle bootstrapMethodHandle =
DexMethodHandle.fromAsmHandle(insn.getBootstrapMethod(), application, clazz);
- if (!bootstrapMethodHandle.member.isDexMethod()) {
- throw new CompilationError("Unsupported dynamic constant (invalid method handle)");
- }
- DexMethod bootstrapMethod = bootstrapMethodHandle.asMethod();
- if (bootstrapMethod.getProto().returnType != application.getTypeFromDescriptor("[Z")
- && bootstrapMethod.getProto().returnType
- != application.getTypeFromDescriptor("Ljava/lang/Object;")) {
- throw new CompilationError("Unsupported dynamic constant (unsupported constant type)");
- }
- if (bootstrapMethod.getProto().getParameters().size() != 3) {
- throw new CompilationError("Unsupported dynamic constant (unsupported signature)");
- }
- if (bootstrapMethod.getProto().getParameters().get(0) != application.getFactory().lookupType) {
- throw new CompilationError(
- "Unsupported dynamic constant (unexpected type of first argument to bootstrap method");
- }
- if (bootstrapMethod.getProto().getParameters().get(1) != application.getFactory().stringType) {
- throw new CompilationError(
- "Unsupported dynamic constant (unexpected type of second argument to bootstrap method");
- }
- if (bootstrapMethod.getProto().getParameters().get(2) != application.getFactory().classType) {
- throw new CompilationError(
- "Unsupported dynamic constant (unexpected type of third argument to bootstrap method");
+ int argumentCount = insn.getBootstrapMethodArgumentCount();
+ List<DexValue> bootstrapMethodArguments = new ArrayList<>(argumentCount);
+ for (int i = 0; i < argumentCount; i++) {
+ Object argument = insn.getBootstrapMethodArgument(i);
+ DexValue dexValue = DexValue.fromAsmBootstrapArgument(argument, application, clazz);
+ bootstrapMethodArguments.add(dexValue);
}
return new CfConstDynamic(
application.getString(constantName),
application.getTypeFromDescriptor(constantDescriptor),
bootstrapMethodHandle,
- new Object[] {});
+ bootstrapMethodArguments);
}
@Override
@@ -186,8 +154,30 @@
NamingLens namingLens,
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
- // TODO(b/198142625): Support CONSTANT_Dynamic for R8 cf to cf.
- throw new CompilationError("Unsupported dynamic constant (not desugaring)");
+ DexMethodHandle rewrittenHandle =
+ rewriter.rewriteDexMethodHandle(
+ reference.getBootstrapMethod(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context);
+ List<DexValue> rewrittenArguments =
+ rewriter.rewriteBootstrapArguments(
+ reference.getBootstrapMethodArguments(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context);
+ Object[] bsmArgs = new Object[rewrittenArguments.size()];
+ for (int i = 0; i < rewrittenArguments.size(); i++) {
+ bsmArgs[i] = CfInvokeDynamic.decodeBootstrapArgument(rewrittenArguments.get(i), namingLens);
+ }
+ ConstantDynamic constantDynamic =
+ new ConstantDynamic(
+ reference.getName().toString(),
+ getConstantTypeDescriptor(graphLens, namingLens, dexItemFactory),
+ rewrittenHandle.toAsmHandle(namingLens),
+ bsmArgs);
+ visitor.visitLdcInsn(constantDynamic);
+ }
+
+ private String getConstantTypeDescriptor(
+ GraphLens graphLens, NamingLens namingLens, DexItemFactory factory) {
+ DexType rewrittenType = graphLens.lookupType(reference.getType());
+ DexType renamedType = namingLens.lookupType(rewrittenType, factory);
+ return renamedType.toDescriptorString();
}
@Override
@@ -212,7 +202,7 @@
registry.registerTypeReference(reference.getType());
registry.registerMethodHandle(
reference.getBootstrapMethod(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
- assert reference.getBootstrapMethodArguments().length == 0;
+ assert reference.getBootstrapMethodArguments().isEmpty();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
index 7866b2d..bdd6512 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -378,6 +378,10 @@
return this == NOT_PRESENT;
}
+ public boolean isNotVerified() {
+ return this == NOT_VERIFIED;
+ }
+
public boolean isValid() {
return this == VALID;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 45228d7..b880c47 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -98,7 +98,7 @@
return 5;
}
- private Object decodeBootstrapArgument(DexValue value, NamingLens lens) {
+ public static Object decodeBootstrapArgument(DexValue value, NamingLens lens) {
switch (value.getValueKind()) {
case DOUBLE:
return value.asDexValueDouble().getValue();
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 f00f7fb..e351e57 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.StringResource;
+import com.android.tools.r8.dump.DumpOptions;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
import com.android.tools.r8.graph.ApplicationReaderMap;
@@ -41,6 +42,7 @@
import com.android.tools.r8.utils.ClasspathClassCollection;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexVersion;
+import com.android.tools.r8.utils.DumpInputFlags;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LibraryClassCollection;
import com.android.tools.r8.utils.MainDexListParser;
@@ -49,9 +51,7 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.io.IOException;
-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.List;
@@ -113,7 +113,7 @@
inputApp.getProguardMapInputData(),
executorService,
ProgramClassCollection.defaultConflictResolver(options.reporter),
- false);
+ DumpInputFlags.noDump());
}
public final LazyLoadedDexApplication read(
@@ -131,19 +131,17 @@
ExecutorService executorService,
ProgramClassConflictResolver resolver)
throws IOException {
- return read(proguardMap, executorService, resolver, true);
+ return read(proguardMap, executorService, resolver, options.getDumpInputFlags());
}
public final LazyLoadedDexApplication read(
StringResource proguardMap,
ExecutorService executorService,
ProgramClassConflictResolver resolver,
- boolean shouldDump)
+ DumpInputFlags dumpInputFlags)
throws IOException {
assert verifyMainDexOptionsCompatible(inputApp, options);
- if (shouldDump) {
- dumpApplication();
- }
+ dumpApplication(dumpInputFlags);
if (options.testing.verifyInputs) {
inputApp.validateInputs();
@@ -184,31 +182,20 @@
return builder.build();
}
- private void dumpApplication() throws IOException {
- Path dumpOutput = null;
- boolean cleanDump = false;
- if (options.dumpInputToFile != null) {
- dumpOutput = Paths.get(options.dumpInputToFile);
- } else if (options.dumpInputToDirectory != null) {
- dumpOutput =
- Paths.get(options.dumpInputToDirectory).resolve("dump" + System.nanoTime() + ".zip");
- } else if (options.testing.dumpAll) {
- cleanDump = true;
- dumpOutput = Paths.get("/tmp").resolve("dump" + System.nanoTime() + ".zip");
+ private void dumpApplication(DumpInputFlags dumpInputFlags) {
+ DumpOptions dumpOptions = options.dumpOptions;
+ if (dumpOptions == null || !dumpInputFlags.shouldDump(dumpOptions)) {
+ return;
}
- if (dumpOutput != null) {
- timing.begin("ApplicationReader.dump");
- inputApp.dump(dumpOutput, options.dumpOptions, options.reporter, options.dexItemFactory());
- if (cleanDump) {
- Files.delete(dumpOutput);
- }
- timing.end();
- Diagnostic message = new StringDiagnostic("Dumped compilation inputs to: " + dumpOutput);
- if (options.dumpInputToFile != null) {
- throw options.reporter.fatalError(message);
- } else if (!cleanDump) {
- options.reporter.info(message);
- }
+ Path dumpOutput = dumpInputFlags.getDumpPath();
+ timing.begin("ApplicationReader.dump");
+ inputApp.dump(dumpOutput, dumpOptions, options.reporter, options.dexItemFactory());
+ timing.end();
+ Diagnostic message = new StringDiagnostic("Dumped compilation inputs to: " + dumpOutput);
+ if (dumpInputFlags.shouldFailCompilation()) {
+ throw options.reporter.fatalError(message);
+ } else {
+ options.reporter.info(message);
}
}
@@ -347,7 +334,7 @@
return new DexApplicationReadFlags(
hasReadProgramResourceFromDex,
hasReadProgramResourceFromCf,
- application.hasReadRecordReferenceFromProgramClass());
+ application.getRecordWitnesses());
}
private void readDexSources(List<ProgramResource> dexSources, Queue<DexProgramClass> classes)
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 6167f9b..a6f6a52 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -7,10 +7,10 @@
import com.android.tools.r8.ByteBufferProvider;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.DefaultInterfaceMethodDiagnostic;
-import com.android.tools.r8.errors.InvokeCustomDiagnostic;
-import com.android.tools.r8.errors.PrivateInterfaceMethodDiagnostic;
-import com.android.tools.r8.errors.StaticInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedDefaultInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedPrivateInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedStaticInterfaceMethodDiagnostic;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -290,9 +290,9 @@
if (!options.canUseDefaultAndStaticInterfaceMethods()
&& !options.testing.allowStaticInterfaceMethodsForPreNApiLevel) {
throw options.reporter.fatalError(
- new StaticInterfaceMethodDiagnostic(holder.getOrigin(), MethodPosition.create(method)));
+ new UnsupportedStaticInterfaceMethodDiagnostic(
+ holder.getOrigin(), MethodPosition.create(method)));
}
-
} else {
if (method.isInstanceInitializer()) {
throw new CompilationError(
@@ -301,7 +301,7 @@
if (!method.accessFlags.isAbstract() && !method.accessFlags.isPrivate() &&
!options.canUseDefaultAndStaticInterfaceMethods()) {
throw options.reporter.fatalError(
- new DefaultInterfaceMethodDiagnostic(
+ new UnsupportedDefaultInterfaceMethodDiagnostic(
holder.getOrigin(), MethodPosition.create(method)));
}
}
@@ -311,7 +311,8 @@
return;
}
throw options.reporter.fatalError(
- new PrivateInterfaceMethodDiagnostic(holder.getOrigin(), MethodPosition.create(method)));
+ new UnsupportedPrivateInterfaceMethodDiagnostic(
+ holder.getOrigin(), MethodPosition.create(method)));
}
if (!method.accessFlags.isPublic()) {
@@ -1382,7 +1383,7 @@
private void checkThatInvokeCustomIsAllowed() {
if (!options.canUseInvokeCustom()) {
throw options.reporter.fatalError(
- new InvokeCustomDiagnostic(Origin.unknown(), Position.UNKNOWN));
+ new UnsupportedInvokeCustomDiagnostic(Origin.unknown(), Position.UNKNOWN));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index cb15cb2..1056bda 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -78,7 +78,7 @@
private final IndexedItemTransaction transaction;
private final FeatureSplit featureSplit;
- private final DexProgramClass primaryClass;
+ private final DexString primaryClassDescriptor;
private DebugRepresentation debugRepresentation;
VirtualFile(int id, AppView<?> appView) {
@@ -107,7 +107,10 @@
this.id = id;
this.indexedItems = new VirtualFileIndexedItemCollection(appView);
this.transaction = new IndexedItemTransaction(indexedItems, appView);
- this.primaryClass = primaryClass;
+ this.primaryClassDescriptor =
+ primaryClass == null
+ ? null
+ : appView.getNamingLens().lookupClassDescriptor(primaryClass.type);
this.featureSplit = featureSplit;
}
@@ -129,7 +132,7 @@
}
public String getPrimaryClassDescriptor() {
- return primaryClass == null ? null : primaryClass.type.descriptor.toString();
+ return primaryClassDescriptor == null ? null : primaryClassDescriptor.toString();
}
public void setDebugRepresentation(DebugRepresentation debugRepresentation) {
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index da85b0e..1d55987 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -14,8 +14,10 @@
import com.android.tools.r8.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@@ -109,34 +111,45 @@
this.dumpInputToFile = dumpInputToFile;
}
- public String dumpOptions() {
+ public String getBuildPropertiesFileContent() {
StringBuilder builder = new StringBuilder();
- addDumpEntry(builder, TOOL_KEY, tool.name());
+ getBuildProperties()
+ .forEach((key, value) -> builder.append(key).append("=").append(value).append("\n"));
+ return builder.toString();
+ }
+
+ public Map<String, String> getBuildProperties() {
+ Map<String, String> buildProperties = new LinkedHashMap<>();
+ addDumpEntry(buildProperties, TOOL_KEY, tool.name());
// We keep the following values for backward compatibility.
addDumpEntry(
- builder,
+ buildProperties,
MODE_KEY,
compilationMode == CompilationMode.DEBUG ? DEBUG_MODE_VALUE : RELEASE_MODE_VALUE);
- addDumpEntry(builder, MIN_API_KEY, minApi);
- addDumpEntry(builder, OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY, optimizeMultidexForLinearAlloc);
+ addDumpEntry(buildProperties, MIN_API_KEY, minApi);
+ addDumpEntry(
+ buildProperties, OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY, optimizeMultidexForLinearAlloc);
if (threadCount != ThreadUtils.NOT_SPECIFIED) {
- addDumpEntry(builder, THREAD_COUNT_KEY, threadCount);
+ addDumpEntry(buildProperties, THREAD_COUNT_KEY, threadCount);
}
- addDumpEntry(builder, DESUGAR_STATE_KEY, desugarState);
- addDumpEntry(builder, ENABLE_MISSING_LIBRARY_API_MODELING, enableMissingLibraryApiModeling);
+ addDumpEntry(buildProperties, DESUGAR_STATE_KEY, desugarState);
+ addDumpEntry(
+ buildProperties, ENABLE_MISSING_LIBRARY_API_MODELING, enableMissingLibraryApiModeling);
if (isAndroidPlatformBuild) {
- addDumpEntry(builder, ANDROID_PLATFORM_BUILD, isAndroidPlatformBuild);
+ addDumpEntry(buildProperties, ANDROID_PLATFORM_BUILD, isAndroidPlatformBuild);
}
- addOptionalDumpEntry(builder, INTERMEDIATE_KEY, intermediate);
- addOptionalDumpEntry(builder, INCLUDE_DATA_RESOURCES_KEY, includeDataResources);
- addOptionalDumpEntry(builder, TREE_SHAKING_KEY, treeShaking);
- addOptionalDumpEntry(builder, MINIFICATION_KEY, minification);
- addOptionalDumpEntry(builder, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
+ addOptionalDumpEntry(buildProperties, INTERMEDIATE_KEY, intermediate);
+ addOptionalDumpEntry(buildProperties, INCLUDE_DATA_RESOURCES_KEY, includeDataResources);
+ addOptionalDumpEntry(buildProperties, TREE_SHAKING_KEY, treeShaking);
+ addOptionalDumpEntry(buildProperties, MINIFICATION_KEY, minification);
+ addOptionalDumpEntry(
+ buildProperties, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
ArrayList<String> sortedKeys = new ArrayList<>(systemProperties.keySet());
sortedKeys.sort(String::compareTo);
sortedKeys.forEach(
- key -> addDumpEntry(builder, SYSTEM_PROPERTY_PREFIX + key, systemProperties.get(key)));
- return builder.toString();
+ key ->
+ addDumpEntry(buildProperties, SYSTEM_PROPERTY_PREFIX + key, systemProperties.get(key)));
+ return buildProperties;
}
public static void parse(String content, DumpOptions.Builder builder) {
@@ -219,12 +232,13 @@
return minApi;
}
- private void addOptionalDumpEntry(StringBuilder builder, String key, Optional<?> optionalValue) {
- optionalValue.ifPresent(bool -> addDumpEntry(builder, key, bool));
+ private void addOptionalDumpEntry(
+ Map<String, String> buildProperties, String key, Optional<?> optionalValue) {
+ optionalValue.ifPresent(bool -> addDumpEntry(buildProperties, key, bool));
}
- private void addDumpEntry(StringBuilder builder, String key, Object value) {
- builder.append(key).append("=").append(value).append("\n");
+ private void addDumpEntry(Map<String, String> buildProperties, String key, Object value) {
+ buildProperties.put(key, Objects.toString(value));
}
private boolean hasDesugaredLibraryConfiguration() {
diff --git a/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.java
deleted file mode 100644
index d77a5f5..0000000
--- a/src/main/java/com/android/tools/r8/errors/ConstMethodHandleDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2022, 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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class ConstMethodHandleDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public ConstMethodHandleDiagnostic(Origin origin, MethodPosition position) {
- super("const-method-handle", AndroidApiLevel.P, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.P, "Const-method-handle", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.java
deleted file mode 100644
index 2a9eff5..0000000
--- a/src/main/java/com/android/tools/r8/errors/ConstMethodTypeDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2022, 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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class ConstMethodTypeDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public ConstMethodTypeDiagnostic(Origin origin, MethodPosition position) {
- super("const-method-type", AndroidApiLevel.P, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.P, "Const-method-type", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/ConstantDynamicDesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ConstantDynamicDesugarDiagnostic.java
new file mode 100644
index 0000000..d80b530
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/ConstantDynamicDesugarDiagnostic.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+/** Common type for all diagnostics related to constant-dynamic desugaring. */
+@Keep
+public class ConstantDynamicDesugarDiagnostic implements DesugarDiagnostic {
+
+ private final Origin origin;
+ private final Position position;
+ private final String message;
+
+ public ConstantDynamicDesugarDiagnostic(Origin origin, Position position, String message) {
+ this.origin = origin;
+ this.position = position;
+ this.message = message;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return message;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
deleted file mode 100644
index 21b15b2..0000000
--- a/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
+++ /dev/null
@@ -1,21 +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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class DefaultInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public DefaultInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
- super("default-interface-method", AndroidApiLevel.N, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.N, "Default interface methods", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
deleted file mode 100644
index 0f8c6ab..0000000
--- a/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
+++ /dev/null
@@ -1,20 +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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class InvokeCustomDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public InvokeCustomDiagnostic(Origin origin, Position position) {
- super("invoke-custom", AndroidApiLevel.O, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(AndroidApiLevel.O, "Invoke-customs", null);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.java
deleted file mode 100644
index 1efbb31..0000000
--- a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicMethodHandleDiagnostic.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2022, 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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class InvokePolymorphicMethodHandleDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public InvokePolymorphicMethodHandleDiagnostic(Origin origin, MethodPosition position) {
- super("invoke-polymorphic-method-handle", AndroidApiLevel.O, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.O,
- "MethodHandle.invoke and MethodHandle.invokeExact",
- getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.java
deleted file mode 100644
index 9758581..0000000
--- a/src/main/java/com/android/tools/r8/errors/InvokePolymorphicVarHandleDiagnostic.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2022, 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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class InvokePolymorphicVarHandleDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public InvokePolymorphicVarHandleDiagnostic(Origin origin, MethodPosition position) {
- super("invoke-polymorphic-var-handle", AndroidApiLevel.P, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.P, "Call to polymorphic signature of VarHandle", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
deleted file mode 100644
index 9c85254..0000000
--- a/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
+++ /dev/null
@@ -1,21 +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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class PrivateInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public PrivateInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
- super("private-interface-method", AndroidApiLevel.N, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.N, "Private interface methods", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
deleted file mode 100644
index 9bc4f48..0000000
--- a/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
+++ /dev/null
@@ -1,21 +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.errors;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class StaticInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
-
- public StaticInterfaceMethodDiagnostic(Origin origin, MethodPosition position) {
- super("static-interface-method", AndroidApiLevel.N, origin, position);
- }
-
- @Override
- public String getDiagnosticMessage() {
- return UnsupportedFeatureDiagnostic.makeMessage(
- AndroidApiLevel.N, "Static interface methods", getPosition().toString());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedConstDynamicDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedConstDynamicDiagnostic.java
new file mode 100644
index 0000000..b75f686
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedConstDynamicDiagnostic.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.InternalOptions;
+
+@Keep
+public class UnsupportedConstDynamicDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "const-dynamic";
+
+ public UnsupportedConstDynamicDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, InternalOptions.constantDynamicApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ InternalOptions.constantDynamicApiLevel(), DESCRIPTOR, getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodHandleDiagnostic.java
new file mode 100644
index 0000000..375adf1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodHandleDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.constantMethodHandleApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedConstMethodHandleDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "const-method-handle";
+
+ public UnsupportedConstMethodHandleDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, constantMethodHandleApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ constantMethodHandleApiLevel(), DESCRIPTOR, getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodTypeDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodTypeDiagnostic.java
new file mode 100644
index 0000000..5809011
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedConstMethodTypeDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.constantMethodTypeApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedConstMethodTypeDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "const-method-type";
+
+ public UnsupportedConstMethodTypeDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, constantMethodTypeApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ constantMethodTypeApiLevel(), DESCRIPTOR, getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedDefaultInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedDefaultInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..6905dcc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedDefaultInterfaceMethodDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.defaultInterfaceMethodsApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedDefaultInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "default-interface-method";
+
+ public UnsupportedDefaultInterfaceMethodDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, defaultInterfaceMethodsApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ defaultInterfaceMethodsApiLevel(), "Default interface methods", getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java
index 74c3ad6..1ac0482 100644
--- a/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedFeatureDiagnostic.java
@@ -15,12 +15,14 @@
public static String makeMessage(
AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
String message =
- unsupportedFeatures
- + " are only supported starting with "
- + minApiLevel.getName()
- + " (--min-api "
- + minApiLevel.getLevel()
- + ")";
+ minApiLevel == null
+ ? (unsupportedFeatures + " are not supported at any API level known by the compiler")
+ : (unsupportedFeatures
+ + " are only supported starting with "
+ + minApiLevel.getName()
+ + " (--min-api "
+ + minApiLevel.getLevel()
+ + ")");
message = (sourceString != null) ? message + ": " + sourceString : message;
return message;
}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedInvokeCustomDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokeCustomDiagnostic.java
new file mode 100644
index 0000000..ca67569
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokeCustomDiagnostic.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.invokeCustomApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedInvokeCustomDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "invoke-custom";
+
+ public UnsupportedInvokeCustomDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, invokeCustomApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(invokeCustomApiLevel(), "Invoke-customs", null);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicMethodHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicMethodHandleDiagnostic.java
new file mode 100644
index 0000000..23718f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicMethodHandleDiagnostic.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.invokePolymorphicOnMethodHandleApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedInvokePolymorphicMethodHandleDiagnostic
+ extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "invoke-polymorphic-method-handle";
+
+ public UnsupportedInvokePolymorphicMethodHandleDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, invokePolymorphicOnMethodHandleApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ invokePolymorphicOnMethodHandleApiLevel(),
+ "MethodHandle.invoke and MethodHandle.invokeExact",
+ getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicVarHandleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicVarHandleDiagnostic.java
new file mode 100644
index 0000000..49f73a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedInvokePolymorphicVarHandleDiagnostic.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.invokePolymorphicOnVarHandleApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedInvokePolymorphicVarHandleDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "invoke-polymorphic-var-handle";
+
+ public UnsupportedInvokePolymorphicVarHandleDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, invokePolymorphicOnVarHandleApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ invokePolymorphicOnVarHandleApiLevel(),
+ "Call to polymorphic signature of VarHandle",
+ getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedPrivateInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedPrivateInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..0a52151
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedPrivateInterfaceMethodDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.privateInterfaceMethodsApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedPrivateInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "private-interface-method";
+
+ public UnsupportedPrivateInterfaceMethodDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, privateInterfaceMethodsApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ privateInterfaceMethodsApiLevel(), "Private interface methods", getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedStaticInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedStaticInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..3220744
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedStaticInterfaceMethodDiagnostic.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.errors;
+
+import static com.android.tools.r8.utils.InternalOptions.staticInterfaceMethodsApiLevel;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class UnsupportedStaticInterfaceMethodDiagnostic extends UnsupportedFeatureDiagnostic {
+
+ // API: MUST NOT CHANGE!
+ private static final String DESCRIPTOR = "static-interface-method";
+
+ public UnsupportedStaticInterfaceMethodDiagnostic(Origin origin, Position position) {
+ super(DESCRIPTOR, staticInterfaceMethodsApiLevel(), origin, position);
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return UnsupportedFeatureDiagnostic.makeMessage(
+ staticInterfaceMethodsApiLevel(), "Static interface methods", getPosition().toString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 0d4f9c9..9da9fd8 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -248,6 +248,10 @@
return stackMapStatus;
}
+ public void setStackMapStatus(StackMapStatus stackMapStatus) {
+ this.stackMapStatus = stackMapStatus;
+ }
+
public com.android.tools.r8.position.Position getDiagnosticPosition() {
return diagnosticPosition;
}
@@ -407,7 +411,7 @@
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
GraphLens graphLens = appView.graphLens();
- assert verifyFrames(method, appView).isValidOrNotPresent()
+ assert getOrComputeStackMapStatus(method, appView).isValidOrNotPresent()
: "Could not validate stack map frames";
DexItemFactory dexItemFactory = appView.dexItemFactory();
InitClassLens initClassLens = appView.initClassLens();
@@ -418,11 +422,13 @@
parameterLabel.write(
appView, method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
}
+ boolean discardFrames =
+ classFileVersion.isLessThan(CfVersion.V1_6)
+ || (appView.enableWholeProgramOptimizations()
+ && classFileVersion.isEqualTo(CfVersion.V1_6)
+ && !options.shouldKeepStackMapTable());
for (CfInstruction instruction : instructions) {
- if (instruction instanceof CfFrame
- && (classFileVersion.isLessThan(CfVersion.V1_6)
- || (classFileVersion.isEqualTo(CfVersion.V1_6)
- && !options.shouldKeepStackMapTable()))) {
+ if (discardFrames && instruction instanceof CfFrame) {
continue;
}
instruction.write(
@@ -545,7 +551,7 @@
}
private void verifyFramesOrRemove(ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
- stackMapStatus = verifyFrames(method, appView, codeLens);
+ stackMapStatus = getOrComputeStackMapStatus(method, appView, codeLens);
if (!stackMapStatus.isValidOrNotPresent()) {
ArrayList<CfInstruction> copy = new ArrayList<>(instructions);
copy.removeIf(CfInstruction::isFrame);
@@ -884,11 +890,20 @@
originalHolder, maxStack, maxLocals, newInstructions, tryCatchRanges, localVariables);
}
- public StackMapStatus verifyFrames(ProgramMethod method, AppView<?> appView) {
- return verifyFrames(method, appView, getCodeLens(appView));
+ public StackMapStatus getOrComputeStackMapStatus(ProgramMethod method, AppView<?> appView) {
+ return getOrComputeStackMapStatus(method, appView, getCodeLens(appView));
}
- public StackMapStatus verifyFrames(ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
+ public StackMapStatus getOrComputeStackMapStatus(
+ ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
+ if (stackMapStatus.isNotVerified()) {
+ setStackMapStatus(computeStackMapStatus(method, appView, codeLens));
+ }
+ return stackMapStatus;
+ }
+
+ private StackMapStatus computeStackMapStatus(
+ ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
CfFrameVerifierEventConsumer eventConsumer =
new CfFrameVerifierEventConsumer() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
index bb3bcf8..7bce5f6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
@@ -3,21 +3,23 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import java.util.Set;
+
// Flags set based on the application when it was read.
// Note that in r8, once classes are pruned, the flags may not reflect the application anymore.
public class DexApplicationReadFlags {
private final boolean hasReadProgramClassFromDex;
private final boolean hasReadProgramClassFromCf;
- private final boolean hasReadRecordReferenceFromProgramClass;
+ private final Set<DexType> recordWitnesses;
public DexApplicationReadFlags(
boolean hasReadProgramClassFromDex,
boolean hasReadProgramClassFromCf,
- boolean hasReadRecordReferenceFromProgramClass) {
+ Set<DexType> recordWitnesses) {
this.hasReadProgramClassFromDex = hasReadProgramClassFromDex;
this.hasReadProgramClassFromCf = hasReadProgramClassFromCf;
- this.hasReadRecordReferenceFromProgramClass = hasReadRecordReferenceFromProgramClass;
+ this.recordWitnesses = recordWitnesses;
}
public boolean hasReadProgramClassFromCf() {
@@ -29,6 +31,10 @@
}
public boolean hasReadRecordReferenceFromProgramClass() {
- return hasReadRecordReferenceFromProgramClass;
+ return !recordWitnesses.isEmpty();
+ }
+
+ public Set<DexType> getRecordWitnesses() {
+ return recordWitnesses;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 265fc65..3069a60 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.OptionalBool;
@@ -856,8 +857,8 @@
public boolean isResolvable(AppView<?> appView) {
if (isResolvable.isUnknown()) {
boolean resolvable;
- if (!isProgramClass()) {
- resolvable = appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(type);
+ if (isLibraryClass()) {
+ resolvable = AndroidApiLevelUtils.isApiSafeForReference(asLibraryClass(), appView);
} else {
resolvable = true;
for (DexType supertype : allImmediateSupertypes()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 51a4a4a..b785927 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -447,6 +447,10 @@
return isInstanceInitializer() || isClassInitializer();
}
+ public boolean isInitializer(boolean isStatic) {
+ return isStatic ? isClassInitializer() : isInstanceInitializer();
+ }
+
public boolean isInstanceInitializer() {
checkIfObsolete();
return accessFlags.isConstructor() && !accessFlags.isStatic();
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index fbd4b5a..c0aaa95 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -666,6 +666,8 @@
public final DexType metafactoryType =
createStaticallyKnownType("Ljava/lang/invoke/LambdaMetafactory;");
+ public final DexType constantBootstrapsType =
+ createStaticallyKnownType("Ljava/lang/invoke/ConstantBootstraps;");
public final DexType callSiteType = createStaticallyKnownType("Ljava/lang/invoke/CallSite;");
public final DexType lookupType =
createStaticallyKnownType("Ljava/lang/invoke/MethodHandles$Lookup;");
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 2cb6abd..c455396 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -312,7 +312,7 @@
public abstract AbstractValue toAbstractValue(AbstractValueFactory factory);
- static DexValue fromAsmBootstrapArgument(
+ public static DexValue fromAsmBootstrapArgument(
Object value, JarApplicationReader application, DexType clazz) {
if (value instanceof Integer) {
return DexValue.DexValueInt.create((Integer) value);
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index ca751d0..d342780 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -7,7 +7,9 @@
import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.objectweb.asm.Type;
@@ -25,6 +27,7 @@
private final ConcurrentHashMap<String, Type> asmTypeCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
private final ApplicationReaderMap applicationReaderMap;
+ private final Set<DexType> recordWitnesses = Sets.newConcurrentHashSet();
private boolean hasReadRecordReferenceFromProgramClass = false;
@@ -152,24 +155,26 @@
return getAsmType(DescriptorUtils.getReturnTypeDescriptor(methodDescriptor));
}
- public void setHasReadRecordReferenceFromProgramClass() {
- hasReadRecordReferenceFromProgramClass = true;
- }
-
- public boolean hasReadRecordReferenceFromProgramClass() {
- return hasReadRecordReferenceFromProgramClass;
- }
-
- public void checkFieldForRecord(DexField dexField) {
- if (options.shouldDesugarRecords() && RecordDesugaring.refersToRecord(dexField, getFactory())) {
- setHasReadRecordReferenceFromProgramClass();
+ public void addRecordWitness(DexType witness, ClassKind<?> classKind) {
+ if (classKind == ClassKind.PROGRAM) {
+ recordWitnesses.add(witness);
}
}
- public void checkMethodForRecord(DexMethod dexMethod) {
+ public Set<DexType> getRecordWitnesses() {
+ return recordWitnesses;
+ }
+
+ public void checkFieldForRecord(DexField dexField, ClassKind<?> classKind) {
+ if (options.shouldDesugarRecords() && RecordDesugaring.refersToRecord(dexField, getFactory())) {
+ addRecordWitness(dexField.getHolderType(), classKind);
+ }
+ }
+
+ public void checkMethodForRecord(DexMethod dexMethod, ClassKind<?> classKind) {
if (options.shouldDesugarRecords()
&& RecordDesugaring.refersToRecord(dexMethod, getFactory())) {
- setHasReadRecordReferenceFromProgramClass();
+ addRecordWitness(dexMethod.getHolderType(), classKind);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 8b8b9d8..73606cc 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -533,7 +533,7 @@
if (!accessFlags.isRecord()) {
return;
}
- application.setHasReadRecordReferenceFromProgramClass();
+ application.addRecordWitness(type, classKind);
// TODO(b/169645628): Change this logic if we start stripping the record components.
// Another approach would be to mark a bit in fields that are record components instead.
String message = "Records are expected to have one record component per instance field.";
@@ -680,7 +680,7 @@
public void visitEnd() {
FieldAccessFlags flags = createFieldAccessFlags(access);
DexField dexField = parent.application.getField(parent.type, name, desc);
- parent.application.checkFieldForRecord(dexField);
+ parent.application.checkFieldForRecord(dexField, parent.classKind);
Wrapper<DexField> signature = FieldSignatureEquivalence.get().wrap(dexField);
if (parent.fieldSignatures.add(signature)) {
DexAnnotationSet annotationSet =
@@ -898,7 +898,7 @@
@Override
public void visitEnd() {
InternalOptions options = parent.application.options;
- parent.application.checkMethodForRecord(method);
+ parent.application.checkMethodForRecord(method, parent.classKind);
if (!flags.isAbstract() && !flags.isNative() && classRequiresCode()) {
code = new LazyCfCode(parent.origin, parent.context, parent.application);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index e750ab3..7ac70e7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -140,12 +140,7 @@
// Check if the type is guaranteed to be present.
DexClass clazz = appView.definitionFor(baseType);
- if (clazz == null) {
- return true;
- }
-
- if (clazz.isLibraryClass()
- && !appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(baseType)) {
+ if (clazz == null || !clazz.isResolvable(appView)) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 9bc1728..5a3a1118 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -183,16 +183,10 @@
// Check if the type is guaranteed to be present.
DexClass clazz = appView.definitionFor(baseType);
- if (clazz == null) {
+ if (clazz == null || !clazz.isResolvable(appView)) {
return true;
}
- if (clazz.isLibraryClass()) {
- if (!appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(baseType)) {
- return true;
- }
- }
-
// Check if the type is guaranteed to be accessible.
if (AccessControl.isClassAccessible(clazz, context, appViewWithClassHierarchy)
.isPossiblyFalse()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index d6c938d..236bd0a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -170,12 +170,7 @@
}
DexClass definition = appView.definitionFor(clazz);
- if (definition == null || definition.accessFlags.isAbstract()) {
- return true;
- }
-
- if (definition.isLibraryClass()
- && !dexItemFactory.libraryTypesAssumedToBePresent.contains(clazz)) {
+ if (definition == null || definition.isAbstract() || !definition.isResolvable(appView)) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index cb3bf73..a62fd2f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -193,7 +193,8 @@
DexBuilder.removeRedundantDebugPositions(code);
CfCode code = buildCfCode();
assert verifyInvokeInterface(code, appView);
- assert code.verifyFrames(method, appView, appView.graphLens()).isValidOrNotPresent();
+ assert code.getOrComputeStackMapStatus(method, appView, appView.graphLens())
+ .isValidOrNotPresent();
return code;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 70fddd9..4a29cb6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -17,12 +17,8 @@
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.ConstMethodHandleDiagnostic;
-import com.android.tools.r8.errors.ConstMethodTypeDiagnostic;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.InvalidDebugInfoException;
-import com.android.tools.r8.errors.InvokePolymorphicMethodHandleDiagnostic;
-import com.android.tools.r8.errors.InvokePolymorphicVarHandleDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
@@ -483,6 +479,14 @@
this.basicBlockNumberGenerator = new NumberGenerator();
}
+ private Origin getOrigin() {
+ return origin;
+ }
+
+ private MethodPosition getPosition() {
+ return MethodPosition.create(method);
+ }
+
public DexItemFactory dexItemFactory() {
return appView.dexItemFactory();
}
@@ -1226,11 +1230,7 @@
}
public void addConstMethodHandle(int dest, DexMethodHandle methodHandle) {
- if (!appView.options().canUseConstantMethodHandle()) {
- throw appView
- .reporter()
- .fatalError(new ConstMethodHandleDiagnostic(origin, MethodPosition.create(method)));
- }
+ assert appView.options().canUseConstantMethodHandle();
TypeElement typeLattice =
TypeElement.fromDexType(
appView.dexItemFactory().methodHandleType, definitelyNotNull(), appView);
@@ -1240,11 +1240,7 @@
}
public void addConstMethodType(int dest, DexProto methodType) {
- if (!appView.options().canUseConstantMethodType()) {
- throw appView
- .reporter()
- .fatalError(new ConstMethodTypeDiagnostic(origin, MethodPosition.create(method)));
- }
+ assert appView.options().canUseConstantMethodType();
TypeElement typeLattice =
TypeElement.fromDexType(
appView.dexItemFactory().methodTypeType, definitelyNotNull(), appView);
@@ -1502,23 +1498,23 @@
add(new RecordFieldValues(fields, out, arguments));
}
+ private boolean verifyRepresentablePolymorphicInvoke(Type type, DexItem item) {
+ if (type != Type.POLYMORPHIC) {
+ return true;
+ }
+ assert item instanceof DexMethod;
+ if (((DexMethod) item).holder == appView.dexItemFactory().methodHandleType) {
+ assert appView.options().canUseInvokePolymorphicOnMethodHandle();
+ }
+ if (((DexMethod) item).holder == appView.dexItemFactory().varHandleType) {
+ assert appView.options().canUseInvokePolymorphicOnVarHandle();
+ }
+ return true;
+ }
+
public void addInvoke(
Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf) {
- if (type == Type.POLYMORPHIC) {
- assert item instanceof DexMethod;
- if (!appView.options().canUseInvokePolymorphic()) {
- throw appView
- .reporter()
- .fatalError(
- new InvokePolymorphicMethodHandleDiagnostic(origin, MethodPosition.create(method)));
- } else if (!appView.options().canUseInvokePolymorphicOnVarHandle()
- && ((DexMethod) item).holder == appView.dexItemFactory().varHandleType) {
- throw appView
- .reporter()
- .fatalError(
- new InvokePolymorphicVarHandleDiagnostic(origin, MethodPosition.create(method)));
- }
- }
+ assert verifyRepresentablePolymorphicInvoke(type, item);
add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
}
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 2a97c16..dfc82e0 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
@@ -81,7 +81,6 @@
import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
import com.android.tools.r8.ir.optimize.outliner.Outliner;
import com.android.tools.r8.ir.optimize.string.StringBuilderAppendOptimizer;
-import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.IdentifierNameStringMarker;
@@ -123,7 +122,6 @@
private final FieldAccessAnalysis fieldAccessAnalysis;
private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis;
private final StringOptimizer stringOptimizer;
- private final StringBuilderOptimizer stringBuilderOptimizer;
private final IdempotentFunctionCallCanonicalizer idempotentFunctionCallCanonicalizer;
private final ClassInliner classInliner;
private final InternalOptions options;
@@ -182,7 +180,6 @@
this.classInitializerDefaultsOptimization =
new ClassInitializerDefaultsOptimization(appView, this);
this.stringOptimizer = new StringOptimizer(appView);
- this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
this.assertionsRewriter = new AssertionsRewriter(appView);
this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView);
@@ -547,7 +544,8 @@
if (options.testing.forceIRForCfToCfDesugar) {
return true;
}
- return !options.isCfDesugaring();
+ assert method.getDefinition().getCode().isCfCode();
+ return !options.isGeneratingClassFiles();
}
private void checkPrefixMerging(ProgramMethod method) {
@@ -807,9 +805,6 @@
if (stringOptimizer != null) {
stringOptimizer.logResult();
}
- if (stringBuilderOptimizer != null) {
- stringBuilderOptimizer.logResults();
- }
}
// Assure that no more optimization feedback left after post processing.
@@ -1316,10 +1311,7 @@
timing.begin("Rewrite move result");
codeRewriter.rewriteMoveResult(code);
timing.end();
- // TODO(b/114002137): Also run for CF
- if (options.enableStringConcatenationOptimization
- && !isDebugMode
- && options.isGeneratingDex()) {
+ if (options.enableStringConcatenationOptimization && !isDebugMode) {
timing.begin("Rewrite string concat");
StringBuilderAppendOptimizer.run(appView, code);
timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
index 4adcf5f..eb12093 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -181,7 +181,7 @@
return methodHandle;
}
- private List<DexValue> rewriteBootstrapArguments(
+ public List<DexValue> rewriteBootstrapArguments(
List<DexValue> bootstrapArgs, MethodHandleUse use, ProgramMethod context) {
List<DexValue> newBootstrapArgs = null;
boolean changed = false;
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 a0fd25e..3e41a70 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
@@ -137,6 +137,7 @@
if (recordRewriter != null) {
desugarings.add(recordRewriter);
}
+ yieldingDesugarings.add(new UnrepresentableInDexInstructionRemover(appView));
}
static NonEmptyCfInstructionDesugaringCollection createForCfToCfNonDesugar(AppView<?> appView) {
@@ -157,6 +158,8 @@
new NonEmptyCfInstructionDesugaringCollection(appView, noAndroidApiLevelCompute());
desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
desugaringCollection.desugarings.add(new InvokeToPrivateRewriter());
+ desugaringCollection.yieldingDesugarings.add(
+ new UnrepresentableInDexInstructionRemover(appView));
return desugaringCollection;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java b/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java
new file mode 100644
index 0000000..6d6dc0f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java
@@ -0,0 +1,397 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import static com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.synthesizeThrowRuntimeExceptionWithMessageMethod;
+
+import com.android.tools.r8.cf.code.CfConstDynamic;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
+import com.android.tools.r8.cf.code.CfConstMethodType;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.errors.UnsupportedConstDynamicDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstMethodHandleDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstMethodTypeDiagnostic;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokePolymorphicMethodHandleDiagnostic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Non desugared invoke-dynamic instructions as well as MethodHandle.invokeX instructions cannot be
+ * represented below O API level. Desugar them into throwing stubs to allow compilation to proceed
+ * under the assumption that the code is dead code.
+ */
+public class UnrepresentableInDexInstructionRemover implements CfInstructionDesugaring {
+
+ private abstract static class InstructionMatcher {
+ final AppView<?> appView;
+ final String descriptor;
+ final AndroidApiLevel supportedApiLevel;
+ // TODO(b/237250957): Using ConcurrentHashMap.newKeySet() causes failures on:
+ // HelloWorldCompiledOnArtTest.testHelloCompiledWithX8Dex[Y, api:21, spec: JDK8, D8_L8DEBUG]
+ final Set<DexMethod> reported = Sets.newConcurrentHashSet();
+
+ InstructionMatcher(AppView<?> appView, String descriptor, AndroidApiLevel supportedApiLevel) {
+ this.appView = appView;
+ this.descriptor = descriptor;
+ this.supportedApiLevel = supportedApiLevel;
+ }
+
+ // Rewrite implementation for each instruction case.
+ abstract DesugarDescription compute(CfInstruction instruction);
+
+ abstract UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position);
+
+ // Helpers
+
+ void report(ProgramMethod context) {
+ if (reported.add(context.getReference())) {
+ UnsupportedFeatureDiagnostic diagnostic =
+ makeDiagnostic(context.getOrigin(), MethodPosition.create(context));
+ assert (diagnostic.getSupportedApiLevel() == -1 && supportedApiLevel == null)
+ || (diagnostic.getSupportedApiLevel() == supportedApiLevel.getLevel());
+ appView.reporter().error(diagnostic);
+ }
+ }
+
+ void invokeThrowingStub(
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ ImmutableList.Builder<CfInstruction> builder) {
+ UtilityMethodForCodeOptimizations throwUtility =
+ synthesizeThrowRuntimeExceptionWithMessageMethod(appView, methodProcessingContext);
+ ProgramMethod throwMethod = throwUtility.uncheckedGetMethod();
+ eventConsumer.acceptThrowMethod(throwMethod, context);
+ builder.add(
+ createMessageString(),
+ new CfInvoke(Opcodes.INVOKESTATIC, throwMethod.getReference(), false),
+ new CfStackInstruction(Opcode.Pop));
+ }
+
+ CfConstString createMessageString() {
+ return new CfConstString(
+ appView
+ .dexItemFactory()
+ .createString(
+ "Instruction is unrepresentable in DEX "
+ + appView.options().getMinApiLevel().getDexVersion()
+ + ": "
+ + descriptor));
+ }
+
+ static void pop(DexType type, Builder<CfInstruction> builder) {
+ assert !type.isVoidType();
+ builder.add(new CfStackInstruction(type.isWideType() ? Opcode.Pop2 : Opcode.Pop));
+ }
+
+ static void pop(Iterable<DexType> types, Builder<CfInstruction> builder) {
+ types.forEach(t -> pop(t, builder));
+ }
+
+ static Builder<CfInstruction> pushReturnValue(DexType type, Builder<CfInstruction> builder) {
+ if (!type.isVoidType()) {
+ builder.add(createDefaultValueForType(type));
+ }
+ return builder;
+ }
+
+ static CfInstruction createDefaultValueForType(DexType type) {
+ assert !type.isVoidType();
+ if (type.isPrimitiveType()) {
+ return new CfConstNumber(0, ValueType.fromDexType(type));
+ }
+ assert type.isReferenceType();
+ return new CfConstNull();
+ }
+ }
+
+ private static class InvokeDynamicMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseInvokeCustom()) {
+ builder.add(new InvokeDynamicMatcher(appView));
+ }
+ }
+
+ InvokeDynamicMatcher(AppView<?> appView) {
+ super(appView, "invoke-dynamic", InternalOptions.invokeCustomApiLevel());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedInvokeCustomDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ CfInvokeDynamic invokeDynamic = instruction.asInvokeDynamic();
+ if (invokeDynamic == null) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ DexCallSite callSite = invokeDynamic.getCallSite();
+ pop(callSite.getMethodProto().getParameters(), replacement);
+ localStackAllocator.allocateLocalStack(1);
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ pushReturnValue(callSite.getMethodProto().getReturnType(), replacement);
+ return replacement.build();
+ })
+ .build();
+ }
+ }
+
+ private static class InvokePolymorphicMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseInvokePolymorphicOnMethodHandle()) {
+ builder.add(new InvokePolymorphicMatcher(appView));
+ }
+ }
+
+ InvokePolymorphicMatcher(AppView<?> appView) {
+ super(
+ appView, "invoke-polymorphic", InternalOptions.invokePolymorphicOnMethodHandleApiLevel());
+ }
+
+ boolean isPolymorphicInvoke(CfInvoke invoke) {
+ return appView.dexItemFactory().polymorphicMethods.isPolymorphicInvoke(invoke.getMethod());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedInvokePolymorphicMethodHandleDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ CfInvoke invoke = instruction.asInvoke();
+ if (invoke == null || !isPolymorphicInvoke(invoke)) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ if (!invoke.isInvokeStatic()) {
+ pop(dexItemFactory.objectType, replacement);
+ }
+ pop(invoke.getMethod().getParameters(), replacement);
+ localStackAllocator.allocateLocalStack(1);
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ pushReturnValue(invoke.getMethod().getReturnType(), replacement);
+ return replacement.build();
+ })
+ .build();
+ }
+ }
+
+ private static class ConstMethodHandleMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseConstantMethodHandle()) {
+ builder.add(new ConstMethodHandleMatcher(appView));
+ }
+ }
+
+ ConstMethodHandleMatcher(AppView<?> appView) {
+ super(appView, "const-method-handle", InternalOptions.constantMethodHandleApiLevel());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedConstMethodHandleDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ if (!(instruction instanceof CfConstMethodHandle)) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ return replacement.add(new CfConstNull()).build();
+ })
+ .build();
+ }
+ }
+
+ private static class ConstMethodTypeMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseConstantMethodType()) {
+ builder.add(new ConstMethodTypeMatcher(appView));
+ }
+ }
+
+ ConstMethodTypeMatcher(AppView<?> appView) {
+ super(appView, "const-method-type", InternalOptions.constantMethodTypeApiLevel());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedConstMethodTypeDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ if (!(instruction instanceof CfConstMethodType)) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ return replacement.add(new CfConstNull()).build();
+ })
+ .build();
+ }
+ }
+
+ private static class ConstDynamicMatcher extends InstructionMatcher {
+ static void addIfNeeded(AppView<?> appView, Builder<InstructionMatcher> builder) {
+ InternalOptions options = appView.options();
+ if (!options.canUseConstantDynamic()) {
+ builder.add(new ConstDynamicMatcher(appView));
+ }
+ }
+
+ ConstDynamicMatcher(AppView<?> appView) {
+ super(appView, "const-dynamic", InternalOptions.constantDynamicApiLevel());
+ }
+
+ @Override
+ UnsupportedFeatureDiagnostic makeDiagnostic(Origin origin, Position position) {
+ return new UnsupportedConstDynamicDiagnostic(origin, position);
+ }
+
+ @Override
+ DesugarDescription compute(CfInstruction instruction) {
+ final CfConstDynamic constDynamic = instruction.asConstDynamic();
+ if (constDynamic == null) {
+ return null;
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) -> {
+ report(context);
+ Builder<CfInstruction> replacement = ImmutableList.builder();
+ invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+ return pushReturnValue(constDynamic.getType(), replacement).build();
+ })
+ .build();
+ }
+ }
+
+ private final List<InstructionMatcher> matchers;
+
+ public UnrepresentableInDexInstructionRemover(AppView<?> appView) {
+ Builder<InstructionMatcher> builder = ImmutableList.builder();
+ InvokeDynamicMatcher.addIfNeeded(appView, builder);
+ InvokePolymorphicMatcher.addIfNeeded(appView, builder);
+ ConstMethodHandleMatcher.addIfNeeded(appView, builder);
+ ConstMethodTypeMatcher.addIfNeeded(appView, builder);
+ ConstDynamicMatcher.addIfNeeded(appView, builder);
+ matchers = builder.build();
+ }
+
+ private DesugarDescription compute(CfInstruction instruction) {
+ for (InstructionMatcher matcher : matchers) {
+ DesugarDescription result = matcher.compute(instruction);
+ if (result != null) {
+ return result;
+ }
+ }
+ return DesugarDescription.nothing();
+ }
+
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringCollection desugaringCollection,
+ DexItemFactory dexItemFactory) {
+ return compute(instruction)
+ .desugarInstruction(
+ freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory);
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ return compute(instruction).needsDesugaring();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
index 8853e8b..b9826d5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -7,16 +7,20 @@
import com.android.tools.r8.cf.code.CfConstDynamic;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.errors.ConstantDynamicDesugarDiagnostic;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.utils.Box;
import java.util.Collection;
import java.util.HashMap;
@@ -33,9 +37,90 @@
this.appView = appView;
}
+ private DesugarDescription report(String message, ProgramMethod context) {
+ return DesugarDescription.builder()
+ .addScanEffect(
+ () ->
+ appView
+ .reporter()
+ .error(
+ new ConstantDynamicDesugarDiagnostic(
+ context.getOrigin(), MethodPosition.create(context), message)))
+ .build();
+ }
+
+ private DesugarDescription computeDesugaring(CfInstruction instruction, ProgramMethod context) {
+ if (!instruction.isConstDynamic()) {
+ return DesugarDescription.nothing();
+ }
+ CfConstDynamic constDynamic = instruction.asConstDynamic();
+ if (!constDynamic.getBootstrapMethodArguments().isEmpty()) {
+ // TODO(b/178172809): Handle bootstrap arguments.
+ return report("Unsupported dynamic constant (has arguments to bootstrap method)", context);
+ }
+ if (!constDynamic.getBootstrapMethod().type.isInvokeStatic()) {
+ return report("Unsupported dynamic constant (not invoke static)", context);
+ }
+ DexItemFactory factory = appView.dexItemFactory();
+ DexMethod bootstrapMethod = constDynamic.getBootstrapMethod().asMethod();
+ DexType holder = bootstrapMethod.getHolderType();
+ if (holder == factory.constantBootstrapsType) {
+ return report("Unsupported dynamic constant (runtime provided bootstrap method)", context);
+ }
+ if (holder != context.getHolderType()) {
+ return report("Unsupported dynamic constant (different owner)", context);
+ }
+ if (bootstrapMethod.getProto().returnType != factory.booleanArrayType
+ && bootstrapMethod.getProto().returnType != factory.objectType) {
+ return report("Unsupported dynamic constant (unsupported constant type)", context);
+ }
+ if (bootstrapMethod.getProto().getParameters().size() != 3) {
+ return report("Unsupported dynamic constant (unsupported signature)", context);
+ }
+ if (bootstrapMethod.getProto().getParameters().get(0) != factory.lookupType) {
+ return report(
+ "Unsupported dynamic constant (unexpected type of first argument to bootstrap method",
+ context);
+ }
+ if (bootstrapMethod.getProto().getParameters().get(1) != factory.stringType) {
+ return report(
+ "Unsupported dynamic constant (unexpected type of second argument to bootstrap method",
+ context);
+ }
+ if (bootstrapMethod.getProto().getParameters().get(2) != factory.classType) {
+ return report(
+ "Unsupported dynamic constant (unexpected type of third argument to bootstrap method",
+ context);
+ }
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context1,
+ methodProcessingContext,
+ dexItemFactory) ->
+ desugarConstDynamicInstruction(
+ instruction.asConstDynamic(),
+ freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context1,
+ methodProcessingContext))
+ .build();
+ }
+
+ @Override
+ public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
+ for (CfInstruction instruction :
+ method.getDefinition().getCode().asCfCode().getInstructions()) {
+ computeDesugaring(instruction, method).scan();
+ }
+ }
+
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
- return instruction.isConstDynamic();
+ return computeDesugaring(instruction, context).needsDesugaring();
}
@Override
@@ -47,17 +132,15 @@
ProgramMethod context,
MethodProcessingContext methodProcessingContext,
CfInstructionDesugaringCollection desugaringCollection,
- DexItemFactory dexItemFactory) {
- if (instruction.isConstDynamic()) {
- return desugarConstDynamicInstruction(
- instruction.asConstDynamic(),
- freshLocalProvider,
- localStackAllocator,
- eventConsumer,
- context,
- methodProcessingContext);
- }
- return null;
+ DexItemFactory factory) {
+ return computeDesugaring(instruction, context)
+ .desugarInstruction(
+ freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ factory);
}
private Collection<CfInstruction> desugarConstDynamicInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
index da8257c..43d76c7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -6,20 +6,22 @@
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import java.util.List;
import java.util.Objects;
public class ConstantDynamicReference {
private final DexString name;
private final DexType type;
private final DexMethodHandle bootstrapMethod;
- private final Object[] bootstrapMethodArguments;
+ private final List<DexValue> bootstrapMethodArguments;
public ConstantDynamicReference(
DexString name,
DexType type,
DexMethodHandle bootstrapMethod,
- Object[] bootstrapMethodArguments) {
- assert bootstrapMethodArguments.length == 0;
+ List<DexValue> bootstrapMethodArguments) {
+ assert bootstrapMethodArguments.isEmpty();
this.name = name;
this.type = type;
this.bootstrapMethod = bootstrapMethod;
@@ -38,7 +40,7 @@
return bootstrapMethod;
}
- public Object[] getBootstrapMethodArguments() {
+ public List<DexValue> getBootstrapMethodArguments() {
return bootstrapMethodArguments;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecificationParser.java
index 328bbf4..e17494f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecificationParser.java
@@ -174,7 +174,8 @@
throw reporter.fatalError(
new StringDiagnostic(
"Unsupported desugared library configuration version, please upgrade the D8/R8"
- + " compiler.",
+ + " compiler."
+ + " See https://developer.android.com/studio/build/library-desugaring-versions.",
origin));
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index 41941e1..f0ec7b2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexApplicationReadFlags;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -35,6 +36,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
@@ -125,7 +127,7 @@
ProgramMethod programMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
CfCode cfCode = programMethod.getDefinition().getCode().asCfCode();
for (CfInstruction instruction : cfCode.getInstructions()) {
- scanInstruction(instruction, eventConsumer);
+ scanInstruction(instruction, eventConsumer, programMethod);
}
}
@@ -134,26 +136,28 @@
// does not rewrite any instruction, and desugarInstruction is expected to rewrite at least one
// instruction for assertions to be valid.
private void scanInstruction(
- CfInstruction instruction, CfInstructionDesugaringEventConsumer eventConsumer) {
+ CfInstruction instruction,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context) {
assert !instruction.isInitClass();
if (instruction.isInvoke()) {
CfInvoke cfInvoke = instruction.asInvoke();
if (refersToRecord(cfInvoke.getMethod(), factory)) {
- ensureRecordClass(eventConsumer);
+ ensureRecordClass(eventConsumer, context);
}
return;
}
if (instruction.isFieldInstruction()) {
CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
if (refersToRecord(fieldInstruction.getField(), factory)) {
- ensureRecordClass(eventConsumer);
+ ensureRecordClass(eventConsumer, context);
}
return;
}
if (instruction.isTypeInstruction()) {
CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
if (refersToRecord(typeInstruction.getType(), factory)) {
- ensureRecordClass(eventConsumer);
+ ensureRecordClass(eventConsumer, context);
}
return;
}
@@ -369,15 +373,25 @@
return false;
}
- private void ensureRecordClass(RecordDesugaringEventConsumer eventConsumer) {
+ /**
+ * If java.lang.Record is referenced from a class' supertype or a program method/field signature,
+ * then the global synthetic is generated upfront of the compilation to avoid confusing D8/R8.
+ *
+ * <p>However, if java.lang.Record is referenced only from an instruction, for example, the code
+ * contains "x instance of java.lang.Record" but no record type is present, then the global
+ * synthetic is generated during instruction desugaring scanning.
+ */
+ private void ensureRecordClass(
+ RecordDesugaringEventConsumer eventConsumer, Collection<ProgramDefinition> contexts) {
DexItemFactory factory = appView.dexItemFactory();
checkRecordTagNotPresent(factory);
appView
.getSyntheticItems()
- .legacyEnsureGlobalClass(
+ .ensureGlobalClass(
() -> new MissingGlobalSyntheticsConsumerDiagnostic("Record desugaring"),
kinds -> kinds.RECORD_TAG,
factory.recordType,
+ contexts,
appView,
builder -> {
DexEncodedMethod init = synthesizeRecordInitMethod();
@@ -386,6 +400,11 @@
eventConsumer::acceptRecordClass);
}
+ private void ensureRecordClass(
+ RecordDesugaringEventConsumer eventConsumer, ProgramDefinition context) {
+ ensureRecordClass(eventConsumer, ImmutableList.of(context));
+ }
+
private void checkRecordTagNotPresent(DexItemFactory factory) {
DexClass r8RecordClass =
appView.appInfo().definitionForWithoutExistenceAssert(factory.recordTagType);
@@ -483,8 +502,16 @@
public void synthesizeClasses(
ClassSynthesisDesugaringContext processingContext,
CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
- if (appView.appInfo().app().getFlags().hasReadRecordReferenceFromProgramClass()) {
- ensureRecordClass(eventConsumer);
+ DexApplicationReadFlags flags = appView.appInfo().app().getFlags();
+ if (flags.hasReadRecordReferenceFromProgramClass()) {
+ List<ProgramDefinition> classes = new ArrayList<>();
+ for (DexType recordWitness : flags.getRecordWitnesses()) {
+ DexClass dexClass = appView.contextIndependentDefinitionFor(recordWitness);
+ assert dexClass != null;
+ assert dexClass.isProgramClass();
+ classes.add(dexClass.asProgramClass());
+ }
+ ensureRecordClass(eventConsumer, classes);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index 257fd7d..33e5159 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -172,6 +172,37 @@
.CfUtilityMethodsForCodeOptimizationsTemplates_throwNoSuchMethodError(options, method);
}
+ public static UtilityMethodForCodeOptimizations synthesizeThrowRuntimeExceptionWithMessageMethod(
+ AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+ InternalOptions options = appView.options();
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexProto proto =
+ dexItemFactory.createProto(dexItemFactory.runtimeExceptionType, dexItemFactory.stringType);
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ ProgramMethod syntheticMethod =
+ syntheticItems.createMethod(
+ kinds -> kinds.THROW_RTE,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder ->
+ builder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setClassFileVersion(CfVersion.V1_8)
+ .setApiLevelForDefinition(appView.computedMinApiLevel())
+ .setApiLevelForCode(appView.computedMinApiLevel())
+ .setCode(
+ method -> getThrowRuntimeExceptionWithMessageCodeTemplate(method, options))
+ .setProto(proto));
+ return new UtilityMethodForCodeOptimizations(syntheticMethod);
+ }
+
+ private static CfCode getThrowRuntimeExceptionWithMessageCodeTemplate(
+ DexMethod method, InternalOptions options) {
+ return CfUtilityMethodsForCodeOptimizations
+ .CfUtilityMethodsForCodeOptimizationsTemplates_throwRuntimeExceptionWithMessage(
+ options, method);
+ }
+
public static class UtilityMethodForCodeOptimizations {
private final ProgramMethod method;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
deleted file mode 100644
index bb75dc2..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
+++ /dev/null
@@ -1,180 +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.ir.optimize.string;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.Sets;
-import java.util.Set;
-
-/**
- * This defines a simple program analysis that determines if there is a path from a call to append()
- * on a StringBuilder back to itself in the control flow graph.
- *
- * <p>The analysis explicitly allows paths from a call to append() back to itself that go through a
- * call to toString() on the builder. This ensures that we can still optimize builders that are
- * fully enclosed in a loop: <code>
- * while (true) {
- * System.out.println(new StringBuilder().append("42").toString());
- * }
- * </code>
- */
-class StringBuilderAppendFlowAnalysis {
-
- /**
- * Returns true if there is a call to {@code append()} on {@param builder}, which is inside a
- * loop.
- */
- static boolean hasAppendInstructionInLoop(
- AppView<?> appView,
- IRCode code,
- Value builder,
- StringBuilderOptimizationConfiguration configuration) {
- IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis =
- new IntraproceduralDataflowAnalysis<>(
- appView,
- AbstractStateImpl.bottom(),
- code,
- new TransferFunction(builder, configuration));
- DataflowAnalysisResult result = analysis.run(builder.definition.getBlock());
- return result.isFailedAnalysisResult();
- }
-
- /** This defines the state that we keep track of for each {@link BasicBlock}. */
- private static class AbstractStateImpl extends AbstractState<AbstractStateImpl> {
-
- private static final AbstractStateImpl BOTTOM = new AbstractStateImpl();
-
- // The set of invoke instructions that call append(), which is on a path to the current program
- // point.
- private final Set<InvokeVirtual> liveAppendInstructions;
-
- private AbstractStateImpl() {
- this(Sets.newIdentityHashSet());
- }
-
- private AbstractStateImpl(Set<InvokeVirtual> liveAppendInstructions) {
- this.liveAppendInstructions = liveAppendInstructions;
- }
-
- public static AbstractStateImpl bottom() {
- return BOTTOM;
- }
-
- private AbstractStateImpl addLiveAppendInstruction(InvokeVirtual invoke) {
- Set<InvokeVirtual> newLiveAppendInstructions =
- SetUtils.newIdentityHashSet(liveAppendInstructions);
- newLiveAppendInstructions.add(invoke);
- return new AbstractStateImpl(newLiveAppendInstructions);
- }
-
- private boolean isAppendInstructionLive(InvokeVirtual invoke) {
- return liveAppendInstructions.contains(invoke);
- }
-
- @Override
- public AbstractStateImpl asAbstractState() {
- return this;
- }
-
- @Override
- public AbstractStateImpl join(AppView<?> appView, AbstractStateImpl state) {
- if (liveAppendInstructions.isEmpty()) {
- return state;
- }
- if (state.liveAppendInstructions.isEmpty()) {
- return this;
- }
- Set<InvokeVirtual> newLiveAppendInstructions =
- SetUtils.newIdentityHashSet(liveAppendInstructions, state.liveAppendInstructions);
- return new AbstractStateImpl(newLiveAppendInstructions);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
- AbstractStateImpl state = (AbstractStateImpl) other;
- return liveAppendInstructions.equals(state.liveAppendInstructions);
- }
-
- @Override
- public int hashCode() {
- return liveAppendInstructions.hashCode();
- }
- }
-
- /**
- * This defines the transfer function for the analysis.
- *
- * <p>If a call to {@code append()} on the builder is seen, then that invoke instruction is added
- * to the abstract state.
- *
- * <p>If a call to {@code toString()} on the builder i seen, then the abstract state is reset to
- * bottom.
- */
- private static class TransferFunction
- implements AbstractTransferFunction<BasicBlock, Instruction, AbstractStateImpl> {
-
- private final Value builder;
- private final StringBuilderOptimizationConfiguration configuration;
-
- private TransferFunction(Value builder, StringBuilderOptimizationConfiguration configuration) {
- this.builder = builder;
- this.configuration = configuration;
- }
-
- @Override
- public TransferFunctionResult<AbstractStateImpl> apply(
- Instruction instruction, AbstractStateImpl state) {
- if (instruction.isInvokeMethod()) {
- return apply(state, instruction.asInvokeMethod());
- }
- return state;
- }
-
- private TransferFunctionResult<AbstractStateImpl> apply(
- AbstractStateImpl state, InvokeMethod invoke) {
- if (isAppendOnBuilder(invoke)) {
- assert invoke.isInvokeVirtual();
- InvokeVirtual appendInvoke = invoke.asInvokeVirtual();
- if (state.isAppendInstructionLive(appendInvoke)) {
- return new FailedTransferFunctionResult<>();
- }
- return state.addLiveAppendInstruction(appendInvoke);
- }
- if (isToStringOnBuilder(invoke)) {
- return AbstractStateImpl.bottom();
- }
- return state;
- }
-
- private boolean isAppendOnBuilder(InvokeMethod invoke) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- return configuration.isAppendMethod(invokedMethod)
- && invoke.getArgument(0).getAliasedValue() == builder;
- }
-
- private boolean isToStringOnBuilder(InvokeMethod invoke) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- return configuration.isToStringMethod(invokedMethod)
- && invoke.getArgument(0).getAliasedValue() == builder;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index 830516e..2606826 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.string.StringBuilderNode.AppendNode;
@@ -262,7 +263,7 @@
assert newInstanceValue != null;
nodeConsumer.accept(
newInstanceValue, createNewInstanceNode(instruction.asNewInstance()));
- } else {
+ } else if (instruction.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
Value receiver = invoke.getReceiver();
if (oracle.isInit(instruction)) {
@@ -316,6 +317,17 @@
escaped ->
nodeConsumer.accept(escaped, createOtherStringBuilderNode(instruction)));
}
+ } else {
+ assert instruction.isInvokeStatic();
+ InvokeStatic invoke = instruction.asInvokeStatic();
+ assert invoke.getInvokedMethod()
+ == appView.dexItemFactory().objectsMethods.toStringWithObject;
+ visitStringBuilderValues(
+ invoke.getFirstOperand(),
+ escapeState,
+ actual ->
+ nodeConsumer.accept(actual, createToStringNode(instruction.asInvokeMethod())),
+ escaped -> nodeConsumer.accept(escaped, createInspectionNode(instruction)));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java
index bc92a0f..e2d46af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java
@@ -63,7 +63,8 @@
}
}
if (isStringBuilderInstruction) {
- if (instruction.isInvokeMethodWithReceiver()) {
+ if (instruction.isInvokeMethod()) {
+ assert !instruction.inValues().isEmpty();
Value firstOperand = instruction.getFirstOperand();
if (!builder.getLiveStringBuilders().contains(firstOperand)) {
// We can have constant NULL being the first operand, which we have not marked as
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
index 22d5c70..c189299 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewInstance;
import com.google.common.collect.Sets;
@@ -426,9 +427,9 @@
*/
static class ToStringNode extends StringBuilderNode implements StringBuilderInstruction {
- private final InvokeVirtual instruction;
+ private final InvokeMethod instruction;
- private ToStringNode(InvokeVirtual instruction) {
+ private ToStringNode(InvokeMethod instruction) {
this.instruction = instruction;
}
@@ -534,7 +535,7 @@
}
/**
- * ImplicitToStringNode are placed a StringBuilder/StringBuffer is appended to another
+ * ImplicitToStringNode are placed when StringBuilder/StringBuffer is appended to another
* StringBuilder/StringBuffer.
*/
static class ImplicitToStringNode extends StringBuilderNode {
@@ -588,7 +589,7 @@
return new AppendNode(instruction);
}
- static ToStringNode createToStringNode(InvokeVirtual instruction) {
+ static ToStringNode createToStringNode(InvokeMethod instruction) {
return new ToStringNode(instruction);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizationConfiguration.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizationConfiguration.java
deleted file mode 100644
index d1612c3..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizationConfiguration.java
+++ /dev/null
@@ -1,24 +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.ir.optimize.string;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.InvokeMethod;
-
-interface StringBuilderOptimizationConfiguration {
- boolean isBuilderType(DexType type);
-
- boolean isBuilderInit(DexMethod method, DexType builderType);
-
- boolean isBuilderInit(DexMethod method);
-
- boolean isBuilderInitWithInitialValue(InvokeMethod invoke);
-
- boolean isAppendMethod(DexMethod method);
-
- boolean isSupportedAppendMethod(InvokeMethod invoke);
-
- boolean isToStringMethod(DexMethod method);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
deleted file mode 100644
index 957ccfe..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ /dev/null
@@ -1,1024 +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.ir.optimize.string;
-
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.escape.EscapeAnalysis;
-import com.android.tools.r8.ir.analysis.escape.EscapeAnalysisConfiguration;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstNumber;
-import com.android.tools.r8.ir.code.ConstString;
-import com.android.tools.r8.ir.code.DominatorTree;
-import com.android.tools.r8.ir.code.DominatorTree.Assumption;
-import com.android.tools.r8.ir.code.Goto;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.NumberConversion;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-// This optimization attempts to replace all builder.toString() calls with a constant string.
-// TODO(b/114002137): for now, the analysis depends on rewriteMoveResult.
-// Consider the following example:
-//
-// StringBuilder builder;
-// if (...) {
-// builder.append("X");
-// } else {
-// builder.append("Y");
-// }
-// builder.toString();
-//
-// Its corresponding IR looks like:
-// block0:
-// b <- new-instance StringBuilder
-// if ... block2 // Otherwise, fallthrough
-// block1:
-// c1 <- "X"
-// b1 <- invoke-virtual b, c1, ...append
-// goto block3
-// block2:
-// c2 <- "Y"
-// b2 <- invoke-virtual b, c2, ...append
-// goto block3
-// block3:
-// invoke-virtual b, ...toString
-//
-// After rewriteMoveResult, aliased out values, b1 and b2, are gone. So the analysis can focus on
-// single SSA values, assuming it's flow-sensitive (which is not true in general).
-public class StringBuilderOptimizer {
-
- private final AppView<?> appView;
- private final DexItemFactory factory;
- @VisibleForTesting
- StringConcatenationAnalysis analysis;
- final StringBuilderOptimizationConfiguration optimizationConfiguration;
-
- private int numberOfBuildersWithMultipleToString = 0;
- private int numberOfBuildersWithoutToString = 0;
- private int numberOfBuildersThatEscape = 0;
- private int numberOfBuildersWhoseResultIsInterned = 0;
- private int numberOfBuildersWithNonTrivialStateChange = 0;
- private int numberOfBuildersWithUnsupportedArg = 0;
- private int numberOfBuildersWithMergingPoints = 0;
- private int numberOfBuildersWithNonDeterministicArg = 0;
- private int numberOfDeadBuilders = 0;
- private int numberOfBuildersSimplified = 0;
- private final Object2IntMap<Integer> histogramOfLengthOfAppendChains;
- private final Object2IntMap<Integer> histogramOfLengthOfEndResult;
- private final Object2IntMap<Integer> histogramOfLengthOfPartialAppendChains;
- private final Object2IntMap<Integer> histogramOfLengthOfPartialResult;
-
- public StringBuilderOptimizer(AppView<? extends AppInfo> appView) {
- this.appView = appView;
- this.factory = appView.dexItemFactory();
- this.optimizationConfiguration = new DefaultStringBuilderOptimizationConfiguration();
- if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
- histogramOfLengthOfAppendChains = new Object2IntArrayMap<>();
- histogramOfLengthOfEndResult = new Object2IntArrayMap<>();
- histogramOfLengthOfPartialAppendChains = new Object2IntArrayMap<>();
- histogramOfLengthOfPartialResult = new Object2IntArrayMap<>();
- } else {
- histogramOfLengthOfAppendChains = null;
- histogramOfLengthOfEndResult = null;
- histogramOfLengthOfPartialAppendChains = null;
- histogramOfLengthOfPartialResult = null;
- }
- }
-
- public void logResults() {
- assert Log.ENABLED;
- Log.info(getClass(),
- "# builders w/ multiple toString(): %s", numberOfBuildersWithMultipleToString);
- Log.info(getClass(),
- "# builders w/o toString(): %s", numberOfBuildersWithoutToString);
- Log.info(getClass(),
- "# builders that escape: %s", numberOfBuildersThatEscape);
- Log.info(getClass(),
- "# builders whose result is interned: %s", numberOfBuildersWhoseResultIsInterned);
- Log.info(getClass(),
- "# builders w/ non-trivial state change: %s", numberOfBuildersWithNonTrivialStateChange);
- Log.info(getClass(),
- "# builders w/ unsupported arg: %s", numberOfBuildersWithUnsupportedArg);
- Log.info(getClass(),
- "# builders w/ merging points: %s", numberOfBuildersWithMergingPoints);
- Log.info(getClass(),
- "# builders w/ non-deterministic arg: %s", numberOfBuildersWithNonDeterministicArg);
- Log.info(getClass(), "# dead builders : %s", numberOfDeadBuilders);
- Log.info(getClass(), "# builders simplified: %s", numberOfBuildersSimplified);
- if (histogramOfLengthOfAppendChains != null) {
- Log.info(getClass(), "------ histogram of StringBuilder append chain lengths ------");
- histogramOfLengthOfAppendChains.forEach((chainSize, count) -> {
- Log.info(getClass(),
- "%s: %s (%s)", chainSize, StringUtils.times("*", Math.min(count, 53)), count);
- });
- }
- if (histogramOfLengthOfEndResult != null) {
- Log.info(getClass(), "------ histogram of StringBuilder result lengths ------");
- histogramOfLengthOfEndResult.forEach((length, count) -> {
- Log.info(getClass(),
- "%s: %s (%s)", length, StringUtils.times("*", Math.min(count, 53)), count);
- });
- }
- if (histogramOfLengthOfPartialAppendChains != null) {
- Log.info(getClass(),
- "------ histogram of StringBuilder append chain lengths (partial) ------");
- histogramOfLengthOfPartialAppendChains.forEach((chainSize, count) -> {
- Log.info(getClass(),
- "%s: %s (%s)", chainSize, StringUtils.times("*", Math.min(count, 53)), count);
- });
- }
- if (histogramOfLengthOfPartialResult != null) {
- Log.info(getClass(), "------ histogram of StringBuilder partial result lengths ------");
- histogramOfLengthOfPartialResult.forEach(
- (length, count) ->
- Log.info(
- getClass(),
- "%s: %s (%s)",
- length,
- StringUtils.times("*", Math.min(count, 53)),
- count));
- }
- }
-
- public void computeTrivialStringConcatenation(IRCode code) {
- StringConcatenationAnalysis analysis = new StringConcatenationAnalysis(code);
- // Only for testing purpose, where we ran the analysis for only one method.
- // Using `this.analysis` is not thread-safe, of course.
- this.analysis = analysis;
- Set<Value> candidateBuilders =
- analysis.findAllLocalBuilders()
- .stream()
- .filter(analysis::canBeOptimized)
- .collect(Collectors.toSet());
- if (candidateBuilders.isEmpty()) {
- return;
- }
- analysis
- .buildBuilderStateGraph(candidateBuilders)
- .applyConcatenationResults(candidateBuilders)
- .removeTrivialBuilders();
- }
-
- class StringConcatenationAnalysis {
-
- // Inspired by {@link JumboStringTest}. Some code intentionally may have too many append(...).
- private static final int CONCATENATION_THRESHOLD = 200;
- private static final String ANY_STRING = "*";
- private static final String DUMMY = "$dummy$";
-
- private final IRCode code;
-
- // A map from SSA Value of StringBuilder type to its toString() counts.
- // Reused (e.g., concatenated, toString, concatenated more, toString) builders are out of scope.
- // TODO(b/114002137): some of those toString could have constant string states.
- final Object2IntMap<Value> builderToStringCounts = new Object2IntArrayMap<>();
-
- StringConcatenationAnalysis(IRCode code) {
- this.code = code;
- }
-
- // This optimization focuses on builders that are created and used locally.
- // In the first step, we collect builders that are created in the current method.
- // In the next step, we will filter out builders that cannot be optimized. To avoid multiple
- // iterations per builder, we're collecting # of uses of those builders by iterating the code
- // twice in this step.
- private Set<Value> findAllLocalBuilders() {
- // During the first iteration, collect builders that are locally created.
- // TODO(b/114002137): Make sure new-instance is followed by <init> before any other calls.
- for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) {
- if (optimizationConfiguration.isBuilderType(newInstance.clazz)) {
- Value builder = newInstance.asNewInstance().dest();
- assert !builderToStringCounts.containsKey(builder);
- builderToStringCounts.put(builder, 0);
- }
- }
- if (builderToStringCounts.isEmpty()) {
- return ImmutableSet.of();
- }
- int concatenationCount = 0;
- // During the second iteration, count builders' usage.
- for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isAppendMethod(invokedMethod)) {
- concatenationCount++;
- // The analysis might be overwhelmed.
- if (concatenationCount > CONCATENATION_THRESHOLD) {
- return ImmutableSet.of();
- }
- } else if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
- assert invoke.arguments().size() == 1;
- Value receiver = invoke.getArgument(0).getAliasedValue();
- for (Value builder : collectAllLinkedBuilders(receiver)) {
- if (builderToStringCounts.containsKey(builder)) {
- int count = builderToStringCounts.getInt(builder);
- builderToStringCounts.put(builder, count + 1);
- }
- }
- }
- }
- return builderToStringCounts.keySet();
- }
-
- private Set<Value> collectAllLinkedBuilders(Value builder) {
- Set<Value> builders = Sets.newIdentityHashSet();
- Set<Value> visited = Sets.newIdentityHashSet();
- collectAllLinkedBuilders(builder, builders, visited);
- return builders;
- }
-
- private void collectAllLinkedBuilders(Value builder, Set<Value> builders, Set<Value> visited) {
- if (!visited.add(builder)) {
- return;
- }
- if (builder.isPhi()) {
- for (Value operand : builder.asPhi().getOperands()) {
- collectAllLinkedBuilders(operand, builders, visited);
- }
- } else {
- builders.add(builder);
- }
- }
-
- private boolean canBeOptimized(Value builder) {
- // If the builder is definitely null, it may be handled by other optimizations.
- // E.g., any further operations, such as append, will raise NPE.
- // But, as we collect local builders, it should never be null.
- assert !builder.isAlwaysNull(appView);
- // Before checking the builder usage, make sure we have its usage count.
- assert builderToStringCounts.containsKey(builder);
- // If a builder is reused, chances are the code is not trivial, e.g., building a prefix
- // at some point; appending different suffices in different conditions; and building again.
- if (builderToStringCounts.getInt(builder) > 1) {
- numberOfBuildersWithMultipleToString++;
- return false;
- }
- // If a builder is not used, i.e., never converted to string, it doesn't make sense to
- // attempt to compute its compile-time constant string.
- if (builderToStringCounts.getInt(builder) < 1) {
- numberOfBuildersWithoutToString++;
- return false;
- }
- // Make sure builder is neither phi nor coming from outside of the method.
- assert !builder.isPhi() && builder.definition.isNewInstance();
- assert builder.getType().isClassType();
- DexType builderType = builder.getType().asClassType().getClassType();
- assert optimizationConfiguration.isBuilderType(builderType);
- EscapeAnalysis escapeAnalysis =
- new EscapeAnalysis(
- appView, new StringBuilderOptimizerEscapeAnalysisConfiguration(builder));
- return !escapeAnalysis.isEscaping(code, builder);
- }
-
- // A map from SSA Value of StringBuilder type to its internal state per instruction.
- final Map<Value, Map<Instruction, BuilderState>> builderStates = new HashMap<>();
-
- // Create a builder state, only used when visiting new-instance instructions.
- private Map<Instruction, BuilderState> createBuilderState(Value builder) {
- // By using LinkedHashMap, we want to visit instructions in the order of their insertions.
- return builderStates.computeIfAbsent(builder, ignore -> new LinkedHashMap<>());
- }
-
- // Get a builder state, used for all other cases except for new-instance instructions.
- private Map<Instruction, BuilderState> getBuilderState(Value builder) {
- return builderStates.get(builder);
- }
-
- // Suppose a simple, trivial chain:
- // new StringBuilder().append("a").append("b").toString();
- //
- // the resulting graph would be:
- // [s1, root, "", {s2}],
- // [s2, s1, "a", {s3}],
- // [s3, s2, "b", {}].
- //
- // For each meaningful IR (init, append, toString), the corresponding state will be bound:
- // <init> -> s1, 1st append -> s2, 2nd append -> s3, toString -> s3.
- //
- // Suppose an example with a phi:
- // StringBuilder b = flag ? new StringBuilder("x") : new StringBuilder("y");
- // b.append("z").toString();
- //
- // the resulting graph would be:
- // [s1, root, "", {s2, s3, s4}],
- // [s2, s1, "x", {}],
- // [s3, s1, "y", {}],
- // [s4, s1, "z", {}].
- //
- // Note that neither s2 nor s3 can dominate s4, and thus all of s{2..4} are linked to s1.
- // An alternative graph shape would be: [s4, {s2, s3}, "z", {}] (and proper successor update in
- // s2 and s3, of course). But, from the point of the view of finding the trivial chain, there is
- // no difference. The current graph construction relies on and resembles dominator tree.
- private StringConcatenationAnalysis buildBuilderStateGraph(Set<Value> candidateBuilders) {
- DominatorTree dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
- for (BasicBlock block : code.topologicallySortedBlocks()) {
- for (Instruction instr : block.getInstructions()) {
- if (instr.isNewInstance()
- && optimizationConfiguration.isBuilderType(instr.asNewInstance().clazz)) {
- Value builder = instr.asNewInstance().dest();
- if (!candidateBuilders.contains(builder)) {
- continue;
- }
- if (builder.hasPhiUsers()) {
- candidateBuilders.remove(builder);
- continue;
- }
- Map<Instruction, BuilderState> perInstrState = createBuilderState(builder);
- perInstrState.put(instr, BuilderState.createRoot());
- continue;
- }
-
- if (instr.isInvokeDirect()
- && optimizationConfiguration.isBuilderInitWithInitialValue(instr.asInvokeDirect())) {
- InvokeDirect invoke = instr.asInvokeDirect();
- Value builder = invoke.getReceiver().getAliasedValue();
- if (!candidateBuilders.contains(builder)) {
- continue;
- }
- assert invoke.inValues().size() == 2;
- Value arg = invoke.getFirstNonReceiverArgument().getAliasedValue();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- DexType parameterType = invokedMethod.getParameter(0);
- String addition = extractConstantArgument(arg, invokedMethod, parameterType);
- Map<Instruction, BuilderState> perInstrState = getBuilderState(builder);
- BuilderState dominantState = findDominantState(dominatorTree, perInstrState, instr);
- if (dominantState != null) {
- BuilderState currentState = dominantState.createChild(addition);
- perInstrState.put(instr, currentState);
- } else {
- // TODO(b/114002137): if we want to utilize partial results, don't remove it here.
- candidateBuilders.remove(builder);
- }
- continue;
- }
-
- if (!instr.isInvokeMethodWithReceiver()) {
- continue;
- }
-
- InvokeMethodWithReceiver invoke = instr.asInvokeMethodWithReceiver();
- if (optimizationConfiguration.isAppendMethod(invoke.getInvokedMethod())) {
- Value builder = invoke.getReceiver().getAliasedValue();
- // If `builder` is phi, itself and predecessors won't be tracked.
- // Not being tracked means that they won't be optimized, which is intentional.
- if (!candidateBuilders.contains(builder)) {
- continue;
- }
- if (invoke.hasUsedOutValue()) {
- candidateBuilders.remove(builder);
- continue;
- }
- Value arg = invoke.getFirstNonReceiverArgument().getAliasedValue();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- DexType parameterType = invokedMethod.getParameter(0);
- String addition = extractConstantArgument(arg, invokedMethod, parameterType);
- Map<Instruction, BuilderState> perInstrState = getBuilderState(builder);
- BuilderState dominantState = findDominantState(dominatorTree, perInstrState, instr);
- if (dominantState != null) {
- BuilderState currentState = dominantState.createChild(addition);
- perInstrState.put(instr, currentState);
- } else {
- // TODO(b/114002137): if we want to utilize partial results, don't remove it here.
- candidateBuilders.remove(builder);
- }
- }
- if (optimizationConfiguration.isToStringMethod(invoke.getInvokedMethod())) {
- Value builder = invoke.getReceiver().getAliasedValue();
- // If `builder` is phi, itself and predecessors won't be tracked.
- // Not being tracked means that they won't be optimized, which is intentional.
- if (!candidateBuilders.contains(builder)) {
- continue;
- }
- Map<Instruction, BuilderState> perInstrState = getBuilderState(builder);
- BuilderState dominantState = findDominantState(dominatorTree, perInstrState, instr);
- if (dominantState != null) {
- // Instead of using the dominant state directly, treat this retrieval point as a new
- // state without an addition so that dominant state can account for dependent states.
- BuilderState currentState = dominantState.createChild("");
- perInstrState.put(instr, currentState);
- } else {
- // TODO(b/114002137): if we want to utilize partial results, don't remove it here.
- candidateBuilders.remove(builder);
- }
- }
- }
- }
-
- return this;
- }
-
- private String extractConstantArgument(Value arg, DexMethod method, DexType argType) {
- String addition = ANY_STRING;
- if (arg.isPhi()) {
- return addition;
- }
- if (arg.definition.isConstString()) {
- addition = arg.definition.asConstString().getValue().toString();
- } else if (arg.definition.isConstNumber() || arg.definition.isNumberConversion()) {
- Number number = extractConstantNumber(arg);
- if (number == null) {
- return addition;
- }
- if (arg.getType().isPrimitiveType()) {
- if (argType == factory.booleanType) {
- addition = String.valueOf(number.intValue() != 0);
- } else if (argType == factory.byteType) {
- addition = String.valueOf(number.byteValue());
- } else if (argType == factory.shortType) {
- addition = String.valueOf(number.shortValue());
- } else if (argType == factory.charType) {
- addition = String.valueOf((char) number.intValue());
- } else if (argType == factory.intType) {
- addition = String.valueOf(number.intValue());
- } else if (argType == factory.longType) {
- addition = String.valueOf(number.longValue());
- } else if (argType == factory.floatType) {
- addition = String.valueOf(number.floatValue());
- } else if (argType == factory.doubleType) {
- addition = String.valueOf(number.doubleValue());
- }
- } else if (arg.getType().isNullType()
- && !method.isInstanceInitializer(factory)
- && argType != factory.charArrayType) {
- assert number.intValue() == 0;
- addition = "null";
- }
- }
- return addition;
- }
-
- private Number extractConstantNumber(Value arg) {
- if (arg.isPhi()) {
- return null;
- }
- if (arg.definition.isConstNumber()) {
- ConstNumber cst = arg.definition.asConstNumber();
- if (cst.outType() == ValueType.LONG) {
- return cst.getLongValue();
- } else if (cst.outType() == ValueType.FLOAT) {
- return cst.getFloatValue();
- } else if (cst.outType() == ValueType.DOUBLE) {
- return cst.getDoubleValue();
- } else {
- assert cst.outType() == ValueType.INT || cst.outType() == ValueType.OBJECT;
- return cst.getIntValue();
- }
- } else if (arg.definition.isNumberConversion()) {
- NumberConversion conversion = arg.definition.asNumberConversion();
- assert conversion.inValues().size() == 1;
- Number temp = extractConstantNumber(conversion.inValues().get(0));
- if (temp == null) {
- return null;
- }
- DexType conversionType = conversion.to.toDexType(factory);
- if (conversionType == factory.booleanType) {
- return temp.intValue() != 0 ? 1 : 0;
- } else if (conversionType == factory.byteType) {
- return temp.byteValue();
- } else if (conversionType == factory.shortType) {
- return temp.shortValue();
- } else if (conversionType == factory.charType) {
- return temp.intValue();
- } else if (conversionType == factory.intType) {
- return temp.intValue();
- } else if (conversionType == factory.longType) {
- return temp.longValue();
- } else if (conversionType == factory.floatType) {
- return temp.floatValue();
- } else if (conversionType == factory.doubleType) {
- return temp.doubleValue();
- }
- }
- return null;
- }
-
- private BuilderState findDominantState(
- DominatorTree dominatorTree,
- Map<Instruction, BuilderState> perInstrState,
- Instruction current) {
- BuilderState result = null;
- BasicBlock lastDominantBlock = null;
- for (Instruction instr : perInstrState.keySet()) {
- BasicBlock block = instr.getBlock();
- if (!dominatorTree.dominatedBy(current.getBlock(), block)) {
- continue;
- }
- if (lastDominantBlock == null
- || dominatorTree.dominatedBy(block, lastDominantBlock)) {
- result = perInstrState.get(instr);
- lastDominantBlock = block;
- }
- }
- return result;
- }
-
- private void logHistogramOfChains(List<String> contents, boolean isPartial) {
- if (!Log.ENABLED || !Log.isLoggingEnabledFor(StringOptimizer.class)) {
- return;
- }
- if (contents == null || contents.isEmpty()) {
- return;
- }
- String result = StringUtils.join("", contents);
- Integer size = Integer.valueOf(contents.size());
- Integer length = Integer.valueOf(result.length());
- if (isPartial) {
- assert histogramOfLengthOfPartialAppendChains != null;
- synchronized (histogramOfLengthOfPartialAppendChains) {
- int count = histogramOfLengthOfPartialAppendChains.getOrDefault(size, 0);
- histogramOfLengthOfPartialAppendChains.put(size, count + 1);
- }
- assert histogramOfLengthOfPartialResult != null;
- synchronized (histogramOfLengthOfPartialResult) {
- int count = histogramOfLengthOfPartialResult.getOrDefault(length, 0);
- histogramOfLengthOfPartialResult.put(length, count + 1);
- }
- } else {
- assert histogramOfLengthOfAppendChains != null;
- synchronized (histogramOfLengthOfAppendChains) {
- int count = histogramOfLengthOfAppendChains.getOrDefault(size, 0);
- histogramOfLengthOfAppendChains.put(size, count + 1);
- }
- assert histogramOfLengthOfEndResult != null;
- synchronized (histogramOfLengthOfEndResult) {
- int count = histogramOfLengthOfEndResult.getOrDefault(length, 0);
- histogramOfLengthOfEndResult.put(length, count + 1);
- }
- }
- }
-
- // StringBuilders that are simplified by this analysis or simply dead (e.g., after applying
- // -assumenosideeffects to logging calls, then logging messages built by builders are dead).
- // Will be used to clean up uses of the builders, such as creation, <init>, and append calls.
- final Set<Value> deadBuilders = Sets.newIdentityHashSet();
- final Set<Value> simplifiedBuilders = Sets.newIdentityHashSet();
-
- private StringConcatenationAnalysis applyConcatenationResults(Set<Value> candidateBuilders) {
- Set<Value> affectedValues = Sets.newIdentityHashSet();
- InstructionListIterator it = code.instructionListIterator();
- while (it.hasNext()) {
- Instruction instr = it.nextUntil(i -> isToStringOfInterest(candidateBuilders, i));
- if (instr == null) {
- break;
- }
- InvokeMethod invoke = instr.asInvokeMethod();
- assert invoke.arguments().size() == 1;
- Value builder = invoke.getArgument(0).getAliasedValue();
- Value outValue = invoke.outValue();
- if (outValue == null || outValue.isDead(appView, code)) {
- // If the out value is still used but potentially dead, replace it with a dummy string.
- if (outValue != null && outValue.isUsed()) {
- Value dummy =
- code.createValue(
- TypeElement.stringClassType(appView, definitelyNotNull()),
- invoke.getLocalInfo());
- it.replaceCurrentInstruction(new ConstString(dummy, factory.createString(DUMMY)));
- } else {
- it.removeOrReplaceByDebugLocalRead();
- }
- // Although this builder is not simplified by this analysis, add that to the set so that
- // it can be removed at the final clean-up phase.
- deadBuilders.add(builder);
- numberOfDeadBuilders++;
- continue;
- }
- Map<Instruction, BuilderState> perInstrState = builderStates.get(builder);
- assert perInstrState != null;
- BuilderState builderState = perInstrState.get(instr);
- assert builderState != null;
- String element = toCompileTimeString(builder, builderState);
- assert element != null;
- Value stringValue =
- code.createValue(
- TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
- affectedValues.addAll(outValue.affectedValues());
- it.replaceCurrentInstruction(new ConstString(stringValue, factory.createString(element)));
- simplifiedBuilders.add(builder);
- numberOfBuildersSimplified++;
- }
- // Concatenation results are not null, and thus propagate that information.
- if (!affectedValues.isEmpty()) {
- new TypeAnalysis(appView).narrowing(affectedValues);
- }
- return this;
- }
-
- private boolean isToStringOfInterest(Set<Value> candidateBuilders, Instruction instr) {
- if (!instr.isInvokeMethod()) {
- return false;
- }
- InvokeMethod invoke = instr.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (!optimizationConfiguration.isToStringMethod(invokedMethod)) {
- return false;
- }
- assert invoke.arguments().size() == 1;
- Value builder = invoke.getArgument(0).getAliasedValue();
- if (!candidateBuilders.contains(builder)) {
- return false;
- }
- // If the result of toString() is no longer used, computing the compile-time constant is
- // even not necessary.
- if (!invoke.hasOutValue() || invoke.outValue().isDead(appView, code)) {
- return true;
- }
- Map<Instruction, BuilderState> perInstrState = builderStates.get(builder);
- if (perInstrState == null) {
- return false;
- }
- BuilderState builderState = perInstrState.get(instr);
- if (builderState == null) {
- return false;
- }
- String element = toCompileTimeString(builder, builderState);
- if (element == null) {
- return false;
- }
- return true;
- }
-
- // Find the trivial chain of builder-append*-toString.
- // Note that we can't determine a compile-time constant string if there are any ambiguity.
- @VisibleForTesting
- String toCompileTimeString(Value builder, BuilderState state) {
- boolean continuedForLogging = false;
- LinkedList<String> contents = new LinkedList<>();
- while (state != null) {
- // Not a single chain if there are multiple successors.
- if (state.nexts != null && state.nexts.size() > 1) {
- numberOfBuildersWithMergingPoints++;
- if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
- logHistogramOfChains(contents, true);
- continuedForLogging = true;
- contents.clear();
- state = state.previous;
- continue;
- }
- return null;
- }
- // Reaching the root.
- if (state.addition == null) {
- break;
- }
- // A non-deterministic argument is appended.
- // TODO(b/129200243): Even though it's ambiguous, if the chain length is 2, and the 1st one
- // is definitely non-null, we can convert it to String#concat.
- if (state.addition.equals(ANY_STRING)) {
- numberOfBuildersWithNonDeterministicArg++;
- if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
- logHistogramOfChains(contents, true);
- continuedForLogging = true;
- contents.clear();
- state = state.previous;
- continue;
- }
- return null;
- }
- contents.push(state.addition);
- state = state.previous;
- }
- if (continuedForLogging) {
- logHistogramOfChains(contents, true);
- return null;
- }
- if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
- logHistogramOfChains(contents, false);
- }
- if (contents.isEmpty()) {
- return null;
- }
- if (StringBuilderAppendFlowAnalysis.hasAppendInstructionInLoop(
- appView, code, builder, optimizationConfiguration)) {
- return null;
- }
- return StringUtils.join("", contents);
- }
-
- void removeTrivialBuilders() {
- if (deadBuilders.isEmpty() && simplifiedBuilders.isEmpty()) {
- return;
- }
- Set<Value> affectedValues = Sets.newIdentityHashSet();
- Set<Value> buildersToRemove = SetUtils.newIdentityHashSet(deadBuilders, simplifiedBuilders);
- Set<Value> buildersUsedInIf = Sets.newIdentityHashSet();
- // All instructions that refer to dead/simplified builders are dead.
- // Here, we remove toString() calls, append(...) calls, <init>, and new-instance in order.
- InstructionListIterator it = code.instructionListIterator();
- boolean shouldRemoveUnreachableBlocks = false;
- while (it.hasNext()) {
- Instruction instr = it.next();
- if (instr.isIf()) {
- If theIf = instr.asIf();
- Value lhs = theIf.lhs().getAliasedValue();
- if (theIf.isZeroTest()) {
- if (buildersToRemove.contains(lhs)) {
- theIf.targetFromNullObject().unlinkSinglePredecessorSiblingsAllowed();
- it.replaceCurrentInstruction(new Goto());
- shouldRemoveUnreachableBlocks = true;
- }
- } else {
- Value rhs = theIf.rhs().getAliasedValue();
- if (buildersToRemove.contains(lhs)) {
- buildersUsedInIf.add(lhs);
- }
- if (buildersToRemove.contains(rhs)) {
- buildersUsedInIf.add(rhs);
- }
- }
- } else if (instr.isInvokeMethod()) {
- InvokeMethod invoke = instr.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isToStringMethod(invokedMethod)
- && buildersToRemove.contains(invoke.getArgument(0).getAliasedValue())) {
- it.removeOrReplaceByDebugLocalRead();
- }
- }
- }
- if (shouldRemoveUnreachableBlocks) {
- affectedValues.addAll(code.removeUnreachableBlocks());
- }
- buildersToRemove.removeAll(buildersUsedInIf);
- // append(...) and <init> don't have out values, so removing them won't bother each other.
- it = code.instructionListIterator();
- while (it.hasNext()) {
- Instruction instr = it.next();
- if (instr.isInvokeVirtual()) {
- InvokeVirtual invoke = instr.asInvokeVirtual();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isAppendMethod(invokedMethod)
- && buildersToRemove.contains(invoke.getReceiver().getAliasedValue())) {
- it.removeOrReplaceByDebugLocalRead();
- }
- }
- if (instr.isInvokeDirect()) {
- InvokeDirect invoke = instr.asInvokeDirect();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isBuilderInit(invokedMethod)
- && buildersToRemove.contains(invoke.getReceiver().getAliasedValue())) {
- it.removeOrReplaceByDebugLocalRead();
- }
- }
- // If there are aliasing instructions, they should be removed before new-instance.
- if (instr.isAssume() && buildersToRemove.contains(instr.outValue().getAliasedValue())) {
- Assume assumeInstruction = instr.asAssume();
- Value src = assumeInstruction.src();
- Value dest = assumeInstruction.outValue();
- dest.replaceUsers(src);
- it.remove();
- }
- }
- // new-instance should be removed at last, since it will check the out value, builder, is not
- // used anywhere, which we've removed so far.
- it = code.instructionListIterator();
- while (it.hasNext()) {
- Instruction instr = it.next();
- if (instr.isNewInstance()
- && optimizationConfiguration.isBuilderType(instr.asNewInstance().clazz)
- && instr.hasOutValue()
- && buildersToRemove.contains(instr.outValue())) {
- it.removeOrReplaceByDebugLocalRead();
- }
- }
- if (!affectedValues.isEmpty()) {
- new TypeAnalysis(appView).narrowing(affectedValues);
- }
- assert code.isConsistentSSA(appView);
- }
- }
-
- class DefaultStringBuilderOptimizationConfiguration
- implements StringBuilderOptimizationConfiguration {
- @Override
- public boolean isBuilderType(DexType type) {
- return type == factory.stringBuilderType
- || type == factory.stringBufferType;
- }
-
- @Override
- public boolean isBuilderInit(DexMethod method, DexType builderType) {
- return builderType == method.holder
- && factory.isConstructor(method);
- }
-
- @Override
- public boolean isBuilderInit(DexMethod method) {
- return isBuilderType(method.holder)
- && factory.isConstructor(method);
- }
-
- @Override
- public boolean isBuilderInitWithInitialValue(InvokeMethod invoke) {
- return isBuilderInit(invoke.getInvokedMethod())
- && invoke.inValues().size() == 2
- && !invoke.inValues().get(1).getType().isPrimitiveType();
- }
-
- @Override
- public boolean isAppendMethod(DexMethod method) {
- return factory.stringBuilderMethods.isAppendMethod(method)
- || factory.stringBufferMethods.isAppendMethod(method);
- }
-
- @Override
- public boolean isSupportedAppendMethod(InvokeMethod invoke) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- assert isAppendMethod(invokedMethod);
- if (invoke.hasOutValue()) {
- return false;
- }
- // Any methods other than append(arg) are not trivial since they may change the builder
- // state not monotonically.
- if (invoke.inValues().size() > 2) {
- numberOfBuildersWithNonTrivialStateChange++;
- return false;
- }
- assert invoke.inValues().size() == 2;
- TypeElement argType = invoke.inValues().get(1).getType();
- if (!argType.isPrimitiveType() && !argType.isClassType() && !argType.isNullType()) {
- numberOfBuildersWithUnsupportedArg++;
- return false;
- }
- if (argType.isClassType()) {
- DexType argClassType = argType.asClassType().getClassType();
- return canHandleArgumentType(argClassType);
- }
- return true;
- }
-
- @Override
- public boolean isToStringMethod(DexMethod method) {
- return method == factory.objectMembers.toString
- || method == factory.stringBuilderMethods.toString
- || method == factory.stringBufferMethods.toString
- || method == factory.stringMembers.valueOf;
- }
-
- private boolean canHandleArgumentType(DexType argType) {
- // TODO(b/113859361): passed to another builder should be an eligible case.
- return argType == factory.stringType || argType == factory.charSequenceType;
- }
- }
-
- class StringBuilderOptimizerEscapeAnalysisConfiguration implements EscapeAnalysisConfiguration {
- final Value builder;
- final DexType builderType;
-
- private StringBuilderOptimizerEscapeAnalysisConfiguration(Value builder) {
- this.builder = builder;
- assert builder.getType().isClassType();
- builderType = builder.getType().asClassType().getClassType();
- }
-
- private void logEscapingRoute(boolean legitimate) {
- if (!legitimate) {
- numberOfBuildersThatEscape++;
- }
- }
-
- @Override
- public boolean isLegitimateEscapeRoute(
- AppView<?> appView,
- EscapeAnalysis escapeAnalysis,
- Instruction escapeRoute,
- ProgramMethod context) {
- if (escapeRoute.isReturn() || escapeRoute.isThrow() || escapeRoute.isStaticPut()) {
- logEscapingRoute(false);
- return false;
- }
- if (escapeRoute.isInvokeMethod()) {
- // Program class may call String#intern(). Only allow library calls.
- // TODO(b/114002137): For now, we allow only library calls to avoid a case like
- // identity(Builder.toString()).intern(); but it's too restrictive.
- DexClass holderClass =
- appView.definitionFor(escapeRoute.asInvokeMethod().getInvokedMethod().holder);
- if (holderClass != null && !holderClass.isLibraryClass()) {
- logEscapingRoute(false);
- return false;
- }
-
- InvokeMethod invoke = escapeRoute.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
-
- if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
- Value out = escapeRoute.outValue();
- if (out != null) {
- // If Builder#toString or String#valueOf is interned, it could be used for equality
- // check. Replacing builder-based runtime result with a compile time constant may change
- // the program's runtime behavior.
- for (Instruction outUser : out.uniqueUsers()) {
- if (outUser.isInvokeMethodWithReceiver()
- && outUser.asInvokeMethodWithReceiver().getInvokedMethod()
- == factory.stringMembers.intern) {
- numberOfBuildersWhoseResultIsInterned++;
- return false;
- }
- }
- }
- // Otherwise, use of Builder#toString and String#valueOf is legitimate.
- return true;
- }
-
- // Make sure builder's uses are local, i.e., not escaping from the current method.
- if (invokedMethod.holder != builderType) {
- logEscapingRoute(false);
- return false;
- }
-
- // <init> is legitimate.
- if (optimizationConfiguration.isBuilderInit(invokedMethod, builderType)) {
- return true;
- }
- // Even though all invocations belong to the builder type, there are some methods other
- // than append/toString, e.g., setCharAt, setLength, subSequence, etc.
- // Seeing any of them indicates that this code is not trivial.
- if (!optimizationConfiguration.isAppendMethod(invokedMethod)) {
- numberOfBuildersWithNonTrivialStateChange++;
- return false;
- }
- if (!optimizationConfiguration.isSupportedAppendMethod(invoke)) {
- return false;
- }
-
- // Reaching here means that this invocation is part of trivial patterns we're looking for.
- return true;
- }
- if (escapeRoute.isArrayPut()) {
- Value array = escapeRoute.asArrayPut().array().getAliasedValue();
- boolean legitimate = !array.isPhi() && array.definition.isCreatingArray();
- logEscapingRoute(legitimate);
- return legitimate;
- }
- if (escapeRoute.isInstancePut()) {
- Value instance = escapeRoute.asInstancePut().object().getAliasedValue();
- boolean legitimate = !instance.isPhi() && instance.definition.isNewInstance();
- logEscapingRoute(legitimate);
- return legitimate;
- }
- // All other cases are not legitimate.
- logEscapingRoute(false);
- return false;
- }
- }
-
- // A chain of builder's internal state changes.
- static class BuilderState {
- BuilderState previous;
- String addition;
- Set<BuilderState> nexts;
-
- private BuilderState() {
- previous = null;
- addition = null;
- nexts = null;
- }
-
- static BuilderState createRoot() {
- return new BuilderState();
- }
-
- BuilderState createChild(String addition) {
- BuilderState newState = new BuilderState();
- newState.previous = this;
- newState.addition = addition;
- if (this.nexts == null) {
- this.nexts = Sets.newIdentityHashSet();
- }
- this.nexts.add(newState);
- return newState;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
index dd49ae4..d7d7f59 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
@@ -61,7 +61,8 @@
|| isStringBuildingMethod(factory.stringBufferMethods, invokedMethod)) {
return true;
}
- return invokedMethod == factory.objectMembers.toString
+ return (invokedMethod == factory.objectMembers.toString
+ || invokedMethod == factory.objectsMethods.toStringWithObject)
&& isLiveStringBuilder.test(instruction.getFirstOperand());
}
return false;
@@ -87,17 +88,20 @@
@Override
public boolean isToString(Instruction instruction, Value value) {
- if (!instruction.isInvokeVirtual()) {
+ if (!instruction.isInvokeMethod()) {
return false;
}
- InvokeVirtual invoke = instruction.asInvokeVirtual();
- if (invoke.getReceiver() != value) {
+ if (instruction.inValues().isEmpty()) {
return false;
}
- DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (instruction.getFirstOperand() != value) {
+ return false;
+ }
+ DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
return factory.stringBuilderMethods.toString == invokedMethod
|| factory.stringBufferMethods.toString == invokedMethod
- || factory.objectMembers.toString == invokedMethod;
+ || factory.objectMembers.toString == invokedMethod
+ || factory.objectsMethods.toStringWithObject == invokedMethod;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
index 0cb8e39..d512ff4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -34,6 +34,7 @@
factory.createSynthesizedType("Ljava/lang/IllegalAccessError;");
factory.createSynthesizedType("Ljava/lang/IncompatibleClassChangeError;");
factory.createSynthesizedType("Ljava/lang/NoSuchMethodError;");
+ factory.createSynthesizedType("Ljava/lang/RuntimeException;");
}
public static CfCode
@@ -145,6 +146,34 @@
ImmutableList.of());
}
+ public static CfCode
+ CfUtilityMethodsForCodeOptimizationsTemplates_throwRuntimeExceptionWithMessage(
+ InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 3,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfNew(options.itemFactory.createType("Ljava/lang/RuntimeException;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/RuntimeException;"),
+ options.itemFactory.createProto(
+ options.itemFactory.voidType, options.itemFactory.stringType),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfThrow(),
+ label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode CfUtilityMethodsForCodeOptimizationsTemplates_toStringIfNotNull(
InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java
similarity index 80%
rename from src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
rename to src/main/java/com/android/tools/r8/optimize/AccessModifier.java
index ad17739..7661768 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java
@@ -10,11 +10,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.MethodAccessFlags;
@@ -26,7 +27,8 @@
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.shaking.KeepFieldInfo;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Timing;
@@ -36,23 +38,23 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-public final class ClassAndMemberPublicizer {
+public final class AccessModifier {
private final DexApplication application;
private final AppView<AppInfoWithLiveness> appView;
- private final KeepInfoCollection keepInfo;
+ private final InternalOptions options;
private final SubtypingInfo subtypingInfo;
private final MethodPoolCollection methodPoolCollection;
private final PublicizedLensBuilder lensBuilder = PublicizerLens.createBuilder();
- private ClassAndMemberPublicizer(
+ private AccessModifier(
DexApplication application,
AppView<AppInfoWithLiveness> appView,
SubtypingInfo subtypingInfo) {
this.application = application;
this.appView = appView;
- this.keepInfo = appView.appInfo().getKeepInfo();
+ this.options = appView.options();
this.subtypingInfo = subtypingInfo;
this.methodPoolCollection =
// We will add private instance methods when we promote them.
@@ -73,8 +75,7 @@
AppView<AppInfoWithLiveness> appView,
SubtypingInfo subtypingInfo)
throws ExecutionException {
- return new ClassAndMemberPublicizer(application, appView, subtypingInfo)
- .run(executorService, timing);
+ return new AccessModifier(application, appView, subtypingInfo).run(executorService, timing);
}
private GraphLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
@@ -83,8 +84,8 @@
// Phase 2: Visit classes and promote class/member to public if possible.
timing.begin("Phase 2: promoteToPublic");
- appView.appInfo().forEachReachableInterface(clazz -> publicizeType(clazz.getType()));
- publicizeType(appView.dexItemFactory().objectType);
+ appView.appInfo().forEachReachableInterface(clazz -> processType(clazz.getType()));
+ processType(appView.dexItemFactory().objectType);
timing.end();
return lensBuilder.build(appView);
@@ -94,21 +95,21 @@
definition.getAccessFlags().promoteToPublic();
}
- private void publicizeType(DexType type) {
+ private void processType(DexType type) {
DexProgramClass clazz = asProgramClassOrNull(application.definitionFor(type));
if (clazz != null) {
- publicizeClass(clazz);
+ processClass(clazz);
}
- subtypingInfo.forAllImmediateExtendsSubtypes(type, this::publicizeType);
+ subtypingInfo.forAllImmediateExtendsSubtypes(type, this::processType);
}
- private void publicizeClass(DexProgramClass clazz) {
+ private void processClass(DexProgramClass clazz) {
if (appView.appInfo().isAccessModificationAllowed(clazz)) {
doPublicize(clazz);
}
// Publicize fields.
- clazz.forEachProgramField(this::publicizeField);
+ clazz.forEachProgramField(this::processField);
// Publicize methods.
Set<DexEncodedMethod> privateInstanceMethods = new LinkedHashSet<>();
@@ -132,19 +133,43 @@
}
}
+ private void processField(ProgramField field) {
+ finalizeField(field);
+ publicizeField(field);
+ }
+
+ private void finalizeField(ProgramField field) {
+ FieldAccessFlags flags = field.getAccessFlags();
+ FieldAccessInfo accessInfo =
+ appView.appInfo().getFieldAccessInfoCollection().get(field.getReference());
+ KeepFieldInfo keepInfo = appView.getKeepInfo(field);
+ if (keepInfo.isAccessModificationAllowed(options)
+ && !keepInfo.isPinned(options)
+ && !accessInfo.hasReflectiveWrite()
+ && !accessInfo.isWrittenFromMethodHandle()
+ && accessInfo.isWrittenOnlyInMethodSatisfying(
+ method ->
+ method.getDefinition().isInitializer(flags.isStatic())
+ && method.getHolder() == field.getHolder())
+ && !flags.isFinal()
+ && !flags.isVolatile()) {
+ flags.promoteToFinal();
+ }
+ }
+
private void publicizeField(ProgramField field) {
- DexEncodedField definition = field.getDefinition();
- if (definition.isPublic()) {
+ FieldAccessFlags flags = field.getAccessFlags();
+ if (flags.isPublic()) {
return;
}
if (!appView.appInfo().isAccessModificationAllowed(field)) {
// TODO(b/131130038): Also do not publicize package-private and protected fields that
// are kept.
- if (definition.isPrivate()) {
+ if (flags.isPrivate()) {
return;
}
}
- doPublicize(field);
+ flags.promoteToPublic();
}
private boolean publicizeMethod(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index df0f324..f968d56 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -1034,9 +1034,14 @@
if (!appView.appInfo().isSubtype(newParameterType, staticType)) {
return null;
}
- return AccessUtils.isAccessibleInSameContextsAs(newParameterType, staticType, appView)
- ? newParameterType
- : null;
+ if (!AccessUtils.isAccessibleInSameContextsAs(newParameterType, staticType, appView)) {
+ return null;
+ }
+ if (!AndroidApiLevelUtils.isApiSafeForTypeStrengthening(
+ newParameterType, staticType, appView)) {
+ return null;
+ }
+ return newParameterType;
}
private RewrittenPrototypeDescription computePrototypeChangesForMethod(
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
index bc44d01..7d65c1e 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfOpenClosedInterfacesAnalysis.java
@@ -91,8 +91,12 @@
CfAnalysisConfig config = createConfig(method, cfCode);
CfOpenClosedInterfacesAnalysisHelper helper =
new CfOpenClosedInterfacesAnalysisHelper(appView, method, unverifiableCodeDiagnostics);
- if (runLinearScan(method, cfCode, config, helper).isNotPresent()) {
+ StackMapStatus stackMapStatus = runLinearScan(method, cfCode, config, helper);
+ if (stackMapStatus.isNotPresent()) {
runFixpoint(method, cfCode, config, helper);
+ cfCode.setStackMapStatus(stackMapStatus);
+ } else if (stackMapStatus.isValid()) {
+ cfCode.setStackMapStatus(stackMapStatus);
}
return helper.getOpenInterfaces();
}
diff --git a/src/main/java/com/android/tools/r8/relocator/Relocator.java b/src/main/java/com/android/tools/r8/relocator/Relocator.java
index 711610f..99c6471 100644
--- a/src/main/java/com/android/tools/r8/relocator/Relocator.java
+++ b/src/main/java/com/android/tools/r8/relocator/Relocator.java
@@ -93,6 +93,7 @@
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
} finally {
+ inputApp.signalFinishedToProviders(options.reporter);
options.signalFinishedToConsumers();
// Dump timings.
if (options.printTimes) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 6214a5a..1df8107 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3436,6 +3436,20 @@
markMethodAsTargeted(new ProgramMethod(clazz, method), reason);
}
});
+
+ // Disallow minification and optimization of types referenced from unresolvable methods. The
+ // graph lenses created by various optimizations only store mappings for method definitions,
+ // thus no lenses contain mappings for unresolvable methods. This can be problematic if an
+ // unresolvable method refers to a class that no longer exists as a result of an optimization.
+ for (DexType referencedType : symbolicMethod.getReferencedBaseTypes(appView.dexItemFactory())) {
+ if (referencedType.isClassType()) {
+ DexProgramClass clazz = asProgramClassOrNull(definitionFor(referencedType, context));
+ if (clazz != null) {
+ applyMinimumKeepInfoWhenLive(
+ clazz, KeepClassInfo.newEmptyJoiner().disallowMinification().disallowOptimization());
+ }
+ }
+ }
}
private DexMethod generatedEnumValuesMethod(DexClass enumClass) {
@@ -3576,7 +3590,7 @@
.forEachRuleInstance(
appView,
(clazz, minimumKeepInfo) ->
- applyMinimumKeepInfoWhenLive(clazz, preconditionEvent, minimumKeepInfo),
+ applyMinimumKeepInfoWhenLive(clazz, minimumKeepInfo, preconditionEvent),
(field, minimumKeepInfo) ->
applyMinimumKeepInfoWhenLive(field, minimumKeepInfo, preconditionEvent),
this::applyMinimumKeepInfoWhenLiveOrTargeted);
@@ -3646,8 +3660,14 @@
private void applyMinimumKeepInfoWhenLive(
DexProgramClass clazz,
- EnqueuerEvent preconditionEvent,
KeepClassInfo.Joiner minimumKeepInfo) {
+ applyMinimumKeepInfoWhenLive(clazz, minimumKeepInfo, EnqueuerEvent.unconditional());
+ }
+
+ private void applyMinimumKeepInfoWhenLive(
+ DexProgramClass clazz,
+ KeepClassInfo.Joiner minimumKeepInfo,
+ EnqueuerEvent preconditionEvent) {
if (liveTypes.contains(clazz)) {
keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfo));
} else {
@@ -3674,7 +3694,7 @@
DexProgramClass clazz,
KeepClassInfo.Joiner minimumKeepInfo) {
if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
- applyMinimumKeepInfoWhenLive(clazz, preconditionEvent, minimumKeepInfo);
+ applyMinimumKeepInfoWhenLive(clazz, minimumKeepInfo, preconditionEvent);
} else {
dependentMinimumKeepInfo
.getOrCreateMinimumKeepInfoFor(preconditionEvent)
@@ -3803,7 +3823,7 @@
minimumKeepClassInfoDependentOnPrecondition.forEach(
appView,
(clazz, minimumKeepInfoForClass) ->
- applyMinimumKeepInfoWhenLive(clazz, preconditionEvent, minimumKeepInfoForClass),
+ applyMinimumKeepInfoWhenLive(clazz, minimumKeepInfoForClass, preconditionEvent),
(field, minimumKeepInfoForField) ->
applyMinimumKeepInfoWhenLive(field, minimumKeepInfoForField, preconditionEvent),
(method, minimumKeepInfoForMethod) ->
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
index 94cf1b5..02979c5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -300,9 +300,10 @@
builder.addSyntheticInput(syntheticInput);
}
}
- // Global synthetic contexts are only collected for per-file modes which should never
- // prune items.
- assert globalContexts.isEmpty();
+ // Global synthetic contexts are only collected for per-file modes which only prune synthetic
+ // items, not inputs.
+ assert globalContexts.isEmpty()
+ || prunedItems.getNoLongerSyntheticItems().size() == prunedItems.getRemovedClasses().size();
return changed ? builder.build() : this;
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 822fa38..527cec3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -36,6 +36,7 @@
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
@@ -63,6 +64,9 @@
public class SyntheticFinalization {
+ // TODO(b/237413146): Implement a non-quadratic grouping algorithm.
+ private static final int GROUP_COUNT_THRESHOLD = 10;
+
public static class Result {
public final CommittedItems commit;
public final NonIdentityGraphLens lens;
@@ -155,12 +159,13 @@
this.committed = committed;
}
- public static void finalize(AppView<AppInfo> appView, ExecutorService executorService)
+ public static void finalize(
+ AppView<AppInfo> appView, Timing timing, ExecutorService executorService)
throws ExecutionException {
assert !appView.appInfo().hasClassHierarchy();
assert !appView.appInfo().hasLiveness();
appView.options().testing.checkDeterminism(appView);
- Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+ Result result = appView.getSyntheticItems().computeFinalSynthetics(appView, timing);
appView.setAppInfo(new AppInfo(result.commit, result.mainDexInfo));
if (result.lens != null) {
appView.setAppInfo(
@@ -177,11 +182,11 @@
}
public static void finalizeWithClassHierarchy(
- AppView<AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+ AppView<AppInfoWithClassHierarchy> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
assert !appView.appInfo().hasLiveness();
appView.options().testing.checkDeterminism(appView);
- Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+ Result result = appView.getSyntheticItems().computeFinalSynthetics(appView, timing);
appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
if (result.lens != null) {
@@ -199,10 +204,10 @@
}
public static void finalizeWithLiveness(
- AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
+ AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
appView.options().testing.checkDeterminism(appView);
- Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+ Result result = appView.getSyntheticItems().computeFinalSynthetics(appView, timing);
appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
if (result.lens != null) {
appView.rewriteWithLensAndApplication(result.lens, result.commit.getApplication().asDirect());
@@ -213,7 +218,7 @@
appView.pruneItems(result.prunedItems, executorService);
}
- Result computeFinalSynthetics(AppView<?> appView) {
+ Result computeFinalSynthetics(AppView<?> appView, Timing timing) {
assert verifyNoNestedSynthetics(appView.dexItemFactory());
assert verifyOneSyntheticPerSyntheticClass();
DexApplication application;
@@ -227,9 +232,18 @@
Map<String, NumberGenerator> generators = new HashMap<>();
application =
buildLensAndProgram(
+ timing,
appView,
- computeEquivalences(appView, committed.getMethods(), generators, lensBuilder),
- computeEquivalences(appView, committed.getClasses(), generators, lensBuilder),
+ timing.time(
+ "Method equivalence",
+ () ->
+ computeEquivalences(
+ appView, committed.getMethods(), generators, lensBuilder, timing)),
+ timing.time(
+ "Class equivalence",
+ () ->
+ computeEquivalences(
+ appView, committed.getClasses(), generators, lensBuilder, timing)),
lensBuilder,
(clazz, reference) ->
finalClassesBuilder.put(clazz.getType(), ImmutableList.of(reference)),
@@ -289,13 +303,15 @@
AppView<?> appView,
ImmutableMap<DexType, List<R>> references,
Map<String, NumberGenerator> generators,
- Builder lensBuilder) {
+ Builder lensBuilder,
+ Timing timing) {
boolean intermediate = appView.options().intermediate;
Map<DexType, D> definitions = lookupDefinitions(appView, references);
ClassToFeatureSplitMap classToFeatureSplitMap =
appView.appInfo().hasClassHierarchy()
? appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap()
: ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap();
+ timing.begin("Potential equivalences");
Collection<List<D>> potentialEquivalences =
computePotentialEquivalences(
definitions,
@@ -304,13 +320,15 @@
appView.graphLens(),
classToFeatureSplitMap,
synthetics);
+ timing.end();
return computeActualEquivalences(
potentialEquivalences,
generators,
appView,
intermediate,
classToFeatureSplitMap,
- lensBuilder);
+ lensBuilder,
+ timing);
}
private boolean isNotSyntheticType(DexType type) {
@@ -361,6 +379,7 @@
}
private static DexApplication buildLensAndProgram(
+ Timing timing,
AppView<?> appView,
Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
Map<DexType, EquivalenceGroup<SyntheticProgramClassDefinition>> syntheticClassGroups,
@@ -448,10 +467,12 @@
assert verifyNonRepresentativesRemovedFromApplication(application, syntheticClassGroups);
assert verifyNonRepresentativesRemovedFromApplication(application, syntheticMethodGroups);
+ timing.begin("Tree fixing");
DexApplication.Builder<?> builder = application.builder();
treeFixer.fixupClasses(deduplicatedClasses);
builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes()));
application = builder.build();
+ timing.end();
}
DexString syntheticSourceFileName =
@@ -459,6 +480,7 @@
? appView.dexItemFactory().createString("R8$$SyntheticClass")
: appView.dexItemFactory().createString("D8$$SyntheticClass");
+ timing.begin("Add final synthetics");
// Add the synthesized from after repackaging which changed class definitions.
final DexApplication appForLookup = application;
syntheticClassGroups.forEach(
@@ -491,7 +513,9 @@
representative.getContext(),
syntheticMethodDefinition.getReference()));
});
+ timing.end();
+ timing.begin("Finish lens");
Iterables.<EquivalenceGroup<? extends SyntheticDefinition<?, ?, DexProgramClass>>>concat(
syntheticClassGroups.values(), syntheticMethodGroups.values())
.forEach(
@@ -511,6 +535,7 @@
lensBuilder.setRepresentative(rewrittenMethod, method);
}
}));
+ timing.end();
for (DexType key : syntheticMethodGroups.keySet()) {
assert application.definitionFor(key) != null;
@@ -557,9 +582,11 @@
AppView<?> appView,
boolean intermediate,
ClassToFeatureSplitMap classToFeatureSplitMap,
- Builder lensBuilder) {
+ Builder lensBuilder,
+ Timing timing) {
Map<String, List<EquivalenceGroup<T>>> groupsPerPrefix = new HashMap<>();
Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
+ timing.begin("Groups");
potentialEquivalences.forEach(
members -> {
List<EquivalenceGroup<T>> groups =
@@ -581,6 +608,8 @@
}
}
});
+ timing.end();
+ timing.begin("External creation");
groupsPerPrefix.forEach(
(externalSyntheticTypePrefix, groups) -> {
Comparator<EquivalenceGroup<T>> comparator = this::compareForFinalGroupSorting;
@@ -613,6 +642,7 @@
equivalences.put(representativeType, group);
}
});
+ timing.end();
equivalences.forEach(
(representativeType, group) ->
group.forEach(
@@ -639,6 +669,10 @@
List<EquivalenceGroup<T>> groups = new ArrayList<>();
// Each other member is in a shared group if it is actually equivalent to the first member.
for (T synthetic : potentialEquivalence) {
+ if (groups.size() > GROUP_COUNT_THRESHOLD) {
+ return ListUtils.map(
+ potentialEquivalence, m -> new EquivalenceGroup<>(m, isPinned(appView, m)));
+ }
boolean mustBeRepresentative = isPinned(appView, synthetic);
EquivalenceGroup<T> equivalenceGroup = null;
for (EquivalenceGroup<T> group : groups) {
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 08d9d27..044b77f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -43,6 +43,7 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
@@ -936,25 +937,6 @@
return globalSynthetic;
}
- // TODO(b/230445931): Remove this once possible.
- @Deprecated
- public DexProgramClass legacyEnsureGlobalClass(
- Supplier<MissingGlobalSyntheticsConsumerDiagnostic> diagnosticSupplier,
- SyntheticKindSelector kindSelector,
- DexType globalType,
- AppView<?> appView,
- Consumer<SyntheticProgramClassBuilder> fn,
- Consumer<DexProgramClass> onCreationConsumer) {
- SyntheticKind kind = kindSelector.select(naming);
- assert kind.isGlobal();
- if (appView.options().intermediate && !appView.options().hasGlobalSyntheticsConsumer()) {
- appView.reporter().fatalError(diagnosticSupplier.get());
- }
- // A global type is its own context.
- SynthesizingContext outerContext = SynthesizingContext.fromType(globalType);
- return internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
- }
-
/** Create a single synthetic method item. */
public ProgramMethod createMethod(
SyntheticKindSelector kindSelector,
@@ -1086,9 +1068,9 @@
// Finalization of synthetic items.
- Result computeFinalSynthetics(AppView<?> appView) {
+ Result computeFinalSynthetics(AppView<?> appView, Timing timing) {
assert !hasPendingSyntheticClasses();
return new SyntheticFinalization(appView.options(), this, committed)
- .computeFinalSynthetics(appView);
+ .computeFinalSynthetics(appView, timing);
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 29ca8e4..631115f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -75,6 +75,7 @@
public final SyntheticKind THROW_IAE = generator.forSingleMethod("ThrowIAE");
public final SyntheticKind THROW_ICCE = generator.forSingleMethod("ThrowICCE");
public final SyntheticKind THROW_NSME = generator.forSingleMethod("ThrowNSME");
+ public final SyntheticKind THROW_RTE = generator.forSingleMethod("ThrowRTE");
public final SyntheticKind TWR_CLOSE_RESOURCE = generator.forSingleMethod("TwrCloseResource");
public final SyntheticKind SERVICE_LOADER = generator.forSingleMethod("ServiceLoad");
public final SyntheticKind OUTLINE = generator.forSingleMethod("Outline");
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 189d9f9..2c3b88b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -106,7 +106,7 @@
return apiLevelOfOriginal.max(apiLevel).isLessThanOrEqualTo(options.getMinApiLevel()).isTrue();
}
- private static boolean isApiSafeForReference(LibraryDefinition definition, AppView<?> appView) {
+ public static boolean isApiSafeForReference(LibraryDefinition definition, AppView<?> appView) {
return isApiSafeForReference(definition, appView.apiLevelCompute(), appView.options());
}
@@ -114,7 +114,9 @@
LibraryDefinition definition,
AndroidApiLevelCompute androidApiLevelCompute,
InternalOptions options) {
- assert options.apiModelingOptions().enableApiCallerIdentification;
+ if (!options.apiModelingOptions().enableApiCallerIdentification) {
+ return false;
+ }
ComputedApiLevel apiLevel =
androidApiLevelCompute.computeApiLevelForLibraryReference(
definition.getReference(), ComputedApiLevel.unknown());
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 8060f36..fbf344b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -467,9 +467,6 @@
}
public void dump(Path output, DumpOptions options, Reporter reporter, DexItemFactory factory) {
- if (options == null) {
- return;
- }
int nextDexIndex = 0;
OpenOption[] openOptions =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -477,7 +474,10 @@
writeToZipStream(
out, dumpVersionFileName, Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
writeToZipStream(
- out, dumpBuildPropertiesFileName, options.dumpOptions().getBytes(), ZipEntry.DEFLATED);
+ out,
+ dumpBuildPropertiesFileName,
+ options.getBuildPropertiesFileContent().getBytes(),
+ ZipEntry.DEFLATED);
if (options.getDesugaredLibraryJsonSource() != null) {
writeToZipStream(
out,
@@ -793,6 +793,18 @@
}
}
+ public void signalFinishedToProviders(Reporter reporter) throws IOException {
+ for (ProgramResourceProvider provider : programResourceProviders) {
+ provider.finished(reporter);
+ }
+ for (ClassFileResourceProvider provider : classpathResourceProviders) {
+ provider.finished(reporter);
+ }
+ for (ClassFileResourceProvider provider : libraryResourceProviders) {
+ provider.finished(reporter);
+ }
+ }
+
/**
* Builder interface for constructing an AndroidApp.
*/
diff --git a/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java b/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java
index d37de80..368974b 100644
--- a/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java
+++ b/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java
@@ -3,53 +3,101 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.dump.DumpOptions;
+import com.android.tools.r8.errors.Unreachable;
import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
public abstract class DumpInputFlags {
+ private static final String DUMP_INPUT_TO_FILE_PROPERTY = "com.android.tools.r8.dumpinputtofile";
+ private static final String DUMP_INPUT_TO_DIRECTORY_PROPERTY =
+ "com.android.tools.r8.dumpinputtodirectory";
+
+ public static DumpInputFlags getDefault() {
+ String dumpInputToFile = System.getProperty(DUMP_INPUT_TO_FILE_PROPERTY);
+ if (dumpInputToFile != null) {
+ return dumpToFile(Paths.get(dumpInputToFile));
+ }
+ String dumpInputToDirectory = System.getProperty(DUMP_INPUT_TO_DIRECTORY_PROPERTY);
+ if (dumpInputToDirectory != null) {
+ return dumpToDirectory(Paths.get(dumpInputToDirectory));
+ }
+ return noDump();
+ }
+
public static DumpInputFlags noDump() {
return new DumpInputFlags() {
+
@Override
- Path getDumpInputToFile() {
- return null;
+ public Path getDumpPath() {
+ throw new Unreachable();
}
@Override
- Path getDumpInputToDirectory() {
- return null;
+ public boolean shouldDump(DumpOptions options) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldFailCompilation() {
+ throw new Unreachable();
}
};
}
public static DumpInputFlags dumpToFile(Path file) {
- return new DumpInputFlags() {
+ return new DumpInputToFileOrDirectoryFlags() {
+
@Override
- Path getDumpInputToFile() {
+ public Path getDumpPath() {
return file;
}
@Override
- Path getDumpInputToDirectory() {
- return null;
+ public boolean shouldFailCompilation() {
+ return true;
}
};
}
- public static DumpInputFlags dumpToDirectory(Path file) {
- return new DumpInputFlags() {
+ public static DumpInputFlags dumpToDirectory(Path directory) {
+ return new DumpInputToFileOrDirectoryFlags() {
+
@Override
- Path getDumpInputToFile() {
- return null;
+ public Path getDumpPath() {
+ return directory.resolve("dump" + System.nanoTime() + ".zip");
}
@Override
- Path getDumpInputToDirectory() {
- return file;
+ public boolean shouldFailCompilation() {
+ return false;
}
};
}
- abstract Path getDumpInputToFile();
+ public abstract Path getDumpPath();
- abstract Path getDumpInputToDirectory();
+ public abstract boolean shouldDump(DumpOptions options);
+
+ public abstract boolean shouldFailCompilation();
+
+ abstract static class DumpInputToFileOrDirectoryFlags extends DumpInputFlags {
+
+ @Override
+ public boolean shouldDump(DumpOptions options) {
+ Map<String, String> buildProperties = options.getBuildProperties();
+ for (Entry<String, String> entry : buildProperties.entrySet()) {
+ String valueRegExp =
+ System.getProperty("com.android.tools.r8.dump.filter.buildproperty." + entry.getKey());
+ if (valueRegExp != null && !Pattern.matches(valueRegExp, entry.getValue())) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
index e5557d9..5c2eec5 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.FileUtils.isArchive;
import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.errors.CompilationError;
@@ -113,9 +114,16 @@
}
@Override
+ public void finished(DiagnosticsHandler handler) throws IOException {
+ close();
+ }
+
+ @Override
public void close() throws IOException {
- openedZipFile.close();
- openedZipFile = null;
+ if (openedZipFile != null) {
+ openedZipFile.close();
+ openedZipFile = null;
+ }
}
private ZipEntry getZipEntryFromDescriptor(String descriptor) throws IOException {
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 a1e7b70..1428459 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
@@ -209,14 +209,12 @@
// 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) == (globalType == appView.dexItemFactory().recordTagType);
- if (contexts != null) {
- assert !contexts.isEmpty();
- for (DexType contextType : contexts) {
- contextToGlobals
- .computeIfAbsent(contextType, k -> SetUtils.newIdentityHashSet())
- .add(globalType);
- }
+ assert contexts != null;
+ assert !contexts.isEmpty();
+ for (DexType contextType : contexts) {
+ contextToGlobals
+ .computeIfAbsent(contextType, k -> SetUtils.newIdentityHashSet())
+ .add(globalType);
}
}
contextToGlobals.forEach(
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d338a11..7d6d982 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -278,10 +278,6 @@
// To print memory one also have to enable printtimes.
public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
- public String dumpInputToFile = System.getProperty("com.android.tools.r8.dumpinputtofile");
- public String dumpInputToDirectory =
- System.getProperty("com.android.tools.r8.dumpinputtodirectory");
-
// Flag to toggle if DEX code objects should pass-through without IR processing.
public boolean passthroughDexCode = false;
@@ -414,6 +410,8 @@
public boolean emitPermittedSubclassesAnnotationsInDex =
System.getProperty("com.android.tools.r8.emitPermittedSubclassesAnnotationsInDex") != null;
+ private DumpInputFlags dumpInputFlags = DumpInputFlags.getDefault();
+
// Contain the contents of the build properties file from the compiler command.
public DumpOptions dumpOptions;
@@ -462,19 +460,8 @@
return marker;
}
- public void setDumpInputFlags(DumpInputFlags dumpInputFlags, boolean skipDump) {
- if (skipDump) {
- dumpInputToDirectory = null;
- dumpInputToFile = null;
- return;
- }
-
- if (dumpInputFlags.getDumpInputToFile() != null) {
- dumpInputToFile = dumpInputFlags.getDumpInputToFile().toString();
- }
- if (dumpInputFlags.getDumpInputToDirectory() != null) {
- dumpInputToDirectory = dumpInputFlags.getDumpInputToDirectory().toString();
- }
+ public void setDumpInputFlags(DumpInputFlags dumpInputFlags) {
+ this.dumpInputFlags = dumpInputFlags;
}
public boolean hasConsumer() {
@@ -522,10 +509,8 @@
}
public boolean shouldKeepStackMapTable() {
- assert isCfDesugaring() || isRelocatorCompilation() || getProguardConfiguration() != null;
- return isCfDesugaring()
- || isRelocatorCompilation()
- || getProguardConfiguration().getKeepAttributes().stackMapTable;
+ assert isRelocatorCompilation() || getProguardConfiguration() != null;
+ return isRelocatorCompilation() || getProguardConfiguration().getKeepAttributes().stackMapTable;
}
public boolean shouldRerunEnqueuer() {
@@ -872,6 +857,10 @@
return cfCodeAnalysisOptions;
}
+ public DumpInputFlags getDumpInputFlags() {
+ return dumpInputFlags;
+ }
+
public OpenClosedInterfacesOptions getOpenClosedInterfacesOptions() {
return openClosedInterfacesOptions;
}
@@ -1953,10 +1942,6 @@
public boolean disableShortenLiveRanges = false;
- // Force each call of application read to dump its inputs to a file, which is subsequently
- // deleted. Useful to check that our dump functionality does not cause compilation failure.
- public boolean dumpAll = false;
-
// Option for testing outlining with interface array arguments, see b/132420510.
public boolean allowOutlinerInterfaceArrayArguments = false;
@@ -2083,28 +2068,76 @@
return CfVersion.V1_5;
}
- public boolean canUseInvokePolymorphicOnVarHandle() {
- return hasFeaturePresentFrom(AndroidApiLevel.P);
+ public static AndroidApiLevel invokePolymorphicOnMethodHandleApiLevel() {
+ return AndroidApiLevel.O;
}
- public boolean canUseInvokePolymorphic() {
- return hasFeaturePresentFrom(AndroidApiLevel.O);
+ public boolean canUseInvokePolymorphicOnMethodHandle() {
+ return hasFeaturePresentFrom(invokePolymorphicOnMethodHandleApiLevel());
+ }
+
+ public static AndroidApiLevel invokePolymorphicOnVarHandleApiLevel() {
+ return AndroidApiLevel.P;
+ }
+
+ public boolean canUseInvokePolymorphicOnVarHandle() {
+ return hasFeaturePresentFrom(invokePolymorphicOnMethodHandleApiLevel());
+ }
+
+ public static AndroidApiLevel constantMethodHandleApiLevel() {
+ return AndroidApiLevel.P;
}
public boolean canUseConstantMethodHandle() {
- return hasFeaturePresentFrom(AndroidApiLevel.P);
+ return hasFeaturePresentFrom(constantMethodHandleApiLevel());
+ }
+
+ public static AndroidApiLevel constantMethodTypeApiLevel() {
+ return AndroidApiLevel.P;
}
public boolean canUseConstantMethodType() {
- return hasFeaturePresentFrom(AndroidApiLevel.P);
+ return hasFeaturePresentFrom(constantMethodTypeApiLevel());
+ }
+
+ public static AndroidApiLevel invokeCustomApiLevel() {
+ return AndroidApiLevel.O;
}
public boolean canUseInvokeCustom() {
- return hasFeaturePresentFrom(AndroidApiLevel.O);
+ return hasFeaturePresentFrom(invokeCustomApiLevel());
+ }
+
+ public static AndroidApiLevel constantDynamicApiLevel() {
+ return null;
+ }
+
+ public boolean canUseConstantDynamic() {
+ return hasFeaturePresentFrom(constantDynamicApiLevel());
+ }
+
+ public static AndroidApiLevel defaultAndStaticInterfaceMethodsApiLevel() {
+ return AndroidApiLevel.N;
+ }
+
+ public static AndroidApiLevel defaultInterfaceMethodsApiLevel() {
+ return defaultAndStaticInterfaceMethodsApiLevel();
+ }
+
+ public static AndroidApiLevel staticInterfaceMethodsApiLevel() {
+ return defaultAndStaticInterfaceMethodsApiLevel();
}
public boolean canUseDefaultAndStaticInterfaceMethods() {
- return hasFeaturePresentFrom(AndroidApiLevel.N);
+ return hasFeaturePresentFrom(defaultInterfaceMethodsApiLevel());
+ }
+
+ public static AndroidApiLevel privateInterfaceMethodsApiLevel() {
+ return AndroidApiLevel.N;
+ }
+
+ public boolean canUsePrivateInterfaceMethods() {
+ return hasFeaturePresentFrom(privateInterfaceMethodsApiLevel());
}
public boolean canUseNestBasedAccess() {
@@ -2150,10 +2183,6 @@
throw new Unreachable();
}
- public boolean canUsePrivateInterfaceMethods() {
- return hasFeaturePresentFrom(AndroidApiLevel.N);
- }
-
// Debug entries may be dropped only if the source file content allows being omitted from
// stack traces, or if the VM will report the source file even with a null valued debug info.
public boolean allowDiscardingResidualDebugInfo() {
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 66e5fc8..dbfd4c6 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -106,10 +106,10 @@
command.add(outJar.toString());
command.add("-printmapping");
command.add(mapFile.toString());
- if (!enableTreeShaking) {
+ if (enableTreeShaking.isFalse()) {
command.add("-dontshrink");
}
- if (!enableMinification) {
+ if (enableMinification.isFalse()) {
command.add("-dontobfuscate");
}
ProcessBuilder pbuilder = new ProcessBuilder(command);
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 10c5358..f3c3dc5 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -498,37 +498,23 @@
static {
ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
builder
- .put(
- DexVm.Version.V13_0_0,
- ImmutableList.of("454-get-vreg", "457-regs", "543-env-long-ref", "518-null-array-get"))
- .put(
- DexVm.Version.V12_0_0,
- ImmutableList.of("454-get-vreg", "457-regs", "543-env-long-ref", "518-null-array-get"))
+ .put(DexVm.Version.V13_0_0, ImmutableList.of("543-env-long-ref", "518-null-array-get"))
+ .put(DexVm.Version.V12_0_0, ImmutableList.of("543-env-long-ref", "518-null-array-get"))
.put(
DexVm.Version.V10_0_0,
ImmutableList.of(
// TODO(b/144975341): Triage, Verif error.
"518-null-array-get",
// TODO(b/144975341): Triage, Linking error.
- "457-regs",
- "543-env-long-ref",
- "454-get-vreg"))
+ "543-env-long-ref"))
.put(
DexVm.Version.V9_0_0,
ImmutableList.of(
- // TODO(120400625): Triage.
- "454-get-vreg",
- // TODO(120402198): Triage.
- "457-regs",
// TODO(120401674): Triage.
"543-env-long-ref",
// TODO(120261858) Triage.
"518-null-array-get"))
- .put(
- DexVm.Version.V8_1_0,
- ImmutableList.of(
- // TODO(119938529): Triage.
- "709-checker-varhandles", "454-get-vreg", "457-regs"))
+ .put(DexVm.Version.V8_1_0, ImmutableList.of())
.put(
DexVm.Version.V7_0_0,
ImmutableList.of(
@@ -872,7 +858,12 @@
DexVm.Version.V4_4_4,
DexVm.Version.V5_1_1,
DexVm.Version.V6_0_1,
- DexVm.Version.V7_0_0)))
+ DexVm.Version.V7_0_0,
+ DexVm.Version.V8_1_0,
+ DexVm.Version.V9_0_0,
+ DexVm.Version.V10_0_0,
+ DexVm.Version.V12_0_0,
+ DexVm.Version.V13_0_0)))
.put("454-get-vreg", TestCondition.match(TestCondition.R8DEX_COMPILER))
// Fails: regs_jni.cc:42] Check failed: GetVReg(m, 0, kIntVReg, &value)
// The R8/D8 code does not put values in the same registers as the tests expects.
@@ -884,7 +875,12 @@
DexVm.Version.V4_4_4,
DexVm.Version.V5_1_1,
DexVm.Version.V6_0_1,
- DexVm.Version.V7_0_0)))
+ DexVm.Version.V7_0_0,
+ DexVm.Version.V8_1_0,
+ DexVm.Version.V9_0_0,
+ DexVm.Version.V10_0_0,
+ DexVm.Version.V12_0_0,
+ DexVm.Version.V13_0_0)))
.put("457-regs", TestCondition.match(TestCondition.R8DEX_COMPILER))
// Class not found.
.put("529-checker-unresolved", TestCondition.any())
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
index 7c3a605..cd5e1ca 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
@@ -56,16 +56,4 @@
R8TestRunner test(String testName, String packageName, String mainClass) {
return new R8TestRunner(testName, packageName, mainClass);
}
-
- @Test
- public void varHandle() throws Throwable {
- test("varhandle", "varhandle", "VarHandleTests")
- .withBuilderTransformation(
- builder ->
- builder.addProguardConfiguration(
- ImmutableList.of("-dontwarn java.lang.invoke.VarHandle"), Origin.unknown()))
- .withMinApiLevel(AndroidApiLevel.P.getLevel())
- .withKeepAll()
- .run();
- }
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ebe8352..3bdfa4b 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -82,8 +82,12 @@
builder.addProguardConfiguration(keepRules, Origin.unknown());
}
builder.addMainDexRulesFiles(mainDexRulesFiles);
- builder.setDisableTreeShaking(!enableTreeShaking);
- builder.setDisableMinification(!enableMinification);
+ if (enableTreeShaking.isFalse()) {
+ builder.setDisableTreeShaking(true);
+ }
+ if (enableMinification.isFalse()) {
+ builder.setDisableMinification(true);
+ }
StringBuilder proguardMapBuilder = new StringBuilder();
if (createDefaultProguardMapConsumer) {
builder.setProguardMapConsumer(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index ed69ecf..caed567 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -228,14 +228,6 @@
.run();
}
- @Test
- public void invokeCustomErrorDueToMinSdk() throws Throwable {
- test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
- .withKeepAll()
- .run();
- }
-
abstract RunExamplesAndroidPTest<B>.TestRunner<?> test(String testName, String packageName,
String mainClass);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index a20964a..485ac54 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -120,42 +120,17 @@
abstract void build(Path inputFile, Path out) throws Throwable;
}
- private static List<String> minSdkErrorExpected =
- ImmutableList.of("varhandle-error-due-to-min-sdk");
+ private static List<String> minSdkErrorExpected = ImmutableList.of();
private static Map<DexVm.Version, List<String>> failsOn;
static {
ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
builder
- .put(DexVm.Version.V4_0_4, ImmutableList.of(
- "native-private-interface-methods", // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V4_4_4, ImmutableList.of(
- "native-private-interface-methods", // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V5_1_1, ImmutableList.of(
- "native-private-interface-methods", // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V6_0_1, ImmutableList.of(
- "native-private-interface-methods", // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V7_0_0, ImmutableList.of(
- // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.V8_1_0, ImmutableList.of(
- // Dex version not supported
- "varhandle"
- ))
- .put(DexVm.Version.DEFAULT, ImmutableList.of(
- // TODO(b/72536415): Update runtime when the support will be ready
- "varhandle"
- ));
+ .put(DexVm.Version.V4_0_4, ImmutableList.of("native-private-interface-methods"))
+ .put(DexVm.Version.V4_4_4, ImmutableList.of("native-private-interface-methods"))
+ .put(DexVm.Version.V5_1_1, ImmutableList.of("native-private-interface-methods"))
+ .put(DexVm.Version.V6_0_1, ImmutableList.of("native-private-interface-methods"));
failsOn = builder.build();
}
@@ -168,20 +143,9 @@
+ "1\n2\n3\n4\n5\n6\n7\n8\n99\n"
+ "i1\ni2\ni3\ni4\ni5\ni6\ni7\ni8\ni99\n",
"native-private-interface-methods",
- "0: s>i>a\n"
- + "1: d>i>s>i>a\n"
- + "2: l>i>s>i>a\n"
- + "3: x>s\n"
- + "4: c>d>i>s>i>a\n",
+ "0: s>i>a\n" + "1: d>i>s>i>a\n" + "2: l>i>s>i>a\n" + "3: x>s\n" + "4: c>d>i>s>i>a\n",
"desugared-private-interface-methods",
- "0: s>i>a\n"
- + "1: d>i>s>i>a\n"
- + "2: l>i>s>i>a\n"
- + "3: x>s\n"
- + "4: c>d>i>s>i>a\n",
- "varhandle",
- "true\nfalse\n"
- );
+ "0: s>i>a\n" + "1: d>i>s>i>a\n" + "2: l>i>s>i>a\n" + "3: x>s\n" + "4: c>d>i>s>i>a\n");
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -241,22 +205,6 @@
}
@Test
- public void varHandle() throws Throwable {
- test("varhandle", "varhandle", "VarHandleTests")
- .withMinApiLevel(AndroidApiLevel.P.getLevel())
- .withKeepAll()
- .run();
- }
-
- @Test
- public void varHandleErrorDueToMinSdk() throws Throwable {
- test("varhandle-error-due-to-min-sdk", "varhandle", "VarHandleTests")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
- .withKeepAll()
- .run();
- }
-
- @Test
public void testTwrCloseResourceMethod() throws Throwable {
TestRunner<?> test = test("twr-close-resource", "twrcloseresource", "TwrCloseResourceTest");
test
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4383be5..98f1627 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1818,6 +1818,10 @@
return AndroidApiLevel.O;
}
+ public static AndroidApiLevel apiLevelWithInvokePolymorphicSupport() {
+ return AndroidApiLevel.O;
+ }
+
public static AndroidApiLevel apiLevelWithConstMethodHandleSupport() {
return AndroidApiLevel.P;
}
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 52d84fc..2b29085 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -250,6 +251,12 @@
return self();
}
+ public T mapUnsupportedFeaturesToWarnings() {
+ return setDiagnosticsLevelModifier(
+ (level, diagnostic) ->
+ diagnostic instanceof UnsupportedFeatureDiagnostic ? DiagnosticsLevel.WARNING : level);
+ }
+
public T allowStdoutMessages() {
// Default ignored.
return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 8b4c482..5d9d7f5 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -75,6 +75,8 @@
private ProgramConsumer programConsumer;
private MainDexClassesCollector mainDexClassesCollector;
private StringConsumer mainDexListConsumer;
+ // TODO(b/186010707): This could become implicit once min always be set when fixed.
+ private boolean noMinApiLevel = false;
private int minApiLevel = -1;
private boolean optimizeMultidexForLinearAlloc = false;
private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
@@ -219,7 +221,7 @@
.addIfNotNull(mainDexListConsumer)
.build());
}
- if (backend.isDex() || !isTestShrinkerBuilder()) {
+ if (!noMinApiLevel && (backend.isDex() || !isTestShrinkerBuilder())) {
assert !builder.isMinApiLevelSet()
: "Don't set the API level directly through BaseCompilerCommand.Builder in tests";
// TODO(b/186010707): This will always be set when fixed.
@@ -364,6 +366,12 @@
return self();
}
+ public T setNoMinApi() {
+ this.minApiLevel = -1;
+ this.noMinApiLevel = true;
+ return self();
+ }
+
/** @deprecated use {@link #setMinApi(AndroidApiLevel)} instead. */
@Deprecated
public T setMinApi(TestRuntime runtime) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index bf47b6e..9072aa3 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Sets;
import java.io.IOException;
@@ -29,8 +30,8 @@
T extends TestShrinkerBuilder<C, B, CR, RR, T>>
extends TestCompilerBuilder<C, B, CR, RR, T> {
- protected boolean enableTreeShaking = true;
- protected boolean enableMinification = true;
+ protected OptionalBool enableTreeShaking = OptionalBool.UNKNOWN;
+ protected OptionalBool enableMinification = OptionalBool.UNKNOWN;
private final Set<Class<? extends Annotation>> addedTestingAnnotations =
Sets.newIdentityHashSet();
@@ -67,7 +68,7 @@
}
public T treeShaking(boolean enable) {
- enableTreeShaking = enable;
+ enableTreeShaking = OptionalBool.of(enable);
return self();
}
@@ -76,7 +77,7 @@
}
public T minification(boolean enable) {
- enableMinification = enable;
+ enableMinification = OptionalBool.of(enable);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java
new file mode 100644
index 0000000..b1c5fc8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2022, 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EffectiveFinalFieldMarkedFinalTest extends TestBase {
+
+ @Parameter(0)
+ public boolean allowAccessModification;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, allowaccessmodification: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .allowAccessModification(allowAccessModification)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+ assertThat(
+ mainClassSubject.uniqueFieldWithName("instanceField"),
+ allOf(isPresent(), onlyIf(allowAccessModification, isFinal())));
+ assertThat(
+ mainClassSubject.uniqueFieldWithName("staticField"),
+ allOf(isPresent(), onlyIf(allowAccessModification, isFinal())));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ static String staticField = System.currentTimeMillis() > 0 ? "Hello" : null;
+
+ String instanceField = System.currentTimeMillis() > 0 ? ", world!" : null;
+
+ public static void main(String[] args) {
+ System.out.print(staticField);
+ System.out.println(new Main().instanceField);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
deleted file mode 100644
index f400169..0000000
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
+++ /dev/null
@@ -1,202 +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.cf;
-
-import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMap.Builder;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Handle;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-// The method MethodHandleDump.transform() translates methods in MethodHandleTest that look like
-// MethodType.methodType(TYPES)
-// and
-// MethodHandles.lookup().findKIND(FOO.class, "METHOD", TYPE)
-// into LDC instructions.
-// This is necessary since there is no Java syntax that compiles to
-// LDC of a constant method handle or constant method type.
-//
-// The method dumpD() dumps a class equivalent to MethodHandleTest.D
-// that uses an LDC instruction instead of MethodHandles.lookup().findSpecial().
-// The LDC instruction loads an InvokeSpecial constant method handle to a C method,
-// so this LDC instruction must be in a subclass of C, and not directly on MethodHandleTest.
-public class MethodHandleDump implements Opcodes {
-
- private static final String cDesc = "com/android/tools/r8/cf/MethodHandleTest$C";
- private static final String eDesc = "com/android/tools/r8/cf/MethodHandleTest$E";
- private static final String fDesc = "com/android/tools/r8/cf/MethodHandleTest$F";
- private static final String iDesc = "com/android/tools/r8/cf/MethodHandleTest$I";
- private static final Type viType = Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE);
- private static final Type jiType = Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE);
- private static final Type vicType =
- Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
- private static final Type jicType =
- Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
- private static final Type veType = Type.getMethodType(Type.VOID_TYPE, Type.getObjectType(eDesc));
- private static final Type fType = Type.getMethodType(Type.getObjectType(fDesc));
- private static final String viDesc = viType.getDescriptor();
- private static final String jiDesc = jiType.getDescriptor();
- private static final String vicDesc = vicType.getDescriptor();
- private static final String jicDesc = jicType.getDescriptor();
- private static final String intDesc = Type.INT_TYPE.getDescriptor();
-
- public static byte[] transform(byte[] input) throws Exception {
- ImmutableMap.Builder<String, Type> typesBuilder = ImmutableMap.builder();
- ImmutableMap<String, Type> types =
- typesBuilder
- .put("viType", viType)
- .put("jiType", jiType)
- .put("vicType", vicType)
- .put("jicType", jicType)
- .put("veType", veType)
- .put("fType", fType)
- .build();
-
- Builder<String, Handle> methodsBuilder = ImmutableMap.builder();
- methodsBuilder
- .put("scviMethod", new Handle(H_INVOKESTATIC, cDesc, "svi", viDesc, false))
- .put("scjiMethod", new Handle(H_INVOKESTATIC, cDesc, "sji", jiDesc, false))
- .put("scvicMethod", new Handle(H_INVOKESTATIC, cDesc, "svic", vicDesc, false))
- .put("scjicMethod", new Handle(H_INVOKESTATIC, cDesc, "sjic", jicDesc, false))
- .put("vcviMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vvi", viDesc, false))
- .put("vcjiMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vji", jiDesc, false))
- .put("vcvicMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vvic", vicDesc, false))
- .put("vcjicMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vjic", jicDesc, false))
- .put("siviMethod", new Handle(H_INVOKESTATIC, iDesc, "svi", viDesc, true))
- .put("sijiMethod", new Handle(H_INVOKESTATIC, iDesc, "sji", jiDesc, true))
- .put("sivicMethod", new Handle(H_INVOKESTATIC, iDesc, "svic", vicDesc, true))
- .put("sijicMethod", new Handle(H_INVOKESTATIC, iDesc, "sjic", jicDesc, true))
- .put("diviMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dvi", viDesc, true))
- .put("dijiMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dji", jiDesc, true))
- .put("divicMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dvic", vicDesc, true))
- .put("dijicMethod", new Handle(H_INVOKEINTERFACE, iDesc, "djic", jicDesc, true))
- .put("vciSetField", new Handle(H_PUTFIELD, cDesc, "vi", intDesc, false))
- .put("sciSetField", new Handle(H_PUTSTATIC, cDesc, "si", intDesc, false))
- .put("vciGetField", new Handle(H_GETFIELD, cDesc, "vi", intDesc, false))
- .put("sciGetField", new Handle(H_GETSTATIC, cDesc, "si", intDesc, false))
- .put("iiSetField", new Handle(H_PUTSTATIC, iDesc, "ii", intDesc, true))
- .put("iiGetField", new Handle(H_GETSTATIC, iDesc, "ii", intDesc, true))
- .put("constructorMethod", new Handle(H_NEWINVOKESPECIAL, cDesc, "<init>", viDesc, false));
- ImmutableMap<String, Handle> methods = methodsBuilder.build();
- ClassReader cr = new ClassReader(input);
- ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
- cr.accept(
- new ClassVisitor(InternalOptions.ASM_VERSION, cw) {
-
- @Override
- public MethodVisitor visitMethod(
- int access, String name, String desc, String signature, String[] exceptions) {
- switch (desc) {
- case "()Ljava/lang/invoke/MethodType;":
- {
- Type type = types.get(name);
- assert type != null : name;
- assert access == ACC_PUBLIC + ACC_STATIC;
- assert signature == null;
- assert exceptions == null;
- MethodVisitor mv = cw.visitMethod(access, name, desc, null, null);
- mv.visitCode();
- mv.visitLdcInsn(type);
- mv.visitInsn(ARETURN);
- mv.visitMaxs(-1, -1);
- mv.visitEnd();
- return null;
- }
- case "()Ljava/lang/invoke/MethodHandle;":
- {
- Handle method = methods.get(name);
- assert access == ACC_PUBLIC + ACC_STATIC;
- assert method != null : name;
- assert signature == null;
- assert exceptions == null;
- MethodVisitor mv = cw.visitMethod(access, name, desc, null, null);
- mv.visitCode();
- mv.visitLdcInsn(method);
- mv.visitInsn(ARETURN);
- mv.visitMaxs(-1, -1);
- mv.visitEnd();
- return null;
- }
- default:
- return super.visitMethod(access, name, desc, signature, exceptions);
- }
- }
- },
- 0);
- return cw.toByteArray();
- }
-
- public static byte[] dumpD() throws Exception {
-
- ClassWriter cw = new ClassWriter(0);
- MethodVisitor mv;
-
- cw.visit(
- V1_8,
- ACC_PUBLIC + ACC_SUPER,
- "com/android/tools/r8/cf/MethodHandleTest$D",
- null,
- "com/android/tools/r8/cf/MethodHandleTest$C",
- null);
-
- cw.visitInnerClass(
- "com/android/tools/r8/cf/MethodHandleTest$D",
- "com/android/tools/r8/cf/MethodHandleTest",
- "D",
- ACC_PUBLIC + ACC_STATIC);
-
- cw.visitInnerClass(
- "com/android/tools/r8/cf/MethodHandleTest$C",
- "com/android/tools/r8/cf/MethodHandleTest",
- "C",
- ACC_PUBLIC + ACC_STATIC);
-
- cw.visitInnerClass(
- "java/lang/invoke/MethodHandles$Lookup",
- "java/lang/invoke/MethodHandles",
- "Lookup",
- ACC_PUBLIC + ACC_FINAL + ACC_STATIC);
-
- {
- mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(
- INVOKESPECIAL, "com/android/tools/r8/cf/MethodHandleTest$C", "<init>", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv =
- cw.visitMethod(
- ACC_PUBLIC + ACC_STATIC,
- "vcviSpecialMethod",
- "()Ljava/lang/invoke/MethodHandle;",
- null,
- null);
- mv.visitCode();
- mv.visitLdcInsn(new Handle(H_INVOKESPECIAL, cDesc, "vvi", viDesc, false));
- mv.visitInsn(ARETURN);
- mv.visitMaxs(-1, -1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "vvi", "(I)V", null, null);
- mv.visitCode();
- mv.visitInsn(RETURN);
- mv.visitMaxs(0, 2);
- mv.visitEnd();
- }
- cw.visitEnd();
-
- return cw.toByteArray();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
deleted file mode 100644
index cc9b61a..0000000
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ /dev/null
@@ -1,209 +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.cf;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.NoVerticalClassMerging;
-import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.R8Command.Builder;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.cf.MethodHandleTest.C;
-import com.android.tools.r8.cf.MethodHandleTest.I;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-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 MethodHandleTestRunner extends TestBase {
- static final Class<?> CLASS = MethodHandleTest.class;
-
- enum LookupType {
- DYNAMIC,
- CONSTANT,
- }
-
- enum MinifyMode {
- NONE,
- MINIFY,
- }
-
- private CompilationMode compilationMode;
- private LookupType lookupType;
- private ProcessResult runInput;
- private MinifyMode minifyMode;
-
- @Parameters(name = "{0}_{1}_{2}")
- public static List<String[]> data() {
- List<String[]> res = new ArrayList<>();
- for (LookupType lookupType : LookupType.values()) {
- for (MinifyMode minifyMode : MinifyMode.values()) {
- if (lookupType == LookupType.DYNAMIC && minifyMode == MinifyMode.MINIFY) {
- // Skip because we don't keep the members looked up dynamically.
- continue;
- }
- for (CompilationMode compilationMode : CompilationMode.values()) {
- res.add(new String[] {lookupType.name(), minifyMode.name(), compilationMode.name()});
- }
- }
- }
- return res;
- }
-
- public MethodHandleTestRunner(String lookupType, String minifyMode, String compilationMode) {
- this.lookupType = LookupType.valueOf(lookupType);
- this.minifyMode = MinifyMode.valueOf(minifyMode);
- this.compilationMode = CompilationMode.valueOf(compilationMode);
- }
-
- @Test
- public void test() throws Exception {
- runInput();
- runCf();
- // TODO(mathiasr): Once we include a P runtime, change this to "P and above".
- if (ToolHelper.getDexVm() == DexVm.ART_DEFAULT && ToolHelper.artSupported()) {
- runDex();
- }
- }
-
- private final Class<?>[] inputClasses = {
- MethodHandleTest.class,
- MethodHandleTest.C.class,
- MethodHandleTest.I.class,
- MethodHandleTest.Impl.class,
- MethodHandleTest.D.class,
- MethodHandleTest.E.class,
- MethodHandleTest.F.class,
- NoVerticalClassMerging.class
- };
-
- private void runInput() throws Exception {
- Path out = temp.getRoot().toPath().resolve("input.jar");
- ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
- for (Class<?> c : inputClasses) {
- archiveConsumer.accept(
- ByteDataView.of(getClassAsBytes(c)),
- DescriptorUtils.javaTypeToDescriptor(c.getName()),
- null);
- }
- archiveConsumer.finished(null);
- String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
- runInput = ToolHelper.runJava(out, CLASS.getName(), expected);
- if (runInput.exitCode != 0) {
- System.out.println(runInput);
- }
- assertEquals(0, runInput.exitCode);
- }
-
- private void runCf() throws Exception {
- Path outCf = temp.getRoot().toPath().resolve("cf.jar");
- build(new ClassFileConsumer.ArchiveConsumer(outCf));
- String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
- ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName(), expected);
- assertEquals(runCf.stderr, 0, runCf.exitCode);
- assertEquals(runInput.toString(), runCf.toString());
- // Ensure that we did not inline the const method handle
- ensureConstHandleNotInlined(outCf);
- }
-
- private void ensureConstHandleNotInlined(Path file) throws IOException, ExecutionException {
- CodeInspector inspector = new CodeInspector(file);
- MethodSubject subject = inspector.clazz(MethodHandleTest.D.class).method(
- "java.lang.MethodHandle", "vcviSpecialMethod");
- assertTrue(inspector.clazz(MethodHandleTest.D.class)
- .method("java.lang.invoke.MethodHandle", "vcviSpecialMethod").isPresent());
- }
-
- private void runDex() throws Exception {
- Path outDex = temp.getRoot().toPath().resolve("dex.zip");
- build(new DexIndexedConsumer.ArchiveConsumer(outDex));
- String expected = lookupType == LookupType.CONSTANT ? "pass" : "exception";
- ProcessResult runDex =
- ToolHelper.runArtRaw(
- outDex.toString(),
- CLASS.getCanonicalName(),
- cmd -> cmd.appendProgramArgument(expected));
- // Only compare stdout and exitCode since dex2oat prints to stderr.
- if (runInput.exitCode != runDex.exitCode) {
- System.out.println(runDex.stderr);
- }
- assertEquals(runInput.exitCode, runDex.exitCode);
- assertEquals(runInput.stdout, runDex.stdout);
- }
-
- private void build(ProgramConsumer programConsumer) throws Exception {
- // MethodHandle.invoke() only supported from Android O
- // ConstMethodHandle only supported from Android P
- Builder builder =
- R8Command.builder()
- .setMode(compilationMode)
- .setProgramConsumer(programConsumer)
- .setDisableTreeShaking(true)
- .setDisableMinification(true);
- if (programConsumer instanceof ClassFileConsumer) {
- builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
- } else {
- AndroidApiLevel apiLevel = AndroidApiLevel.P;
- builder
- .setMinApiLevel(apiLevel.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(apiLevel));
- }
- for (Class<?> c : inputClasses) {
- byte[] classAsBytes = getClassAsBytes(c);
- builder.addClassProgramData(classAsBytes, Origin.unknown());
- }
- if (minifyMode == MinifyMode.MINIFY) {
- ToolHelper.allowTestProguardOptions(builder);
- builder.addProguardConfiguration(
- Arrays.asList(
- keepMainProguardConfiguration(MethodHandleTest.class),
- noVerticalClassMergingRule(),
- // Prevent the second argument of C.svic(), C.sjic(), I.sjic() and I.svic() from
- // being removed although they are never used unused. This is needed since these
- // methods are accessed reflectively.
- "-keep,allowobfuscation public class " + C.class.getTypeName() + " {",
- " static void svic(int, char);",
- " static long sjic(int, char);",
- "}",
- "-keep,allowobfuscation public interface " + I.class.getTypeName() + " {",
- " static long sjic(int, char);",
- " static void svic(int, char);",
- "}"),
- Origin.unknown());
- }
- ToolHelper.runR8(builder.build());
- }
-
- private byte[] getClassAsBytes(Class<?> clazz) throws Exception {
- if (lookupType == LookupType.CONSTANT) {
- if (clazz == MethodHandleTest.D.class) {
- return MethodHandleDump.dumpD();
- } else if (clazz == MethodHandleTest.class) {
- return MethodHandleDump.transform(ToolHelper.getClassAsBytes(clazz));
- }
- }
- return ToolHelper.getClassAsBytes(clazz);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java
new file mode 100644
index 0000000..889fd91
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2022, 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.cf.methodhandles;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.references.Reference.classFromClass;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandle;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Test that unrepresentable MethodHandle invokes are replaced by throwing instructions. See
+ * b/174733673.
+ */
+@RunWith(Parameterized.class)
+public class InvokeMethodHandleRuntimeErrorTest extends TestBase {
+
+ private static final String EXPECTED = StringUtils.lines("I.target");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeMethodHandleRuntimeErrorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean hasCompileSupport() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ @Test
+ public void testReference() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(Main.class, I.class, Super.class)
+ .addProgramClassFileData(getInvokeCustomTransform())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(Main.class, I.class, Super.class)
+ .addProgramClassFileData(getInvokeCustomTransform())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (hasCompileSupport()) {
+ diagnostics.assertNoMessages();
+ } else {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ }
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ hasCompileSupport(),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r ->
+ r.assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("const-method-handle")));
+ }
+
+ private static byte[] getInvokeCustomTransform() throws Throwable {
+ ClassReference symbolicHolder = classFromClass(InvokeCustom.class);
+ MethodReference method = methodFromMethod(InvokeCustom.class.getMethod("target"));
+ return transformer(InvokeCustom.class)
+ .transformMethodInsnInMethod(
+ "test",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ // Replace null argument by a const method handle.
+ visitor.visitInsn(Opcodes.POP);
+ visitor.visitLdcInsn(
+ new Handle(
+ Opcodes.H_INVOKEVIRTUAL,
+ symbolicHolder.getBinaryName(),
+ method.getMethodName(),
+ method.getMethodDescriptor(),
+ false));
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ interface I {
+ default void target() {
+ System.out.println("I.target");
+ }
+ }
+
+ static class Super implements I {}
+
+ static class InvokeCustom extends Super {
+
+ public static void doInvoke(MethodHandle handle) throws Throwable {
+ handle.invoke(new InvokeCustom());
+ }
+
+ public static void test() throws Throwable {
+ doInvoke(null /* will be const method handle */);
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) throws Throwable {
+ InvokeCustom.test();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleDump.java
new file mode 100644
index 0000000..4881866
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleDump.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2022, 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.cf.methodhandles;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.google.common.collect.ImmutableMap;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// The method MethodHandleDump.transform() translates methods in MethodHandleTest that look like
+// MethodType.methodType(TYPES)
+// and
+// MethodHandles.lookup().findKIND(FOO.class, "METHOD", TYPE)
+// into LDC instructions.
+// This is necessary since there is no Java syntax that compiles to
+// LDC of a constant method handle or constant method type.
+//
+// The method dumpD() dumps a class equivalent to MethodHandleTest.D
+// that uses an LDC instruction instead of MethodHandles.lookup().findSpecial().
+// The LDC instruction loads an InvokeSpecial constant method handle to a C method,
+// so this LDC instruction must be in a subclass of C, and not directly on MethodHandleTest.
+public class MethodHandleDump implements Opcodes {
+
+ private static final String cDesc = TestBase.binaryName(MethodHandleTest.C.class);
+ private static final String eDesc = TestBase.binaryName(MethodHandleTest.E.class);
+ private static final String fDesc = TestBase.binaryName(MethodHandleTest.F.class);
+ private static final String iDesc = TestBase.binaryName(MethodHandleTest.I.class);
+ private static final Type viType = Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE);
+ private static final Type jiType = Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE);
+ private static final Type vicType =
+ Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
+ private static final Type jicType =
+ Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
+ private static final Type veType = Type.getMethodType(Type.VOID_TYPE, Type.getObjectType(eDesc));
+ private static final Type fType = Type.getMethodType(Type.getObjectType(fDesc));
+ private static final String viDesc = viType.getDescriptor();
+ private static final String jiDesc = jiType.getDescriptor();
+ private static final String vicDesc = vicType.getDescriptor();
+ private static final String jicDesc = jicType.getDescriptor();
+
+ public static byte[] getTransformedClass() throws Exception {
+ ImmutableMap<String, Type> types =
+ ImmutableMap.<String, Type>builder()
+ .put("viType", viType)
+ .put("jiType", jiType)
+ .put("vicType", vicType)
+ .put("jicType", jicType)
+ .put("veType", veType)
+ .put("fType", fType)
+ .build();
+
+ ImmutableMap<String, Handle> methods =
+ ImmutableMap.<String, Handle>builder()
+ .put("scviMethod", new Handle(H_INVOKESTATIC, cDesc, "svi", viDesc, false))
+ .put("scjiMethod", new Handle(H_INVOKESTATIC, cDesc, "sji", jiDesc, false))
+ .put("scvicMethod", new Handle(H_INVOKESTATIC, cDesc, "svic", vicDesc, false))
+ .put("scjicMethod", new Handle(H_INVOKESTATIC, cDesc, "sjic", jicDesc, false))
+ .put("vcviMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vvi", viDesc, false))
+ .put("vcjiMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vji", jiDesc, false))
+ .put("vcvicMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vvic", vicDesc, false))
+ .put("vcjicMethod", new Handle(H_INVOKEVIRTUAL, cDesc, "vjic", jicDesc, false))
+ .put("siviMethod", new Handle(H_INVOKESTATIC, iDesc, "svi", viDesc, true))
+ .put("sijiMethod", new Handle(H_INVOKESTATIC, iDesc, "sji", jiDesc, true))
+ .put("sivicMethod", new Handle(H_INVOKESTATIC, iDesc, "svic", vicDesc, true))
+ .put("sijicMethod", new Handle(H_INVOKESTATIC, iDesc, "sjic", jicDesc, true))
+ .put("diviMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dvi", viDesc, true))
+ .put("dijiMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dji", jiDesc, true))
+ .put("divicMethod", new Handle(H_INVOKEINTERFACE, iDesc, "dvic", vicDesc, true))
+ .put("dijicMethod", new Handle(H_INVOKEINTERFACE, iDesc, "djic", jicDesc, true))
+ .put(
+ "constructorMethod", new Handle(H_NEWINVOKESPECIAL, cDesc, "<init>", viDesc, false))
+ .build();
+
+ return ClassFileTransformer.create(MethodHandleTest.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ switch (desc) {
+ case "()Ljava/lang/invoke/MethodType;":
+ {
+ Type type = types.get(name);
+ assert type != null : name;
+ assert access == ACC_PUBLIC + ACC_STATIC;
+ assert signature == null;
+ assert exceptions == null;
+ MethodVisitor mv = super.visitMethod(access, name, desc, null, null);
+ mv.visitCode();
+ mv.visitLdcInsn(type);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ case "()Ljava/lang/invoke/MethodHandle;":
+ {
+ Handle method = methods.get(name);
+ assert access == ACC_PUBLIC + ACC_STATIC;
+ assert method != null : name;
+ assert signature == null;
+ assert exceptions == null;
+ MethodVisitor mv = super.visitMethod(access, name, desc, null, null);
+ mv.visitCode();
+ mv.visitLdcInsn(method);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ default:
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ }
+ })
+ .transform();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
rename to src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
index 690dd6b..497e19f 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, 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.cf;
+package com.android.tools.r8.cf.methodhandles;
import com.android.tools.r8.NoVerticalClassMerging;
import java.lang.invoke.MethodHandle;
@@ -16,13 +16,6 @@
System.out.println("C " + i);
}
- public C() {
- System.out.println("C");
- }
-
- public int vi;
- public static int si;
-
public static void svi(int i) {
System.out.println("svi " + i);
}
@@ -60,20 +53,6 @@
}
}
- public static class D extends C {
- public static MethodHandle vcviSpecialMethod() {
- try {
- return MethodHandles.lookup().findSpecial(C.class, "vvi", viType(), D.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public void vvi(int i) {
- // Overridden to output nothing.
- }
- }
-
public static class E {
// Class that is only mentioned in parameter list of LDC(MethodType)-instruction.
}
@@ -84,7 +63,6 @@
@NoVerticalClassMerging
public interface I {
- int ii = 42;
static void svi(int i) {
System.out.println("svi " + i);
@@ -128,7 +106,6 @@
public static void main(String[] args) {
// When MethodHandleTestRunner invokes this program with the JVM, "fail" is passed as arg.
// When invoked with Art, no arg is passed since interface fields may be modified on Art.
- String expectedResult = args[0];
C c = new C(42);
I i = new Impl();
try {
@@ -148,33 +125,6 @@
assertEquals(42L, (long) dijiMethod().invoke(i, 14));
divicMethod().invoke(i, 15, 'x');
assertEquals(42L, (long) dijicMethod().invoke(i, 16, 'x'));
- vciSetField().invoke(c, 17);
- assertEquals(17, (int) vciGetField().invoke(c));
- sciSetField().invoke(18);
- assertEquals(18, (int) sciGetField().invoke());
- String interfaceSetResult;
- try {
- iiSetField().invoke(19);
- interfaceSetResult = "pass";
- } catch (RuntimeException e) {
- if (e.getCause() instanceof IllegalAccessException) {
- interfaceSetResult = "exception";
- } else {
- throw e;
- }
- } catch (IllegalAccessError e) {
- interfaceSetResult = "error";
- }
- if (!interfaceSetResult.equals(expectedResult)) {
- throw new RuntimeException(
- "Wrong outcome of iiSetField().invoke(): Expected "
- + expectedResult
- + " but got "
- + interfaceSetResult);
- }
- assertEquals(interfaceSetResult.equals("pass") ? 19 : 42, (int) iiGetField().invoke());
- MethodHandle methodHandle = D.vcviSpecialMethod();
- methodHandle.invoke(new D(), 20);
constructorMethod().invoke(21);
System.out.println(veType().parameterType(0).getName().lastIndexOf('.'));
System.out.println(fType().returnType().getName().lastIndexOf('.'));
@@ -189,12 +139,6 @@
}
}
- private static void assertEquals(int l, int x) {
- if (l != x) {
- throw new AssertionError("Not equal: " + l + " != " + x);
- }
- }
-
public static MethodType viType() {
return MethodType.methodType(void.class, int.class);
}
@@ -347,54 +291,6 @@
}
}
- public static MethodHandle vciSetField() {
- try {
- return MethodHandles.lookup().findSetter(C.class, "vi", int.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static MethodHandle sciSetField() {
- try {
- return MethodHandles.lookup().findStaticSetter(C.class, "si", int.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static MethodHandle vciGetField() {
- try {
- return MethodHandles.lookup().findGetter(C.class, "vi", int.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static MethodHandle sciGetField() {
- try {
- return MethodHandles.lookup().findStaticGetter(C.class, "si", int.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static MethodHandle iiSetField() {
- try {
- return MethodHandles.lookup().findStaticSetter(I.class, "ii", int.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static MethodHandle iiGetField() {
- try {
- return MethodHandles.lookup().findStaticGetter(I.class, "ii", int.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
public static MethodHandle constructorMethod() {
try {
return MethodHandles.lookup().findConstructor(C.class, viType());
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java
new file mode 100644
index 0000000..1812854
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java
@@ -0,0 +1,207 @@
+// Copyright (c) 2022, 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.cf.methodhandles;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.C;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.E;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.F;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.I;
+import com.android.tools.r8.cf.methodhandles.MethodHandleTest.Impl;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MethodHandleTestRunner extends TestBase {
+ static final Class<?> CLASS = MethodHandleTest.class;
+
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
+
+ enum MinifyMode {
+ NONE,
+ MINIFY,
+ }
+
+ private String getExpected() {
+ return StringUtils.lines(
+ "C 42", "svi 1", "sji 2", "svic 3", "sjic 4", "vvi 5", "vji 6", "vvic 7", "vjic 8", "svi 9",
+ "sji 10", "svic 11", "sjic 12", "dvi 13", "dji 14", "dvic 15", "djic 16", "C 21", "37",
+ "37");
+ }
+
+ private final TestParameters parameters;
+ private final LookupType lookupType;
+ private final MinifyMode minifyMode;
+
+ @Parameters(name = "{0}, lookup:{1}, minify:{2}")
+ public static List<Object[]> data() {
+ List<Object[]> res = new ArrayList<>();
+ for (TestParameters params :
+ TestParameters.builder()
+ .withCfRuntimes()
+ .withDexRuntimesStartingFromExcluding(Version.V7_0_0)
+ // .withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
+ .withAllApiLevels()
+ .build()) {
+ for (LookupType lookupType : LookupType.values()) {
+ for (MinifyMode minifyMode : MinifyMode.values()) {
+ if (lookupType == LookupType.DYNAMIC && minifyMode == MinifyMode.MINIFY) {
+ // Skip because we don't keep the members looked up dynamically.
+ continue;
+ }
+ res.add(new Object[] {params, lookupType.name(), minifyMode.name()});
+ }
+ }
+ }
+ return res;
+ }
+
+ public MethodHandleTestRunner(TestParameters parameters, String lookupType, String minifyMode) {
+ this.parameters = parameters;
+ this.lookupType = LookupType.valueOf(lookupType);
+ this.minifyMode = MinifyMode.valueOf(minifyMode);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(getInputClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), CLASS.getName())
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() && minifyMode == MinifyMode.NONE);
+ testForD8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(getInputClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), CLASS.getName())
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestBuilder<?> builder =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(getInputClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addNoVerticalClassMergingAnnotations();
+ if (minifyMode == MinifyMode.MINIFY) {
+ builder
+ .enableProguardTestOptions()
+ .addKeepMainRule(MethodHandleTest.class)
+ .addKeepRules(
+ // Prevent the second argument of C.svic(), C.sjic(), I.sjic() and I.svic() from
+ // being removed although they are never used unused. This is needed since these
+ // methods are accessed reflectively.
+ "-keep,allowobfuscation public class " + typeName(C.class) + " {",
+ " static void svic(int, char);",
+ " static long sjic(int, char);",
+ "}",
+ "-keep,allowobfuscation public interface " + typeName(I.class) + " {",
+ " static long sjic(int, char);",
+ " static void svic(int, char);",
+ "}");
+ // TODO(b/235810300): The compiler fails with assertion in AppInfoWithLiveness.
+ if (lookupType == LookupType.CONSTANT && hasConstMethodCompileSupport()) {
+ builder.allowDiagnosticMessages();
+ assertThrows(CompilationFailedException.class, builder::compile);
+ return;
+ }
+ } else {
+ builder.noTreeShaking();
+ builder.noMinification();
+ }
+ builder
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), CLASS.getCanonicalName())
+ .apply(this::checkResult);
+ }
+
+ private boolean hasConstMethodCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
+ || (lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport())) {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ if (lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport()) {
+ result
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("invoke-polymorphic"));
+ return;
+ }
+ if (lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport()) {
+ result
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("const-method-handle"));
+ return;
+ }
+ result.assertSuccessWithOutput(getExpected());
+ }
+
+ private List<Class<?>> getInputClasses() {
+ Builder<Class<?>> builder =
+ ImmutableList.<Class<?>>builder().add(C.class, I.class, Impl.class, E.class, F.class);
+ if (lookupType == LookupType.DYNAMIC) {
+ builder.add(MethodHandleTest.class);
+ }
+ return builder.build();
+ }
+
+ private List<byte[]> getTransformedClasses() throws Exception {
+ if (lookupType == LookupType.DYNAMIC) {
+ return ImmutableList.of();
+ }
+ return ImmutableList.of(MethodHandleDump.getTransformedClass());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java
new file mode 100644
index 0000000..ccb901d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2022, 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.cf.methodhandles;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.UnsupportedInvokePolymorphicMethodHandleDiagnostic;
+import com.android.tools.r8.examples.JavaExampleClassProxy;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+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;
+
+/** Test for VarHandle (these are a refactoring of the old example test setup.) */
+@RunWith(Parameterized.class)
+public class VarHandleTest extends TestBase {
+
+ private static final String PKG = "varhandle";
+ private static final String EXAMPLE = "examplesJava9/" + PKG;
+ private final JavaExampleClassProxy MAIN =
+ new JavaExampleClassProxy(EXAMPLE, PKG + ".VarHandleTests");
+
+ private static final String EXPECTED = StringUtils.lines("true", "false");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build();
+ }
+
+ public VarHandleTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private boolean hasMethodHandlesClass() {
+ return parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
+ }
+
+ private boolean hasFindStaticVarHandleMethod() {
+ // API docs list this as present from T(33), but it was included from 28
+ return parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.P);
+ }
+
+ private boolean hasVarHandleInLibrary() {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.T);
+ }
+
+ public List<Path> getProgramInputs() {
+ return ImmutableList.of(JavaExampleClassProxy.examplesJar(EXAMPLE));
+ }
+
+ @Test
+ public void testReference() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramFiles(getProgramInputs())
+ .run(parameters.getRuntime(), MAIN.typeName())
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ assumeTrue(parameters.isDexRuntime());
+ assumeFalse(
+ "TODO(b/204855476): The default VM throws unsupported. Ignore it and reconsider for 8.0.0",
+ parameters.isDexRuntimeVersion(Version.DEFAULT));
+ testForD8()
+ .addProgramFiles(getProgramInputs())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (hasInvokePolymorphicCompileSupport()) {
+ diagnostics.assertNoMessages();
+ } else {
+ diagnostics
+ .assertAllWarningsMatch(
+ diagnosticType(UnsupportedInvokePolymorphicMethodHandleDiagnostic.class))
+ .assertOnlyWarnings();
+ }
+ })
+ .run(parameters.getRuntime(), MAIN.typeName())
+ .applyIf(
+ !hasMethodHandlesClass(),
+ r ->
+ r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)
+ .assertStderrMatches(containsString("java.lang.invoke.MethodHandles")),
+ !hasFindStaticVarHandleMethod(),
+ r ->
+ r.assertFailureWithErrorThatThrows(NoSuchMethodError.class)
+ .assertStderrMatches(containsString("findStaticVarHandle")),
+ !hasInvokePolymorphicCompileSupport(),
+ r ->
+ r.assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("invoke-polymorphic")),
+ r -> r.assertSuccessWithOutput(EXPECTED));
+ }
+
+ @Test
+ public void testR8() throws Throwable {
+ // This just tests R8 on the targets where the program is fully supported.
+ assumeTrue(hasInvokePolymorphicCompileSupport() && hasFindStaticVarHandleMethod());
+ testForR8(parameters.getBackend())
+ .addProgramFiles(getProgramInputs())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(MAIN.typeName())
+ .applyIf(!hasVarHandleInLibrary(), b -> b.addDontWarn("java.lang.invoke.VarHandle"))
+ .run(parameters.getRuntime(), MAIN.typeName())
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/C.java b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/C.java
new file mode 100644
index 0000000..0ccb1e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, 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.cf.methodhandles.fields;
+
+// This is a top-level class.
+// The use of handles will check generics on C and fail if it cannot find the outer class.
+public class C {
+
+ public int vi;
+ public static int si;
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java
new file mode 100644
index 0000000..062f22e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java
@@ -0,0 +1,229 @@
+// Copyright (c) 2022, 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.cf.methodhandles.fields;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.H_GETFIELD;
+import static org.objectweb.asm.Opcodes.H_GETSTATIC;
+import static org.objectweb.asm.Opcodes.H_PUTFIELD;
+import static org.objectweb.asm.Opcodes.H_PUTSTATIC;
+
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableMap;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class ClassFieldMethodHandleTest extends TestBase {
+
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
+
+ private final TestParameters parameters;
+ private final LookupType lookupType;
+
+ @Parameters(name = "{0}, lookup:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ TestParameters.builder().withAllRuntimesAndApiLevels().build(), LookupType.values());
+ }
+
+ public ClassFieldMethodHandleTest(TestParameters parameters, LookupType lookupType) {
+ this.parameters = parameters;
+ this.lookupType = lookupType;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(C.class)
+ .addProgramClassFileData(getTransformedMain())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(C.class)
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepClassAndMembersRules(C.class, Main.class)
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(C.class)
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ private boolean hasConstMethodCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private boolean hasMethodHandlesRuntimeSupport() {
+ return parameters.isCfRuntime()
+ || parameters
+ .asDexRuntime()
+ .maxSupportedApiLevel()
+ .isGreaterThanOrEqualTo(AndroidApiLevel.O);
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
+ || lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport()) {
+ diagnostics
+ .assertAllWarningsMatch(
+ DiagnosticsMatcher.diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ if (lookupType == LookupType.DYNAMIC && hasInvokePolymorphicCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else if (hasConstMethodCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else if (lookupType == LookupType.DYNAMIC && !hasMethodHandlesRuntimeSupport()) {
+ result.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+ } else {
+ result.assertFailureWithErrorThatMatches(
+ containsString(
+ lookupType == LookupType.DYNAMIC ? "invoke-polymorphic" : "const-method-handle"));
+ }
+ }
+
+ private String getExpected() {
+ return StringUtils.lines("AOK");
+ }
+
+ byte[] getTransformedMain() throws Exception {
+ return transformer(Main.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ MethodVisitor mv =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (lookupType == LookupType.CONSTANT && name.endsWith("Field")) {
+ String fieldName = name.startsWith("sci") ? "si" : "vi";
+ int type =
+ ImmutableMap.<String, Integer>builder()
+ .put("sciSetField", H_PUTSTATIC)
+ .put("sciGetField", H_GETSTATIC)
+ .put("vciSetField", H_PUTFIELD)
+ .put("vciGetField", H_GETFIELD)
+ .build()
+ .get(name);
+ mv.visitCode();
+ mv.visitLdcInsn(new Handle(type, binaryName(C.class), fieldName, "I", false));
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ return mv;
+ }
+ })
+ .transform();
+ }
+
+ public static class Main {
+
+ public static MethodHandle vciSetField() {
+ try {
+ return MethodHandles.lookup().findSetter(C.class, "vi", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle vciGetField() {
+ try {
+ return MethodHandles.lookup().findGetter(C.class, "vi", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle sciSetField() {
+ try {
+ return MethodHandles.lookup().findStaticSetter(C.class, "si", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle sciGetField() {
+ try {
+ return MethodHandles.lookup().findStaticGetter(C.class, "si", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void assertEquals(int x, int y) {
+ if (x != y) {
+ throw new AssertionError("failed!");
+ }
+ }
+
+ public static void main(String[] args) throws Throwable {
+ C c = new C();
+ vciSetField().invoke(c, 17);
+ assertEquals(17, (int) vciGetField().invoke(c));
+ sciSetField().invoke(18);
+ assertEquals(18, (int) sciGetField().invoke());
+ System.out.println("AOK");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java
new file mode 100644
index 0000000..3d62b40
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java
@@ -0,0 +1,217 @@
+// Copyright (c) 2022, 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.cf.methodhandles.fields;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.H_GETSTATIC;
+import static org.objectweb.asm.Opcodes.H_PUTSTATIC;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class InterfaceFieldMethodHandleTest extends TestBase {
+
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
+
+ private final TestParameters parameters;
+ private final LookupType lookupType;
+
+ @Parameters(name = "{0}, lookup:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ TestParameters.builder()
+ // Runtimes without Handle APIs fail in various ways. Start testing beyond that point.
+ .withDexRuntimesStartingFromExcluding(Version.V7_0_0)
+ .withAllApiLevels()
+ .withCfRuntimes()
+ .build(),
+ LookupType.values());
+ }
+
+ public InterfaceFieldMethodHandleTest(TestParameters parameters, LookupType lookupType) {
+ this.parameters = parameters;
+ this.lookupType = lookupType;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getTransformedMain())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepClassAndMembersRules(I.class, Main.class)
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ private boolean hasConstMethodCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
+ || lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport()) {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ if (parameters.isDexRuntimeVersion(Version.V13_0_0)
+ && lookupType == LookupType.CONSTANT
+ && hasConstMethodCompileSupport()) {
+ // TODO(b/235576668): VM 13 throws an escaping IAE outside the guarded range.
+ result
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class)
+ .assertStderrMatches(containsString("Main.main"));
+ return;
+ }
+ if (lookupType == LookupType.DYNAMIC && hasInvokePolymorphicCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else if (hasConstMethodCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else {
+ result.assertFailureWithErrorThatMatches(
+ containsString(
+ lookupType == LookupType.DYNAMIC ? "invoke-polymorphic" : "const-method-handle"));
+ }
+ }
+
+ private String getExpected() {
+ if (lookupType == LookupType.CONSTANT && parameters.isDexRuntimeVersion(Version.V9_0_0)) {
+ // VM 9 will assign the value in the setter in contrast to RI.
+ return StringUtils.lines("42", "pass", "19");
+ }
+ return StringUtils.lines("42", lookupType == LookupType.DYNAMIC ? "exception" : "error", "42");
+ }
+
+ byte[] getTransformedMain() throws Exception {
+ return transformer(Main.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ MethodVisitor mv =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (lookupType == LookupType.CONSTANT && name.endsWith("Field")) {
+ int type = name.equals("iiSetField") ? H_PUTSTATIC : H_GETSTATIC;
+ mv.visitCode();
+ mv.visitLdcInsn(new Handle(type, binaryName(I.class), "ii", "I", true));
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ return mv;
+ }
+ })
+ .transform();
+ }
+
+ public interface I {
+ int ii = 42;
+ }
+
+ public static class Main {
+
+ public static MethodHandle iiSetField() {
+ try {
+ return MethodHandles.lookup().findStaticSetter(I.class, "ii", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static MethodHandle iiGetField() {
+ try {
+ return MethodHandles.lookup().findStaticGetter(I.class, "ii", int.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void read() throws Throwable {
+ System.out.println(iiGetField().invoke());
+ }
+
+ public static void main(String[] args) throws Throwable {
+ read();
+ // Note: having the try-catch inlined here hits ART issue b/235576668.
+ try {
+ iiSetField().invoke(19);
+ System.out.println("pass");
+ } catch (IllegalAccessError e) {
+ System.out.println("error");
+ } catch (RuntimeException e) {
+ System.out.println("exception");
+ }
+ read();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
new file mode 100644
index 0000000..459ce08
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
@@ -0,0 +1,206 @@
+// Copyright (c) 2022, 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.cf.methodhandles.invokespecial;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.H_INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialMethodHandleTest extends TestBase {
+
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
+
+ private final TestParameters parameters;
+ private final LookupType lookupType;
+
+ @Parameters(name = "{0}, lookup:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ TestParameters.builder()
+ // Runtimes without Handle APIs fail in various ways. Start testing beyond that point.
+ .withDexRuntimesStartingFromExcluding(Version.V7_0_0)
+ .withAllApiLevels()
+ .withCfRuntimes()
+ .build(),
+ LookupType.values());
+ }
+
+ public InvokeSpecialMethodHandleTest(TestParameters parameters, LookupType lookupType) {
+ this.parameters = parameters;
+ this.lookupType = lookupType;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(C.class, Main.class)
+ .addProgramClassFileData(getTransformedD())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(C.class, Main.class)
+ .addProgramClassFileData(getTransformedD())
+ .setMinApi(parameters.getApiLevel())
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepClassAndMembersRules(C.class, D.class, Main.class)
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .addProgramClasses(C.class, Main.class)
+ .addProgramClassFileData(getTransformedD())
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkResult);
+ }
+
+ private boolean hasConstMethodCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithConstMethodHandleSupport());
+ }
+
+ private boolean hasInvokePolymorphicCompileSupport() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
+ || (lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport())) {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedFeatureDiagnostic.class))
+ .assertOnlyWarnings();
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ }
+
+ private void checkResult(TestRunResult<?> result) {
+ if (lookupType == LookupType.DYNAMIC && hasInvokePolymorphicCompileSupport()) {
+ result.assertSuccessWithOutput(getExpected());
+ } else if (lookupType == LookupType.CONSTANT && hasConstMethodCompileSupport()) {
+ if (parameters.isDexRuntimeVersion(Version.V9_0_0)) {
+ // VM 9 incorrectly prints out the overridden method despite the direct target.
+ result.assertSuccessWithOutput("");
+ } else if (parameters.isDexRuntimeVersion(Version.V13_0_0)) {
+ // TODO(b/235807678): Subsequent ART VMs incorrectly throw IAE.
+ result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ } else if (parameters.isDexRuntime()
+ && parameters.asDexRuntime().getVersion().isNewerThan(Version.V9_0_0)) {
+ // VMs between 9 and 13 segfault.
+ result.assertFailureWithErrorThatMatches(containsString("HandleUnexpectedSignal"));
+ } else {
+ result.assertSuccessWithOutput(getExpected());
+ }
+ } else {
+ result.assertFailureWithErrorThatMatches(
+ containsString(
+ lookupType == LookupType.DYNAMIC ? "invoke-polymorphic" : "const-method-handle"));
+ }
+ }
+
+ private String getExpected() {
+ return StringUtils.lines("vvi 20");
+ }
+
+ byte[] getTransformedD() throws Exception {
+ return transformer(D.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ MethodVisitor mv =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (lookupType == LookupType.CONSTANT && name.equals("vcviSpecialMethod")) {
+ mv.visitCode();
+ mv.visitLdcInsn(
+ new Handle(H_INVOKESPECIAL, binaryName(C.class), "vvi", "(I)V", false));
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ return null;
+ }
+ return mv;
+ }
+ })
+ .transform();
+ }
+
+ public static class C {
+
+ public void vvi(int i) {
+ System.out.println("vvi " + i);
+ }
+ }
+
+ public static class D extends C {
+
+ public void vvi(int i) {
+ // Overridden to output nothing.
+ }
+
+ public static MethodHandle vcviSpecialMethod() {
+ try {
+ return MethodHandles.lookup()
+ .findSpecial(C.class, "vvi", MethodType.methodType(void.class, int.class), D.class);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Throwable {
+ MethodHandle methodHandle = D.vcviSpecialMethod();
+ methodHandle.invoke(new D(), 20);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/UnresolvableMethodWithClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/UnresolvableMethodWithClassMergingTest.java
new file mode 100644
index 0000000..c8a33ee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/UnresolvableMethodWithClassMergingTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, 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 com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+
+public class UnresolvableMethodWithClassMergingTest extends HorizontalClassMergingTestBase {
+
+ public UnresolvableMethodWithClassMergingTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class, B.class)
+ .addKeepMainRule(Main.class)
+ .addDontWarn(Missing.class)
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoOtherClassesMerged)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, Missing.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B");
+ }
+
+ public static class Main {
+ public static void main(String[] args) {
+ System.out.println(new A());
+ Missing.print(new B());
+ }
+ }
+
+ static class A {
+
+ @Override
+ public String toString() {
+ return "A";
+ }
+ }
+
+ static class B {
+
+ @Override
+ public String toString() {
+ return "B";
+ }
+ }
+
+ static class Missing {
+
+ static void print(B b) {
+ System.out.println(b);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java
new file mode 100644
index 0000000..cf34d5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2022, 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.code.invokedynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Test that unrepresentable invoke-dynamic instructions are replaced by throwing instructions. See
+ * b/174733673.
+ */
+@RunWith(Parameterized.class)
+public class InvokeCustomRuntimeErrorTest extends TestBase {
+
+ private static final String EXPECTED = StringUtils.lines("A::foo");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeCustomRuntimeErrorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean hasCompileSupport() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokeCustomSupport());
+ }
+
+ @Test
+ public void testReference() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(I.class, A.class)
+ .addProgramClassFileData(getTransformedTestClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8CfNoDesugaring() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ // Explicitly test that no-desugaring will maintain a passthrough of the CF code.
+ testForD8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class)
+ .addProgramClassFileData(getTransformedTestClass())
+ .setNoMinApi()
+ .disableDesugaring()
+ .compile()
+ .assertNoMessages()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8DexNoDesugaring() throws Throwable {
+ assumeTrue(parameters.isDexRuntime() && parameters.getApiLevel().equals(AndroidApiLevel.B));
+ // Explicitly test that no-desugaring will still strip instructions.
+ testForD8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class)
+ .addProgramClassFileData(getTransformedTestClass())
+ .setMinApi(parameters.getApiLevel())
+ .disableDesugaring()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedInvokeCustomDiagnostic.class))
+ .assertOnlyWarnings())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("invoke-dynamic"));
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ // For CF compilations we desugar to API level B, thus it should always fail.
+ AndroidApiLevel minApi =
+ parameters.isDexRuntime() ? parameters.getApiLevel() : AndroidApiLevel.B;
+ boolean expectedSuccess = parameters.isDexRuntime() && hasCompileSupport();
+ testForD8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class)
+ .addProgramClassFileData(getTransformedTestClass())
+ .setMinApi(minApi)
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (expectedSuccess) {
+ diagnostics.assertNoMessages();
+ } else {
+ diagnostics
+ .assertAllWarningsMatch(diagnosticType(UnsupportedInvokeCustomDiagnostic.class))
+ .assertOnlyWarnings();
+ }
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ expectedSuccess,
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r ->
+ r.assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertStderrMatches(containsString("invoke-dynamic")));
+ }
+
+ private byte[] getTransformedTestClass() throws Exception {
+ ClassReference aClass = Reference.classFromClass(A.class);
+ MethodReference iFoo = Reference.methodFromMethod(I.class.getDeclaredMethod("foo"));
+ MethodReference bsm =
+ Reference.methodFromMethod(
+ TestClass.class.getDeclaredMethod(
+ "bsmCreateCallSite",
+ Lookup.class,
+ String.class,
+ MethodType.class,
+ MethodHandle.class));
+ return transformer(TestClass.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("replaced")) {
+ visitor.visitInvokeDynamicInsn(
+ iFoo.getMethodName(),
+ "(" + aClass.getDescriptor() + ")V",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ bsm.getHolderClass().getBinaryName(),
+ bsm.getMethodName(),
+ bsm.getMethodDescriptor(),
+ false),
+ new Handle(
+ Opcodes.H_INVOKEVIRTUAL,
+ aClass.getBinaryName(),
+ iFoo.getMethodName(),
+ iFoo.getMethodDescriptor(),
+ false));
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public interface I {
+ void foo();
+ }
+
+ public static class A implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("A::foo");
+ }
+ }
+
+ static class TestClass {
+
+ public static CallSite bsmCreateCallSite(
+ MethodHandles.Lookup caller, String name, MethodType type, MethodHandle handle)
+ throws Throwable {
+ return new ConstantCallSite(handle);
+ }
+
+ public static void replaced(Object o) {
+ throw new RuntimeException("unreachable!");
+ }
+
+ public static void main(String[] args) {
+ replaced(new A());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java
index d6eeb25..efcbe63 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/UnsupportedFeaturesDiagnosticApiTest.java
@@ -10,10 +10,19 @@
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.errors.InvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstDynamicDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstMethodHandleDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstMethodTypeDiagnostic;
+import com.android.tools.r8.errors.UnsupportedDefaultInterfaceMethodDiagnostic;
import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokePolymorphicMethodHandleDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokePolymorphicVarHandleDiagnostic;
+import com.android.tools.r8.errors.UnsupportedPrivateInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.UnsupportedStaticInterfaceMethodDiagnostic;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
+import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.junit.Test;
@@ -30,12 +39,29 @@
@Test
public void test() throws Exception {
+ check(UnsupportedDefaultInterfaceMethodDiagnostic::new, "default-interface-method", 24);
+ check(UnsupportedStaticInterfaceMethodDiagnostic::new, "static-interface-method", 24);
+ check(UnsupportedPrivateInterfaceMethodDiagnostic::new, "private-interface-method", 24);
+ check(UnsupportedInvokeCustomDiagnostic::new, "invoke-custom", 26);
+ check(
+ UnsupportedInvokePolymorphicMethodHandleDiagnostic::new,
+ "invoke-polymorphic-method-handle",
+ 26);
+ check(
+ UnsupportedInvokePolymorphicVarHandleDiagnostic::new, "invoke-polymorphic-var-handle", 28);
+ check(UnsupportedConstMethodHandleDiagnostic::new, "const-method-handle", 28);
+ check(UnsupportedConstMethodTypeDiagnostic::new, "const-method-type", 28);
+ check(UnsupportedConstDynamicDiagnostic::new, "const-dynamic", -1);
+ }
+
+ public void check(
+ BiFunction<Origin, Position, UnsupportedFeatureDiagnostic> makeFn,
+ String descriptor,
+ int level) {
ApiTest test = new ApiTest(ApiTest.PARAMETERS);
test.run(
- new InvokeCustomDiagnostic(Origin.unknown(), Position.UNKNOWN),
- result -> {
- assertEquals("invoke-custom @ 26", result);
- });
+ makeFn.apply(Origin.unknown(), Position.UNKNOWN),
+ result -> assertEquals(descriptor + " @ " + level, result));
}
public static class ApiTest extends CompilerApiTest {
@@ -49,7 +75,16 @@
new DiagnosticsHandler() {
@Override
public void warning(Diagnostic warning) {
- if (warning instanceof UnsupportedFeatureDiagnostic) {
+ if (warning instanceof UnsupportedConstDynamicDiagnostic
+ || warning instanceof UnsupportedConstMethodHandleDiagnostic
+ || warning instanceof UnsupportedConstMethodTypeDiagnostic
+ || warning instanceof UnsupportedDefaultInterfaceMethodDiagnostic
+ || warning instanceof UnsupportedInvokeCustomDiagnostic
+ || warning instanceof UnsupportedInvokePolymorphicMethodHandleDiagnostic
+ || warning instanceof UnsupportedInvokePolymorphicVarHandleDiagnostic
+ || warning instanceof UnsupportedPrivateInterfaceMethodDiagnostic
+ || warning instanceof UnsupportedStaticInterfaceMethodDiagnostic
+ || warning instanceof UnsupportedFeatureDiagnostic) {
UnsupportedFeatureDiagnostic unsupportedFeature =
(UnsupportedFeatureDiagnostic) warning;
String featureDescriptor = unsupportedFeature.getFeatureDescriptor();
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
index 48cbeaf..8c1d8f1 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
@@ -4,16 +4,23 @@
package com.android.tools.r8.desugar.constantdynamic;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsLevel;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.errors.ConstantDynamicDesugarDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstDynamicDiagnostic;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.IOException;
import org.junit.Test;
@@ -37,7 +44,7 @@
@Test
public void testReference() throws Exception {
assumeTrue(parameters.isCfRuntime());
- assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
testForJvm()
@@ -46,33 +53,70 @@
.assertSuccessWithOutputLines("null");
}
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
testForD8()
.addProgramClassFileData(getTransformedMain())
.setMinApi(parameters.getApiLevel())
+ .setDiagnosticsLevelModifier(
+ (level, diagnostic) ->
+ (diagnostic instanceof UnsupportedFeatureDiagnostic
+ || diagnostic instanceof ConstantDynamicDesugarDiagnostic)
+ ? DiagnosticsLevel.WARNING
+ : level)
.compileWithExpectedDiagnostics(
diagnostics ->
- diagnostics.assertErrorMessageThatMatches(
- containsString(
- "Unsupported dynamic constant (runtime provided bootstrap method)")));
+ diagnostics.assertWarningsMatch(
+ diagnosticType(UnsupportedConstDynamicDiagnostic.class),
+ allOf(
+ diagnosticType(ConstantDynamicDesugarDiagnostic.class),
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (runtime provided bootstrap"
+ + " method)")))))
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
}
+ // TODO(b/198142625): Support const-dynamic in IR CF/CF.
@Test(expected = CompilationFailedException.class)
- public void testR8() throws Exception {
- assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ public void testR8Cf() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
testForR8(parameters.getBackend())
.addProgramClassFileData(getTransformedMain())
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
+ .compile();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedMain())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .allowDiagnosticWarningMessages()
+ .mapUnsupportedFeaturesToWarnings()
.compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics.assertErrorMessageThatMatches(
- containsString(
- "Unsupported dynamic constant (runtime provided bootstrap method)")));
+ diagnostics -> {
+ if (parameters.isDexRuntime()) {
+ diagnostics.assertWarningsMatch(
+ allOf(
+ diagnosticType(UnsupportedFeatureDiagnostic.class),
+ diagnosticMessage(containsString("const-dynamic"))));
+ }
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
}
private byte[] getTransformedMain() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
index 1ae1ccf..87f3382 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
@@ -4,19 +4,23 @@
package com.android.tools.r8.desugar.constantdynamic;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
-import static com.android.tools.r8.OriginMatcher.hasParent;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsLevel;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.errors.ConstantDynamicDesugarDiagnostic;
+import com.android.tools.r8.errors.UnsupportedConstDynamicDiagnostic;
+import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
@@ -49,7 +53,7 @@
@Test
public void testReference() throws Exception {
assumeTrue(parameters.isCfRuntime());
- assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
testForJvm()
@@ -59,65 +63,130 @@
}
@Test
+ public void testD8CfNoDesugar() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setNoMinApi()
+ .disableDesugaring()
+ .compile()
+ .assertNoMessages()
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
public void testD8Cf() throws Exception {
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForD8(Backend.CF)
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- diagnosticMessage(
- containsString("Unsupported dynamic constant (different owner)")));
- }));
+ assumeTrue(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .setDiagnosticsLevelModifier(
+ (level, diagnostic) ->
+ (diagnostic instanceof ConstantDynamicDesugarDiagnostic
+ || diagnostic instanceof UnsupportedFeatureDiagnostic)
+ ? DiagnosticsLevel.WARNING
+ : level)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertAllWarningsMatch(
+ anyOf(
+ allOf(
+ diagnosticType(UnsupportedConstDynamicDiagnostic.class),
+ diagnosticMessage(containsString("const-dynamic"))),
+ allOf(
+ diagnosticType(ConstantDynamicDesugarDiagnostic.class),
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (different owner)")))))
+ .assertOnlyWarnings())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
}
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
-
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForD8(parameters.getBackend())
- .addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyErrors();
- diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(
- containsString("Unsupported dynamic constant (different owner)")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
- }));
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .setDiagnosticsLevelModifier(
+ (level, diagnostic) ->
+ (diagnostic instanceof ConstantDynamicDesugarDiagnostic
+ || diagnostic instanceof UnsupportedFeatureDiagnostic)
+ ? DiagnosticsLevel.WARNING
+ : level)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertAllWarningsMatch(
+ anyOf(
+ allOf(
+ diagnosticType(UnsupportedConstDynamicDiagnostic.class),
+ diagnosticMessage(containsString("const-dynamic"))),
+ allOf(
+ diagnosticType(ConstantDynamicDesugarDiagnostic.class),
+ diagnosticMessage(
+ containsString("Unsupported dynamic constant (different owner)")))));
+ })
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
}
@Test
- public void testR8() throws Exception {
- assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
-
+ public void testR8Cf() {
+ assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
assertThrows(
CompilationFailedException.class,
() ->
testForR8(parameters.getBackend())
.addProgramClassFileData(getTransformedClasses())
- .setMinApi(parameters.getApiLevel())
.addKeepMainRule(MAIN_CLASS)
.compileWithExpectedDiagnostics(
diagnostics -> {
diagnostics.assertOnlyErrors();
diagnostics.assertErrorsMatch(
- allOf(
- diagnosticMessage(
- containsString("Unsupported dynamic constant (different owner)")),
- diagnosticOrigin(hasParent(Origin.unknown()))));
+ diagnosticMessage(
+ containsString("Unsupported dynamic constant (not desugaring)")));
}));
}
+ @Test
+ public void testR8Dex() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ .allowDiagnosticMessages()
+ .mapUnsupportedFeaturesToWarnings()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics
+ .assertAllWarningsMatch(
+ anyOf(
+ allOf(
+ diagnosticType(UnsupportedConstDynamicDiagnostic.class),
+ diagnosticMessage(containsString("const-dynamic"))),
+ allOf(
+ diagnosticType(ConstantDynamicDesugarDiagnostic.class),
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (different owner)")))))
+ .assertOnlyWarnings();
+ })
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("const-dynamic"));
+ }
+
private Collection<byte[]> getTransformedClasses() throws IOException {
return ImmutableList.of(
transformer(A.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
index 6200ec5..1e475ce 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.DumpInputFlags;
import com.android.tools.r8.utils.ZipUtils;
import java.io.IOException;
import java.nio.file.Files;
@@ -58,7 +59,8 @@
testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
.addProgramClasses(TestClass.class)
.addKeepMainRule(TestClass.class)
- .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .addOptionsModification(
+ options -> options.setDumpInputFlags(DumpInputFlags.dumpToDirectory(dumpDir)))
.allowDiagnosticInfoMessages()
.compile()
.inspectDiagnosticMessages(
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java
new file mode 100644
index 0000000..7c2ae96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2022, 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.records;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * The test verifies records on classpath do not generate a record global synthetic on the program
+ * if the program does not refer to record.
+ */
+@RunWith(Parameterized.class)
+public class RecordClasspathTest extends TestBase {
+
+ private static final String RECORD_NAME_1 = "RecordWithMembers";
+ private static final byte[][] PROGRAM_DATA_1 = RecordTestUtils.getProgramData(RECORD_NAME_1);
+ private static final String EXPECTED_RESULT = StringUtils.lines("Hello");
+
+ private final TestParameters parameters;
+
+ public RecordClasspathTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(TestClass.class)
+ .addClasspathClassFileData(PROGRAM_DATA_1)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addClasspathClassFileData(PROGRAM_DATA_1)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertNoRecord)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testD8DexPerFile() throws Exception {
+ GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
+ Assume.assumeFalse(parameters.isCfRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addClasspathClassFileData(PROGRAM_DATA_1)
+ .setMinApi(parameters.getApiLevel())
+ .setIntermediate(true)
+ .setOutputMode(OutputMode.DexFilePerClassFile)
+ .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals))
+ .compile()
+ .inspect(this::assertNoRecord)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ assertFalse(globals.hasGlobals());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addClasspathClassFileData(PROGRAM_DATA_1)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class);
+ if (parameters.isCfRuntime()) {
+ builder
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .compile()
+ .inspect(this::assertNoRecord)
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ builder.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private void assertNoRecord(CodeInspector inspector) {
+ // Verify that the record class was not added as part of the compilation.
+ assertEquals(1, inspector.allClasses().size());
+ assertTrue(inspector.clazz(TestClass.class).isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index b77f5a7..0866830 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -10,10 +10,10 @@
import static org.hamcrest.MatcherAssert.assertThat;
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.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -23,6 +23,7 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -73,15 +74,25 @@
@Test
public void testMergeDesugaredInputs() throws Exception {
- // TODO(b/231598779): Records do not yet have contexts so per-file modes fail.
- // This test should be extended or duplicated to also test the pre-file-dex modes.
- assumeTrue("b/230445931", parameters.isDexRuntime());
+ testMergeDesugaredInputsDexPerClass(false);
+ }
+
+ @Test
+ public void testMergeDesugaredInputsDexPerClass() throws Exception {
+ Assume.assumeTrue("CF is already run from the other test", parameters.isDexRuntime());
+ testMergeDesugaredInputsDexPerClass(true);
+ }
+
+ private void testMergeDesugaredInputsDexPerClass(boolean filePerClass) throws Exception {
GlobalSyntheticsTestingConsumer globals1 = new GlobalSyntheticsTestingConsumer();
Path output1 =
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA_1)
.setMinApi(parameters.getApiLevel())
.setIntermediate(true)
+ .applyIf(
+ filePerClass && !parameters.isCfRuntime(),
+ b -> b.setOutputMode(OutputMode.DexFilePerClassFile))
.apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1))
.compile()
.inspect(this::assertDoesNotHaveRecordTag)
@@ -93,6 +104,9 @@
.addProgramClassFileData(PROGRAM_DATA_2)
.setMinApi(parameters.getApiLevel())
.setIntermediate(true)
+ .applyIf(
+ filePerClass && !parameters.isCfRuntime(),
+ b -> b.setOutputMode(OutputMode.DexFilePerClassFile))
.apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals2))
.compile()
.inspect(this::assertDoesNotHaveRecordTag)
@@ -107,8 +121,8 @@
.apply(
b ->
b.getBuilder()
- .addGlobalSyntheticsResourceProviders(
- globals1.getIndexedModeProvider(), globals2.getIndexedModeProvider()))
+ .addGlobalSyntheticsResourceProviders(globals1.getProviders())
+ .addGlobalSyntheticsResourceProviders(globals2.getProviders()))
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::assertHasRecordTag);
diff --git a/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java b/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
index 05824cc..52aafa3 100644
--- a/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
@@ -3,16 +3,21 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dump;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+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.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DumpInputFlags;
import com.android.tools.r8.utils.ZipUtils;
import java.io.IOException;
import java.nio.file.Files;
@@ -40,6 +45,28 @@
}
@Test
+ public void testDumpToFileOptionsModification() throws Exception {
+ Path dump = temp.newFolder().toPath().resolve("dump.zip");
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> options.setDumpInputFlags(DumpInputFlags.dumpToFile(dump)))
+ .allowDiagnosticErrorMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(containsString("Dumped compilation inputs to:"))));
+ fail("Expected compilation to fail");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ verifyDump(dump, false, true);
+ }
+
+ @Test
public void testDumpToFileSystemProperty() throws Exception {
Path dump = temp.newFolder().toPath().resolve("dump.zip");
try {
@@ -47,11 +74,35 @@
.addJvmFlag("-Dcom.android.tools.r8.dumpinputtofile=" + dump)
.addProgramClasses(TestClass.class)
.compile();
+ fail("Expected external compilation to exit");
} catch (AssertionError e) {
- verifyDump(dump, false, true);
- return;
+ // Expected.
}
- fail("Expected external compilation to exit");
+ verifyDump(dump, false, true);
+ }
+
+ @Test
+ public void testDumpToFileSystemPropertyWhenMinifying() throws Exception {
+ for (boolean minification : BooleanUtils.values()) {
+ Path dump = temp.newFolder().toPath().resolve("dump.zip");
+ try {
+ testForExternalR8(parameters.getBackend(), parameters.getRuntime())
+ .addJvmFlag("-Dcom.android.tools.r8.dumpinputtofile=" + dump)
+ .addJvmFlag("-Dcom.android.tools.r8.dump.filter.buildproperty.minification=^true$")
+ .addProgramClasses(TestClass.class)
+ .applyIf(!minification, builder -> builder.addKeepRules("-dontobfuscate"))
+ .compile();
+ // Without minification there should be no dump, and thus compilation should succeed.
+ assertFalse(minification);
+ } catch (AssertionError e) {
+ assertTrue(minification);
+ }
+ if (minification) {
+ verifyDump(dump, false, true);
+ } else {
+ assertFalse(Files.exists(dump));
+ }
+ }
}
@Test
@@ -70,7 +121,7 @@
}
@Test
- public void testDumpToDirectorySystemProperty() throws Exception {
+ public void testDumpToDirectoryOptionsModification() throws Exception {
Path dumpDir = temp.newFolder().toPath();
testForR8(parameters.getBackend())
.addProgramClasses(TestClass.class)
@@ -78,14 +129,28 @@
// Ensure the compilation and run can actually succeed.
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.addKeepMainRule(TestClass.class)
- .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .addOptionsModification(
+ options -> options.setDumpInputFlags(DumpInputFlags.dumpToDirectory(dumpDir)))
.allowDiagnosticInfoMessages()
.compile()
.assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello, world");
+ verifyDumpDirectory(dumpDir, false, true);
+ }
- verifyDumpDirectory(dumpDir, false, false);
+ @Test
+ public void testDumpToDirectorySystemProperty() throws Exception {
+ Path dumpDir = temp.newFolder().toPath();
+ testForExternalR8(parameters.getBackend(), parameters.getRuntime())
+ .addJvmFlag("-Dcom.android.tools.r8.dumpinputtodirectory=" + dumpDir.toString())
+ .addProgramClasses(TestClass.class)
+ // Setting a directory will allow compilation to continue.
+ // Ensure the compilation and run can actually succeed.
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .addKeepMainRule(TestClass.class)
+ .compile();
+ verifyDumpDirectory(dumpDir, false, true);
}
@Test
@@ -95,7 +160,6 @@
.dumpInputToDirectory(dumpDir.toString())
.addProgramClasses(TestClass.class)
.compile();
-
verifyDumpDirectory(dumpDir, false, false);
}
diff --git a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
index 1d01be7..f0372a8 100644
--- a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
+++ b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
@@ -14,6 +14,7 @@
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.DumpInputFlags;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
@@ -103,7 +104,8 @@
.addMainDexListFiles(
mainDexListForMainDexFile1AndMainDexFile2(), mainDexListForMainDexFile3())
.addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
- .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .addOptionsModification(
+ options -> options.setDumpInputFlags(DumpInputFlags.dumpToDirectory(dumpDir)))
.compileWithExpectedDiagnostics(
diagnostics ->
diagnostics
@@ -131,7 +133,8 @@
.addInnerClasses(DumpMainDexInputsTest.class)
.addMainDexRulesFiles(newMainDexRulesPath1(), newMainDexRulesPath2())
.addMainDexKeepClassRules(MainDexClass1.class, MainDexClass2.class, TestClass.class)
- .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .addOptionsModification(
+ options -> options.setDumpInputFlags(DumpInputFlags.dumpToDirectory(dumpDir)))
.compile()
.assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
.run(parameters.getRuntime(), TestClass.class)
@@ -150,7 +153,8 @@
.addInnerClasses(DumpMainDexInputsTest.class)
.addMainDexRuleFiles(newMainDexRulesPath1(), newMainDexRulesPath2())
.addMainDexKeepClassRules(MainDexClass1.class, MainDexClass2.class, TestClass.class)
- .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .addOptionsModification(
+ options -> options.setDumpInputFlags(DumpInputFlags.dumpToDirectory(dumpDir)))
.addKeepAllClassesRule()
.allowDiagnosticMessages()
.compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithMultipleWritesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithMultipleWritesTest.java
new file mode 100644
index 0000000..26fde45
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithMultipleWritesTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2022, 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.optimize.membervaluepropagation;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AccessFlags;
+import java.io.IOException;
+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;
+
+/**
+ * Tests the handling of final fields that are assigned more than once.
+ *
+ * <p>According to the JVM specification, fields that are declared final must never be assigned to
+ * after object construction, but the specification does not require that the field is assigned at
+ * most once.
+ */
+@RunWith(Parameterized.class)
+public class FinalFieldWithMultipleWritesTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getTransformedClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("0", "1", "2", "2");
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getTransformedClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("0", "1", "2", "2");
+ }
+
+ private static byte[] getTransformedClass() throws IOException, NoSuchFieldException {
+ return transformer(A.class)
+ .setAccessFlags(A.class.getDeclaredField("f"), AccessFlags::setFinal)
+ .transform();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A a = new A();
+ System.out.println(a.f);
+ }
+ }
+
+ static class A {
+
+ /* final */ int f;
+
+ A() {
+ System.out.println(this);
+ f = 1;
+ System.out.println(this);
+ f = 2;
+ System.out.println(this);
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(f);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
index 8cc3b8c..0e8b0ad 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
@@ -46,7 +46,6 @@
private void validateOutlining(CodeInspector inspector, Class<?> testClass, String argumentType) {
boolean isStringBuilderOptimized =
enableArgumentPropagation
- && parameters.isDexRuntime()
&& (argumentType.equals("char") || argumentType.equals("boolean"));
if (isStringBuilderOptimized) {
assertEquals(1, inspector.allClasses().size());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java
index 0074f01..5cc664d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java
@@ -5,7 +5,7 @@
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.TestBase;
import com.android.tools.r8.TestParameters;
@@ -45,11 +45,10 @@
private void inspect(CodeInspector inspector) {
MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
assertThat(mainMethodSubject, isPresent());
- assertEquals(
- parameters.isCfRuntime(),
+ assertTrue(
mainMethodSubject
.streamInstructions()
- .anyMatch(instruction -> instruction.isNewInstance("java.lang.StringBuilder")));
+ .noneMatch(instruction -> instruction.isNewInstance("java.lang.StringBuilder")));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
index 365ced3..e7c5301 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
@@ -37,7 +37,7 @@
@Parameters(name = "{0}, configuration: {1}")
public static List<Object[]> data() {
return buildParameters(
- getTestParameters().withDexRuntimes().withAllApiLevels().build(), getTestExpectations());
+ getTestParameters().withAllRuntimesAndApiLevels().build(), getTestExpectations());
}
private static class StringBuilderResult {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectMutationThroughPhiTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectMutationThroughPhiTest.java
index 88f0cf8..92a4f9a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectMutationThroughPhiTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectMutationThroughPhiTest.java
@@ -42,7 +42,7 @@
inspector -> {
MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
assertEquals(
- parameters.isCfRuntime() ? 4 : 3,
+ 3,
mainMethodSubject
.streamInstructions()
.filter(isInvokeStringBuilderAppendWithString())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectPhiMutationThroughPhiOperandTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectPhiMutationThroughPhiOperandTest.java
index 81fa83b..14abc82 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectPhiMutationThroughPhiOperandTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIndirectPhiMutationThroughPhiOperandTest.java
@@ -41,9 +41,8 @@
.inspect(
inspector -> {
MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
- // TODO(b/114002137): Also run for CF
assertEquals(
- parameters.isCfRuntime() ? 4 : 3,
+ 3,
mainMethodSubject
.streamInstructions()
.filter(isInvokeStringBuilderAppendWithString())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithObjectsToStringTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithObjectsToStringTest.java
index 9b7594b..c89ae2e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithObjectsToStringTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithObjectsToStringTest.java
@@ -6,7 +6,7 @@
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.TestBase;
import com.android.tools.r8.TestParameters;
@@ -42,12 +42,10 @@
inspector -> {
MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
assertThat(mainMethodSubject, isPresent());
- // TODO(b/219455761): Extend StringBuilder optimizer to Objects.toString().
- assertEquals(
- canUseJavaUtilObjects(parameters),
+ assertTrue(
mainMethodSubject
.streamInstructions()
- .anyMatch(InstructionSubject::isNewInstance));
+ .noneMatch(InstructionSubject::isNewInstance));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("foo");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
index b8ad8d9..2bc9233 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
@@ -29,4 +29,8 @@
public static NoSuchMethodError throwNoSuchMethodError() {
throw new NoSuchMethodError();
}
+
+ public static RuntimeException throwRuntimeExceptionWithMessage(String message) {
+ throw new RuntimeException(message);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index 5db275b..aecc306 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -579,12 +579,10 @@
private void ensureSameOutput(JasminBuilder app) throws Exception {
String javaOutput = runOnJava(app, MAIN_CLASS);
- String dxOutput = runOnArtDx(app, MAIN_CLASS);
String d8Output = runOnArtD8(app, MAIN_CLASS);
String r8Output = runOnArtR8(app, MAIN_CLASS);
String r8ShakenOutput = runOnArtR8(app, MAIN_CLASS, keepMainProguardConfiguration(MAIN_CLASS),
null);
- Assert.assertEquals(javaOutput, dxOutput);
Assert.assertEquals(javaOutput, d8Output);
Assert.assertEquals(javaOutput, r8Output);
Assert.assertEquals(javaOutput, r8ShakenOutput);
@@ -603,7 +601,6 @@
private void ensureAllFail(JasminBuilder app) throws Exception {
ensureFails(app, MAIN_CLASS, this::runOnJavaRaw);
- ensureFails(app, MAIN_CLASS, this::runOnArtDxRaw);
ensureFails(app, MAIN_CLASS, this::runOnArtD8Raw);
ensureFails(app, MAIN_CLASS, (a, m) -> runOnArtR8Raw(a, m, null));
ensureFails(app, MAIN_CLASS,
@@ -623,7 +620,6 @@
}
};
runtest.accept(() -> runOnJavaNoVerifyRaw(app, MAIN_CLASS), null);
- runtest.accept(() -> runOnArtDxRaw(app, MAIN_CLASS), null);
runtest.accept(() -> runOnArtD8Raw(app, MAIN_CLASS), CompilerUnderTest.D8);
runtest.accept(() -> runOnArtR8Raw(app, MAIN_CLASS, null), CompilerUnderTest.R8);
runtest.accept(() -> runOnArtR8Raw(app, MAIN_CLASS, keepMainProguardConfiguration(MAIN_CLASS),
@@ -644,8 +640,6 @@
private void ensureRuntimeException(JasminBuilder app, Class exception) throws Exception {
String name = exception.getSimpleName();
- ProcessResult dxOutput = runOnArtDxRaw(app, MAIN_CLASS);
- Assert.assertTrue(dxOutput.stderr.contains(name));
ProcessResult d8Output = runOnArtD8Raw(app, MAIN_CLASS);
Assert.assertTrue(d8Output.stderr.contains(name));
ProcessResult r8Output = runOnArtR8Raw(app, MAIN_CLASS, null);
@@ -660,16 +654,14 @@
private void ensureRuntimeException(JasminBuilder app, JasminBuilder library, Class exception)
throws Exception {
String name = exception.getSimpleName();
- ProcessResult dxOutput = runOnArtDxRaw(app, library, MAIN_CLASS);
- Assert.assertTrue(dxOutput.stderr.contains(name));
ProcessResult d8Output = runOnArtD8Raw(app, library, MAIN_CLASS);
- Assert.assertTrue(d8Output.stderr.contains(name));
+ Assert.assertTrue(d8Output.toString(), d8Output.stderr.contains(name));
ProcessResult r8Output = runOnArtR8Raw(app, library, MAIN_CLASS,
noShrinkingNoMinificationProguardConfiguration(), null);
- Assert.assertTrue(r8Output.stderr.contains(name));
+ Assert.assertTrue(r8Output.toString(), r8Output.stderr.contains(name));
ProcessResult r8ShakenOutput = runOnArtR8Raw(app, library, MAIN_CLASS,
keepMainProguardConfiguration(MAIN_CLASS), null);
- Assert.assertTrue(r8ShakenOutput.stderr.contains(name));
+ Assert.assertTrue(r8ShakenOutput.toString(), r8ShakenOutput.stderr.contains(name));
ProcessResult javaOutput = runOnJavaNoVerifyRaw(app, library, MAIN_CLASS);
Assert.assertTrue(javaOutput.stderr.contains(name));
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index b7fe293..31740b7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -37,6 +37,7 @@
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import org.hamcrest.CoreMatchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -161,7 +162,7 @@
} else {
assertThat(
inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
- notIf(isPresent(), testParameters.isDexRuntime()));
+ CoreMatchers.not(isPresent()));
}
// TODO(b/173337498): MainKt$testStateful$1 should be class inlined.
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromForcefullyMovedMethodTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromForcefullyMovedMethodTest.java
new file mode 100644
index 0000000..f75e718
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromForcefullyMovedMethodTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2022, 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.missingclasses;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.diagnostic.DefinitionContext;
+import com.android.tools.r8.diagnostic.internal.DefinitionClassContextImpl;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+public class MissingClassReferencedFromForcefullyMovedMethodTest extends MissingClassesTestBase {
+
+ private static final DefinitionContext referencedFrom =
+ DefinitionClassContextImpl.builder()
+ .setClassContext(Reference.classFromClass(I.class))
+ .setOrigin(getOrigin(I.class))
+ .build();
+
+ public MissingClassReferencedFromForcefullyMovedMethodTest(TestParameters parameters) {
+ super(parameters);
+ assumeFalse(parameters.canUseDefaultAndStaticInterfaceMethods());
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testNoRules() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+ addInterface());
+ }
+
+ @Test
+ public void testDontWarnMainClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface().andThen(addDontWarn(I.class)));
+ }
+
+ @Test
+ public void testDontWarnMissingClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addInterface().andThen(addDontWarn(MissingFunctionalInterface.class)));
+ }
+
+ @Test
+ public void testIgnoreWarnings() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+ addInterface().andThen(addIgnoreWarnings()));
+ }
+
+ @Override
+ ClassReference getMissingClassReference() {
+ return Reference.classFromClass(MissingFunctionalInterface.class);
+ }
+
+ private ThrowableConsumer<R8FullTestBuilder> addInterface() {
+ return builder -> builder.addProgramClasses(I.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I.forcefullyMovedMethod();
+ }
+ }
+
+ interface I {
+
+ static void forcefullyMovedMethod() {
+ MissingFunctionalInterface i = () -> {};
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 2e6ffd2..73b6dfc 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -39,6 +39,11 @@
interface MissingInterface {}
+ interface MissingFunctionalInterface {
+
+ void m();
+ }
+
protected final TestParameters parameters;
@Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageObjectOnProgramPathTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageObjectOnProgramPathTest.java
new file mode 100644
index 0000000..1e0b59e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageObjectOnProgramPathTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static org.junit.Assert.assertThrows;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsMatcher;
+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 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;
+import org.objectweb.asm.ClassWriter;
+
+/** This is a regression test for b/237124748 */
+@RunWith(Parameterized.class)
+public class RepackageObjectOnProgramPathTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assertThrows(
+ CompilationFailedException.class,
+ () -> {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(dumpObject())
+ .addProgramClasses(A.class, Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .addDontWarn("*")
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ // TODO(b/237124748): We should not throw an error.
+ diagnostics.assertErrorThatMatches(
+ DiagnosticsMatcher.diagnosticException(NullPointerException.class)));
+ });
+ }
+
+ public static byte[] dumpObject() {
+ ClassWriter classWriter = new ClassWriter(0);
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "java/lang/Object", null, null, null);
+ classWriter.visitEnd();
+ return classWriter.toByteArray();
+ }
+
+ public static class A {
+
+ @NeverInline
+ public static void foo() {
+ System.out.println("A::foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 997bfd8..0cf3e9e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -78,13 +78,8 @@
if (enableOptimization) {
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("simple"), 1);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("local"), 1);
- // String concatenation optimization is enabled for DEX output.
// Even replaced ordinal is concatenated (and gone).
- if (parameters.isDexRuntime()) {
- assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
- } else {
- assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
- }
+ assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
@@ -131,9 +126,7 @@
if (enableOptimization) {
assertNameReplacedWithConst(clazz.uniqueMethodWithName("simple"), "TWO");
assertNameReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
- // String concatenation optimization is enabled for DEX output.
- String expectedConst = parameters.isDexRuntime() ? "1TWO" : "TWO";
- assertNameReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), expectedConst);
+ assertNameReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), "1TWO");
assertNameReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
assertNameReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 6df7b0a..5a16cf6 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -10,6 +10,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.R8;
import com.android.tools.r8.R8FullTestBuilder;
@@ -232,7 +233,7 @@
.apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
.compile()
.graphInspector();
- assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector);
+ assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, true);
GraphInspector ifThenKeepClassesWithMembersInspector =
testForR8(Backend.CF)
@@ -251,7 +252,7 @@
.apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
.compile()
.graphInspector();
- assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
+ assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector, true);
GraphInspector ifHasMemberThenKeepClassInspector =
testForR8(Backend.CF)
@@ -272,7 +273,7 @@
.apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
.compile()
.graphInspector();
- assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
+ assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true);
}
private void configureHorizontalClassMerging(R8FullTestBuilder testBuilder) {
@@ -292,6 +293,13 @@
private void assertRetainedClassesEqual(
GraphInspector referenceResult, GraphInspector conditionalResult) {
+ assertRetainedClassesEqual(referenceResult, conditionalResult, false);
+ }
+
+ private void assertRetainedClassesEqual(
+ GraphInspector referenceResult,
+ GraphInspector conditionalResult,
+ boolean expectConditionalIsLarger) {
Set<String> referenceClasses =
new TreeSet<>(
referenceResult.codeInspector().allClasses().stream()
@@ -303,10 +311,14 @@
.collect(Collectors.toSet());
Set<String> notInReference =
new TreeSet<>(Sets.difference(conditionalClasses, referenceClasses));
- assertEquals(
- "Classes in -if rule that are not in -keepclassmembers rule",
- Collections.emptySet(),
- notInReference);
+ if (expectConditionalIsLarger) {
+ assertFalse("Expected classes in -if rule to retain more.", notInReference.isEmpty());
+ } else {
+ assertEquals(
+ "Classes in -if rule that are not in -keepclassmembers rule",
+ Collections.emptySet(),
+ notInReference);
+ }
Set<String> notInConditional =
new TreeSet<>(Sets.difference(referenceClasses, conditionalClasses));
assertEquals(
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 56435ed..da3ef50 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -46,7 +46,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;
import org.junit.Assert;
@@ -1384,14 +1383,8 @@
opts.outline.minSize = 3;
opts.outline.maxSize = 3;
- // Do not allow dead code elimination of the new-instance instructions. This can be
- // achieved
- // by not assuming that StringBuilder is present.
- DexItemFactory dexItemFactory = opts.itemFactory;
- opts.itemFactory.libraryTypesAssumedToBePresent =
- new HashSet<>(dexItemFactory.libraryTypesAssumedToBePresent);
- dexItemFactory.libraryTypesAssumedToBePresent.remove(
- dexItemFactory.stringBuilderType);
+ // Do not allow dead code elimination of the new-instance instructions.
+ opts.apiModelingOptions().enableApiCallerIdentification = false;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
diff --git a/tools/archive.py b/tools/archive.py
index d7bf1a9..91affd6 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -161,11 +161,13 @@
create_maven_release.generate_desugar_configuration_maven_zip(
utils.DESUGAR_CONFIGURATION_MAVEN_ZIP,
utils.DESUGAR_CONFIGURATION,
- utils.DESUGAR_IMPLEMENTATION)
+ utils.DESUGAR_IMPLEMENTATION,
+ utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP)
create_maven_release.generate_desugar_configuration_maven_zip(
- utils.DESUGAR_CONFIGURATION_LEGACY_JDK11_MAVEN_ZIP,
+ utils.DESUGAR_CONFIGURATION_JDK11_LEGACY_MAVEN_ZIP,
utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
- utils.DESUGAR_IMPLEMENTATION_JDK11)
+ utils.DESUGAR_IMPLEMENTATION_JDK11,
+ utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP)
version = GetVersion()
is_main = IsMain(version)
@@ -196,6 +198,8 @@
utils.MAVEN_ZIP_LIB,
utils.DESUGAR_CONFIGURATION,
utils.DESUGAR_CONFIGURATION_MAVEN_ZIP,
+ utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
+ utils.DESUGAR_CONFIGURATION_JDK11_LEGACY_MAVEN_ZIP,
utils.GENERATED_LICENSE,
]:
file_name = os.path.basename(file)
@@ -266,6 +270,40 @@
utils.upload_file_to_cloud_storage(
desugar_jdk_libs_configuration_jar, jar_destination)
+ # TODO(sgjesse): Refactor this to avoid the duplication of what is above.
+ # Upload desugar_jdk_libs JDK-11 legacyconfiguration to a maven compatible location.
+ if file == utils.DESUGAR_CONFIGURATION_JDK11_LEGACY:
+ jar_basename = 'desugar_jdk_libs_configuration.jar'
+ jar_version_name = 'desugar_jdk_libs_configuration-%s-jdk11-legacy.jar' % version
+ maven_dst = GetUploadDestination(
+ utils.get_maven_path('desugar_jdk_libs_configuration', version),
+ jar_version_name, is_main)
+
+ with utils.TempDir() as tmp_dir:
+ desugar_jdk_libs_configuration_jar = os.path.join(tmp_dir,
+ jar_version_name)
+ create_maven_release.generate_jar_with_desugar_configuration(
+ utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
+ utils.DESUGAR_IMPLEMENTATION_JDK11,
+ utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP,
+ desugar_jdk_libs_configuration_jar)
+
+ if options.dry_run:
+ print('Dry run, not actually creating maven repo for '
+ + 'desugar configuration.')
+ if options.dry_run_output:
+ shutil.copyfile(
+ desugar_jdk_libs_configuration_jar,
+ os.path.join(options.dry_run_output, jar_version_name))
+ else:
+ utils.upload_file_to_cloud_storage(
+ desugar_jdk_libs_configuration_jar, maven_dst)
+ print('Maven repo root available at: %s' % GetMavenUrl(is_main))
+ # Also archive the jar as non maven destination for Google3
+ jar_destination = GetUploadDestination(
+ version, jar_basename, is_main)
+ utils.upload_file_to_cloud_storage(
+ desugar_jdk_libs_configuration_jar, jar_destination)
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 10b300a..5298435 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -174,7 +174,7 @@
version = GetVersion(
os.path.join(
checkout_dir,
- VERSION_FILE_JDK11 if variant == 'jdk11' else VERSION_FILE_JDK8))
+ VERSION_FILE_JDK11 if options.variant == 'jdk11' else VERSION_FILE_JDK8))
destination = archive.GetVersionDestination(
'gs://', LIBRARY_NAME + '/' + version, is_main)
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 6250c75..de9f0ac 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -383,7 +383,8 @@
move(destination + '.zip', destination)
# Generate the maven zip for the configuration to desugar desugar_jdk_libs.
-def generate_desugar_configuration_maven_zip(out, configuration, implementation):
+def generate_desugar_configuration_maven_zip(
+ out, configuration, implementation, conversions):
with utils.TempDir() as tmp_dir:
version = utils.desugar_configuration_version(configuration)
# Generate the pom file.
@@ -394,7 +395,7 @@
generate_jar_with_desugar_configuration(
configuration,
implementation,
- utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP,
+ conversions,
jar_file)
# Write the maven zip file.
generate_maven_zip(
diff --git a/tools/git_sync_cl_chain.py b/tools/git_sync_cl_chain.py
index 7dc512e..0fd65c6 100755
--- a/tools/git_sync_cl_chain.py
+++ b/tools/git_sync_cl_chain.py
@@ -40,6 +40,9 @@
def ParseOptions(argv):
result = optparse.OptionParser()
+ result.add_option('--bypass-hooks',
+ help='Bypass presubmit hooks',
+ action='store_true')
result.add_option('--delete', '-d',
help='Delete closed branches',
choices=['y', 'n', 'ask'],
@@ -103,7 +106,7 @@
utils.RunCmd(['git', 'checkout', branch.name], quiet=True)
- status = get_status_for_current_branch()
+ status = get_status_for_current_branch(branch)
print('Syncing %s (status: %s)' % (branch.name, status))
pull_for_current_branch(branch, options)
@@ -135,8 +138,10 @@
'Cannot upload branch %s since it comes after a local branch'
% branch.name)
else:
- utils.RunCmd(
- ['git', 'cl', 'upload', '-m', options.message], quiet=True)
+ upload_cmd = ['git', 'cl', 'upload', '-m', options.message]
+ if options.bypass_hooks:
+ upload_cmd.append('--bypass-hooks')
+ utils.RunCmd(upload_cmd, quiet=True)
if get_delete_branches_option(closed_branches, options):
delete_branches(closed_branches)
@@ -169,11 +174,13 @@
answer = sys.stdin.read(1)
return answer.lower() == 'y'
-def get_status_for_current_branch():
+def get_status_for_current_branch(current_branch):
+ if current_branch.name == 'main':
+ return 'main'
return utils.RunCmd(['git', 'cl', 'status', '--field', 'status'], quiet=True)[0].strip()
def is_root_branch(branch, options):
- return branch == options.from_branch or branch.upstream is None
+ return branch.name == options.from_branch or branch.upstream is None
def pull_for_current_branch(branch, options):
if branch.name == 'main' and options.skip_main:
diff --git a/tools/r8_release.py b/tools/r8_release.py
index eab96be..f19c99e 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -377,15 +377,24 @@
'desugar_jdk_libs_configuration.jar')
download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace_lib.jar')
g4_open('METADATA')
- sed(r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev',
- options.version,
- os.path.join(third_party_r8, 'METADATA'))
+ metadata_path = os.path.join(third_party_r8, 'METADATA')
+ match_count = 0
+ version_match_regexp = r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev'
+ for line in open(metadata_path, 'r'):
+ result = re.search(version_match_regexp, line)
+ if result:
+ match_count = match_count + 1
+ if match_count != 3:
+ print(("Could not find the previous -dev release string to replace in " +
+ "METADATA. Expected to find is mentioned 3 times. Please update %s " +
+ "manually and run again with options --google " +
+ "--use-existing-work-branch.") % metadata_path)
+ sys.exit(1)
+ sed(version_match_regexp, options.version, metadata_path)
sed(r'\{ year.*\}',
('{ year: %i month: %i day: %i }'
% (today.year, today.month, today.day)),
- os.path.join(third_party_r8, 'METADATA'))
-
-
+ metadata_path)
subprocess.check_output('chmod u+w *', shell=True)
with utils.ChangedWorkingDirectory(google3_base):
@@ -554,10 +563,14 @@
if not args.use_existing_work_branch:
clients = subprocess.check_output('g4 myclients', shell=True).decode('utf-8')
if ':%s:' % client_name in clients:
- print(("Remove the existing '%s' client before continuing " +
- "(force delete: 'g4 citc -d -f %s'), " +
- "or use option --use-existing-work-branch.") % (client_name, client_name))
- sys.exit(1)
+ if args.delete_work_branch:
+ subprocess.check_call('g4 citc -d -f %s' % client_name, shell=True)
+ else:
+ print(("Remove the existing '%s' client before continuing " +
+ "(force delete: 'g4 citc -d -f %s'), " +
+ "or use either --use-existing-work-branch or " +
+ "--delete-work-branch.") % (client_name, client_name))
+ sys.exit(1)
def extract_version_from_pom(pom_file):
@@ -823,6 +836,10 @@
default=False,
action='store_true',
help='Use existing work branch/CL in aosp/studio/google3')
+ result.add_argument('--delete-work-branch', '--delete_work_branch',
+ default=False,
+ action='store_true',
+ help='Delete CL in google3')
result.add_argument('--no-upload', '--no_upload',
default=False,
action='store_true',
diff --git a/tools/utils.py b/tools/utils.py
index cd41ad0..ecd172c 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -66,6 +66,7 @@
R8LIB_TESTS_DEPS_JAR = R8_TESTS_DEPS_JAR
MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
MAVEN_ZIP_LIB = os.path.join(LIBS, 'r8lib.zip')
+LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP = os.path.join(LIBS, 'library_desugar_conversions_legacy.jar')
LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(LIBS, 'library_desugar_conversions.jar')
DESUGAR_CONFIGURATION = os.path.join(
@@ -78,8 +79,8 @@
'third_party', 'openjdk', 'desugar_jdk_libs_11', 'desugar_jdk_libs.jar')
DESUGAR_CONFIGURATION_MAVEN_ZIP = os.path.join(
LIBS, 'desugar_jdk_libs_configuration.zip')
-DESUGAR_CONFIGURATION_LEGACY_JDK11_MAVEN_ZIP = os.path.join(
- LIBS, 'desugar_jdk_libs_configuration_legacy_jdk11.zip')
+DESUGAR_CONFIGURATION_JDK11_LEGACY_MAVEN_ZIP = os.path.join(
+ LIBS, 'desugar_jdk_libs_configuration_jdk11_legacy.zip')
GENERATED_LICENSE = os.path.join(GENERATED_LICENSE_DIR, 'LICENSE')
RT_JAR = os.path.join(REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
@@ -208,6 +209,8 @@
self._quiet = quiet
def log(self, text):
+ if len(text.strip()) == 0:
+ return
if self._quiet:
if self._has_printed:
sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)