Compiler APIs for global synthetic classes.
Bug: b/208788455
Change-Id: I960be943dfeaf6675fb87261c0468b5368833f9e
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 9884112..7f74668 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
import com.android.tools.r8.utils.DumpInputFlags;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramProvider;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
@@ -82,6 +83,9 @@
public static class Builder extends BaseCompilerCommand.Builder<D8Command, Builder> {
private boolean intermediate = false;
+ private GlobalSyntheticsConsumer globalSyntheticsConsumer = null;
+ private List<GlobalSyntheticsResourceProvider> globalSyntheticsResourceProviders =
+ new ArrayList<>();
private DesugarGraphConsumer desugarGraphConsumer = null;
private StringConsumer desugaredLibraryKeepRuleConsumer = null;
private String synthesizedClassPrefix = "";
@@ -144,6 +148,16 @@
/**
* Indicate if compilation is to intermediate results, i.e., intended for later merging.
*
+ * <p>When compiling to intermediate mode, the compiler will avoid sharing of synthetic items,
+ * and instead annotate them as synthetics for possible later merging. For global synthetics,
+ * the compiler will emit these to a separate consumer (see {@code GlobalSyntheticsConsumer}
+ * with the expectation that a later build step will consume them again as part of a
+ * non-intermediate build (see {@code GlobalSyntheticsResourceProvider}. Synthetic items
+ * typically come from the desugaring of various language features, such as lambdas and default
+ * interface methods. Global synthetics are non-local in that many compilation units may
+ * reference the same synthetic. For example, desugaring records requires a global tag to
+ * distinguish the class of all records.
+ *
* <p>Intermediate mode is implied if compiling results to a "file-per-class-file".
*/
public Builder setIntermediate(boolean value) {
@@ -152,6 +166,43 @@
}
/**
+ * Set a consumer for receiving the global synthetic content for the given compilation.
+ *
+ * <p>Note: this consumer is ignored if the compilation is not an "intermediate mode"
+ * compilation.
+ */
+ public Builder setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalSyntheticsConsumer) {
+ this.globalSyntheticsConsumer = globalSyntheticsConsumer;
+ return self();
+ }
+
+ /** Add global synthetics resource providers. */
+ public Builder addGlobalSyntheticsResourceProviders(
+ GlobalSyntheticsResourceProvider... providers) {
+ return addGlobalSyntheticsResourceProviders(Arrays.asList(providers));
+ }
+
+ /** Add global synthetics resource providers. */
+ public Builder addGlobalSyntheticsResourceProviders(
+ Collection<GlobalSyntheticsResourceProvider> providers) {
+ providers.forEach(globalSyntheticsResourceProviders::add);
+ return self();
+ }
+
+ /** Add global synthetics resource files. */
+ public Builder addGlobalSyntheticsFiles(Path... files) {
+ return addGlobalSyntheticsFiles(Arrays.asList(files));
+ }
+
+ /** Add global synthetics resource files. */
+ public Builder addGlobalSyntheticsFiles(Collection<Path> files) {
+ for (Path file : files) {
+ addGlobalSyntheticsResourceProviders(new GlobalSyntheticsResourceFile(file));
+ }
+ return self();
+ }
+
+ /**
* Set a consumer for receiving the keep rules to use when compiling the desugared library for
* the program being compiled in this compilation.
*
@@ -294,6 +345,11 @@
ImmutableList<ProguardConfigurationRule> mainDexKeepRules =
ProguardConfigurationParser.parse(mainDexRules, factory, getReporter());
+ if (!globalSyntheticsResourceProviders.isEmpty()) {
+ addProgramResourceProvider(
+ new InternalGlobalSyntheticsProgramProvider(globalSyntheticsResourceProviders));
+ }
+
return new D8Command(
getAppBuilder().build(),
getMode(),
@@ -303,6 +359,7 @@
getReporter(),
getDesugaringState(),
intermediate,
+ intermediate ? globalSyntheticsConsumer : null,
isOptimizeMultidexForLinearAlloc(),
getIncludeClassesChecksum(),
getDexClassChecksumFilter(),
@@ -326,6 +383,7 @@
static final String USAGE_MESSAGE = D8CommandParser.USAGE_MESSAGE;
private final boolean intermediate;
+ private final GlobalSyntheticsConsumer globalSyntheticsConsumer;
private final DesugarGraphConsumer desugarGraphConsumer;
private final StringConsumer desugaredLibraryKeepRuleConsumer;
private final DesugaredLibrarySpecification desugaredLibrarySpecification;
@@ -385,6 +443,7 @@
Reporter diagnosticsHandler,
DesugarState enableDesugaring,
boolean intermediate,
+ GlobalSyntheticsConsumer globalSyntheticsConsumer,
boolean optimizeMultidexForLinearAlloc,
boolean encodeChecksum,
BiPredicate<String, Long> dexClassChecksumFilter,
@@ -420,6 +479,7 @@
mapIdProvider,
null);
this.intermediate = intermediate;
+ this.globalSyntheticsConsumer = globalSyntheticsConsumer;
this.desugarGraphConsumer = desugarGraphConsumer;
this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
this.desugaredLibrarySpecification = desugaredLibrarySpecification;
@@ -434,6 +494,7 @@
private D8Command(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
intermediate = false;
+ globalSyntheticsConsumer = null;
desugarGraphConsumer = null;
desugaredLibraryKeepRuleConsumer = null;
desugaredLibrarySpecification = null;
@@ -468,6 +529,7 @@
internal.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(getMinApiLevel()));
internal.intermediate = intermediate;
internal.retainCompileTimeAnnotations = intermediate;
+ internal.setGlobalSyntheticsConsumer(globalSyntheticsConsumer);
internal.desugarGraphConsumer = desugarGraphConsumer;
internal.mainDexKeepRules = mainDexKeepRules;
internal.lineNumberOptimization = LineNumberOptimization.OFF;
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
new file mode 100644
index 0000000..ea8ad11
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
@@ -0,0 +1,31 @@
+// 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;
+
+/**
+ * Consumer receiving the data representing global synthetics for the program.
+ *
+ * <p>Global synthetic information is only produced as part of D8 intermediate builds (e.g., for
+ * incremental compilation.) The global synthetic information represents desugaring content that may
+ * be duplicated among many intermediate-mode builds and will need to be merged to ensure a valid
+ * final program (i.e., a program that does not contain any duplicate definitions).
+ *
+ * <p>The data obtained for global synthetics must be passed to the subsequent compilation unit that
+ * builds a non-intermediate output. That compilation output can then be packaged as a final
+ * application. It is valid to merge just the globals in such a final step. See {@code
+ * GlobalSyntheticsResourceProvider}.
+ */
+@Keep
+public interface GlobalSyntheticsConsumer {
+
+ /**
+ * Callback to receive the data representing the global synthetics for the program.
+ *
+ * <p>The encoding of the global synthetics is compiler internal and may vary between compiler
+ * versions. The data received here is thus only valid as inputs to the same compiler version.
+ *
+ * @param bytes Opaque encoding of the global synthetics for the program.
+ */
+ void accept(byte[] bytes);
+}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java
new file mode 100644
index 0000000..598a074
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java
@@ -0,0 +1,36 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class GlobalSyntheticsResourceFile implements GlobalSyntheticsResourceProvider {
+
+ private final Path file;
+ private final Origin origin;
+
+ public GlobalSyntheticsResourceFile(Path file) {
+ this.file = file;
+ this.origin = new PathOrigin(file);
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ try {
+ return Files.newInputStream(file);
+ } catch (IOException e) {
+ throw new ResourceException(origin, e);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceProvider.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceProvider.java
new file mode 100644
index 0000000..4cfdbb1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceProvider.java
@@ -0,0 +1,23 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import java.io.InputStream;
+
+/**
+ * Interface to provide global synthetic information to the compiler.
+ *
+ * <p>The global synthetic information can only be obtained by consuming it from a previous
+ * compilation unit for the same compiler version. See {@code GlobalSyntheticsConsumer}.
+ */
+@Keep
+public interface GlobalSyntheticsResourceProvider {
+
+ /** Get the origin of the global synthetics resource. */
+ Origin getOrigin();
+
+ /** Get the bytes of the global synthetics resource. */
+ InputStream getByteStream() throws ResourceException;
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 5725955..15bf742 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.androidapi;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
@@ -233,7 +234,8 @@
appView
.appInfo()
.getSyntheticItems()
- .ensureFixedClassFromType(
+ .ensureGlobalClass(
+ () -> new MissingGlobalSyntheticsConsumerDiagnostic("API stubbing"),
kinds -> kinds.API_MODEL_STUB,
libraryClass.getType(),
appView,
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index e7f4ad0..1e62a10 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.debuginfo.DebugRepresentation.DebugRepresentationPredicate;
import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
+import com.android.tools.r8.dex.VirtualFile.FilePerInputClassDistributor;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
import com.android.tools.r8.graph.AppServices;
@@ -51,6 +52,7 @@
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexConsumer;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OriginalSourceFiles;
import com.android.tools.r8.utils.PredicateUtils;
@@ -85,8 +87,10 @@
private final Predicate<DexType> isTypeMissing;
public List<Marker> markers;
public List<DexString> markerStrings;
+ public Set<VirtualFile> globalSyntheticFiles;
public DexIndexedConsumer programConsumer;
+ public InternalGlobalSyntheticsDexConsumer globalsSyntheticsConsumer;
private static class SortAnnotations extends MixedSectionCollection {
@@ -179,20 +183,47 @@
private List<VirtualFile> distribute(ExecutorService executorService)
throws ExecutionException, IOException {
+ Collection<DexProgramClass> classes = appView.appInfo().classes();
+ Collection<DexProgramClass> globalSynthetics = new ArrayList<>();
+ if (appView.options().intermediate && appView.options().hasGlobalSyntheticsConsumer()) {
+ Collection<DexProgramClass> allClasses = classes;
+ classes = new ArrayList<>(allClasses.size());
+ for (DexProgramClass clazz : allClasses) {
+ if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+ globalSynthetics.add(clazz);
+ } else {
+ classes.add(clazz);
+ }
+ }
+ }
+
// Distribute classes into dex files.
VirtualFile.Distributor distributor;
if (options.isGeneratingDexFilePerClassFile()) {
- distributor = new VirtualFile.FilePerInputClassDistributor(this,
- options.getDexFilePerClassFileConsumer().combineSyntheticClassesWithPrimaryClass());
+ distributor =
+ new VirtualFile.FilePerInputClassDistributor(
+ this,
+ classes,
+ options.getDexFilePerClassFileConsumer().combineSyntheticClassesWithPrimaryClass());
} else if (!options.canUseMultidex()
&& options.mainDexKeepRules.isEmpty()
&& appView.appInfo().getMainDexInfo().isEmpty()
&& options.enableMainDexListCheck) {
- distributor = new VirtualFile.MonoDexDistributor(this, options);
+ distributor = new VirtualFile.MonoDexDistributor(this, classes, options);
} else {
- distributor = new VirtualFile.FillFilesDistributor(this, options, executorService);
+ distributor = new VirtualFile.FillFilesDistributor(this, classes, options, executorService);
}
- return distributor.run();
+
+ List<VirtualFile> virtualFiles = distributor.run();
+ if (!globalSynthetics.isEmpty()) {
+ List<VirtualFile> files =
+ new FilePerInputClassDistributor(this, globalSynthetics, false).run();
+ globalSyntheticFiles = new HashSet<>(files);
+ virtualFiles.addAll(globalSyntheticFiles);
+ globalsSyntheticsConsumer =
+ new InternalGlobalSyntheticsDexConsumer(options.getGlobalSyntheticsConsumer());
+ }
+ return virtualFiles;
}
/**
@@ -330,6 +361,9 @@
executorService);
merger.add(timings);
merger.end();
+ if (globalsSyntheticsConsumer != null) {
+ globalsSyntheticsConsumer.finished(options.reporter);
+ }
}
// A consumer can manage the generated keep rules.
@@ -460,7 +494,11 @@
ProgramConsumer consumer;
ByteBufferProvider byteBufferProvider;
- if (programConsumer != null) {
+
+ if (globalSyntheticFiles != null && globalSyntheticFiles.contains(virtualFile)) {
+ consumer = globalsSyntheticsConsumer;
+ byteBufferProvider = globalsSyntheticsConsumer;
+ } else if (programConsumer != null) {
consumer = programConsumer;
byteBufferProvider = programConsumer;
} else if (virtualFile.getPrimaryClassDescriptor() != null) {
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 7d88ac7..827f43bd 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -307,6 +307,7 @@
}
public abstract List<VirtualFile> run() throws ExecutionException, IOException;
+
}
/**
@@ -316,11 +317,15 @@
* may then be distributed in several individual virtual files.
*/
public static class FilePerInputClassDistributor extends Distributor {
+ private final Collection<DexProgramClass> classes;
private final boolean combineSyntheticClassesWithPrimaryClass;
- FilePerInputClassDistributor(ApplicationWriter writer,
+ FilePerInputClassDistributor(
+ ApplicationWriter writer,
+ Collection<DexProgramClass> classes,
boolean combineSyntheticClassesWithPrimaryClass) {
super(writer);
+ this.classes = classes;
this.combineSyntheticClassesWithPrimaryClass = combineSyntheticClassesWithPrimaryClass;
}
@@ -329,7 +334,7 @@
HashMap<DexProgramClass, VirtualFile> files = new HashMap<>();
Collection<DexProgramClass> synthetics = new ArrayList<>();
// Assign dedicated virtual files for all program classes.
- for (DexProgramClass clazz : appView.appInfo().classes()) {
+ for (DexProgramClass clazz : classes) {
// TODO(b/181636450): Simplify this making use of the assumption that synthetics are never
// duplicated.
if (!combineSyntheticClassesWithPrimaryClass
@@ -370,9 +375,11 @@
protected final VirtualFile mainDexFile;
protected final InternalOptions options;
- DistributorBase(ApplicationWriter writer, InternalOptions options) {
+ DistributorBase(
+ ApplicationWriter writer, Collection<DexProgramClass> classes, InternalOptions options) {
super(writer);
this.options = options;
+ this.classes = SetUtils.newIdentityHashSet(classes);
// Create the primary dex file. The distribution will add more if needed.
mainDexFile = new VirtualFile(0, writer.appView, writer.namingLens);
@@ -380,7 +387,6 @@
virtualFiles.add(mainDexFile);
addMarkers(mainDexFile);
- classes = SetUtils.newIdentityHashSet(appView.appInfo().classes());
originalNames =
computeOriginalNameMapping(
classes, appView.graphLens(), appView.appInfo().app().getProguardMap());
@@ -513,9 +519,12 @@
public static class FillFilesDistributor extends DistributorBase {
private final ExecutorService executorService;
- FillFilesDistributor(ApplicationWriter writer, InternalOptions options,
+ FillFilesDistributor(
+ ApplicationWriter writer,
+ Collection<DexProgramClass> classes,
+ InternalOptions options,
ExecutorService executorService) {
- super(writer, options);
+ super(writer, classes, options);
this.executorService = executorService;
}
@@ -576,8 +585,9 @@
}
public static class MonoDexDistributor extends DistributorBase {
- MonoDexDistributor(ApplicationWriter writer, InternalOptions options) {
- super(writer, options);
+ MonoDexDistributor(
+ ApplicationWriter writer, Collection<DexProgramClass> classes, InternalOptions options) {
+ super(writer, classes, options);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/errors/MissingGlobalSyntheticsConsumerDiagnostic.java b/src/main/java/com/android/tools/r8/errors/MissingGlobalSyntheticsConsumerDiagnostic.java
new file mode 100644
index 0000000..ff99d05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/MissingGlobalSyntheticsConsumerDiagnostic.java
@@ -0,0 +1,34 @@
+// 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.Position;
+
+public class MissingGlobalSyntheticsConsumerDiagnostic implements DesugarDiagnostic {
+
+ private final String generatingReason;
+
+ public MissingGlobalSyntheticsConsumerDiagnostic(String generatingReason) {
+ this.generatingReason = generatingReason;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return Origin.unknown();
+ }
+
+ @Override
+ public Position getPosition() {
+ return Position.UNKNOWN;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return "Invalid build configuration. "
+ + "Attempt to create a global synthetic for '"
+ + generatingReason
+ + "' without a global-synthetics consumer.";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 72884e9..810874b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -337,14 +337,6 @@
return isDoubleType() || isLongType();
}
- public boolean isSynthesizedTypeAllowedDuplication() {
- // If we are desugaring Records, then the r8Record type is mapped back to java.lang.Record, and
- // java.lang.Record can be duplicated.
- // If we are not desugaring Records, then the r8Record type can be duplicated instead.
- return descriptor.toString().equals(DexItemFactory.recordDescriptorString)
- || descriptor.toString().equals(DexItemFactory.recordTagDescriptorString);
- }
-
public boolean isLegacySynthesizedTypeAllowedDuplication() {
return oldSynthesizedName(toSourceString());
}
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 a08a5ba..6a84f09 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
@@ -22,6 +22,7 @@
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
@@ -373,7 +374,8 @@
checkRecordTagNotPresent(factory);
appView
.getSyntheticItems()
- .ensureFixedClassFromType(
+ .ensureGlobalClass(
+ () -> new MissingGlobalSyntheticsConsumerDiagnostic("Record desugaring"),
kinds -> kinds.RECORD_TAG,
factory.recordType,
appView,
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 11f0a2c..7f8d075 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -48,6 +48,7 @@
import com.android.tools.r8.utils.AsmUtils;
import com.android.tools.r8.utils.ComparatorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsCfConsumer;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OriginalSourceFiles;
import com.android.tools.r8.utils.PredicateUtils;
@@ -57,6 +58,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
@@ -149,36 +151,67 @@
sourceFileEnvironment = ApplicationWriter.createSourceFileEnvironment(proguardMapId);
}
LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
- for (DexProgramClass clazz : application.classes()) {
- assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
- try {
- writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
- } catch (ClassTooLargeException e) {
- throw appView
- .options()
- .reporter
- .fatalError(
- new ConstantPoolOverflowDiagnostic(
- clazz.getOrigin(),
- Reference.classFromBinaryName(e.getClassName()),
- e.getConstantPoolCount()));
- } catch (MethodTooLargeException e) {
- throw appView
- .options()
- .reporter
- .fatalError(
- new CodeSizeOverflowDiagnostic(
- clazz.getOrigin(),
- Reference.methodFromDescriptor(
- Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
- e.getMethodName(),
- e.getDescriptor()),
- e.getCodeSize()));
+ Collection<DexProgramClass> classes = application.classes();
+ Collection<DexProgramClass> globalSyntheticClasses = new ArrayList<>();
+ if (options.intermediate && options.hasGlobalSyntheticsConsumer()) {
+ Collection<DexProgramClass> allClasses = classes;
+ classes = new ArrayList<>(allClasses.size());
+ for (DexProgramClass clazz : allClasses) {
+ if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+ globalSyntheticClasses.add(clazz);
+ } else {
+ classes.add(clazz);
+ }
}
}
+ for (DexProgramClass clazz : classes) {
+ writeClassCatchingErrors(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
+ }
+ if (!globalSyntheticClasses.isEmpty()) {
+ InternalGlobalSyntheticsCfConsumer globalsConsumer =
+ new InternalGlobalSyntheticsCfConsumer(options.getGlobalSyntheticsConsumer());
+ for (DexProgramClass clazz : globalSyntheticClasses) {
+ writeClassCatchingErrors(
+ clazz, globalsConsumer, rewriter, markerString, sourceFileEnvironment);
+ }
+ globalsConsumer.finished(options.reporter);
+ }
ApplicationWriter.supplyAdditionalConsumers(application, appView, namingLens, options);
}
+ private void writeClassCatchingErrors(
+ DexProgramClass clazz,
+ ClassFileConsumer consumer,
+ LensCodeRewriterUtils rewriter,
+ Optional<String> markerString,
+ SourceFileEnvironment sourceFileEnvironment) {
+ assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
+ try {
+ writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
+ } catch (ClassTooLargeException e) {
+ throw appView
+ .options()
+ .reporter
+ .fatalError(
+ new ConstantPoolOverflowDiagnostic(
+ clazz.getOrigin(),
+ Reference.classFromBinaryName(e.getClassName()),
+ e.getConstantPoolCount()));
+ } catch (MethodTooLargeException e) {
+ throw appView
+ .options()
+ .reporter
+ .fatalError(
+ new CodeSizeOverflowDiagnostic(
+ clazz.getOrigin(),
+ Reference.methodFromDescriptor(
+ Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
+ e.getMethodName(),
+ e.getDescriptor()),
+ e.getCodeSize()));
+ }
+ }
+
private void writeClass(
DexProgramClass clazz,
ClassFileConsumer consumer,
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 48ff52a..cee58c4 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
@@ -278,6 +279,32 @@
return isSyntheticClass(clazz.type);
}
+ public boolean isGlobalSyntheticClass(DexProgramClass clazz) {
+ SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.type);
+ if (definition != null) {
+ return definition.getKind().isGlobal();
+ }
+ return isGlobalReferences(committed.getNonLegacyClasses().get(clazz.type));
+ }
+
+ private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
+ if (references == null) {
+ return false;
+ }
+ if (references.size() == 1 && references.get(0).getKind().isGlobal()) {
+ return true;
+ }
+ assert verifyNoGlobals(references);
+ return false;
+ }
+
+ private static boolean verifyNoGlobals(List<SyntheticProgramClassReference> references) {
+ for (SyntheticProgramClassReference reference : references) {
+ assert !reference.getKind().isGlobal();
+ }
+ return true;
+ }
+
public boolean isSyntheticOfKind(DexType type, SyntheticKindSelector kindSelector) {
SyntheticKind kind = kindSelector.select(naming);
return pending.containsTypeOfKind(type, kind) || committed.containsTypeOfKind(type, kind);
@@ -769,14 +796,20 @@
}
}
- public DexProgramClass ensureFixedClassFromType(
+ public DexProgramClass ensureGlobalClass(
+ Supplier<MissingGlobalSyntheticsConsumerDiagnostic> diagnosticSupplier,
SyntheticKindSelector kindSelector,
- DexType contextType,
+ DexType globalType,
AppView<?> appView,
Consumer<SyntheticProgramClassBuilder> fn,
Consumer<DexProgramClass> onCreationConsumer) {
SyntheticKind kind = kindSelector.select(naming);
- SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
+ 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);
}
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 20bde1a..ef26890 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -31,6 +31,7 @@
public static final String KT_EXTENSION = ".kt";
public static final String MODULE_INFO_CLASS = "module-info.class";
public static final String MODULES_PREFIX = "/modules";
+ public static final String GLOBAL_SYNTHETIC_EXTENSION = ".global";
public static final boolean isAndroid =
System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik");
diff --git a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
new file mode 100644
index 0000000..3945a6e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
@@ -0,0 +1,125 @@
+// 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.utils;
+
+import static com.android.tools.r8.utils.FileUtils.GLOBAL_SYNTHETIC_EXTENSION;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.Version;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public abstract class InternalGlobalSyntheticsProgramConsumer {
+
+ public static final String COMPILER_INFO_ENTRY_NAME = "compilerinfo";
+ public static final String OUTPUT_KIND_ENTRY_NAME = "kind";
+
+ private final GlobalSyntheticsConsumer consumer;
+ private final List<Pair<String, byte[]>> content = new ArrayList<>();
+
+ public InternalGlobalSyntheticsProgramConsumer(GlobalSyntheticsConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ public abstract Kind getKind();
+
+ synchronized void addGlobalSynthetic(String descriptor, byte[] data) {
+ add(getGlobalSyntheticFileName(descriptor), data);
+ }
+
+ private void add(String entryName, byte[] data) {
+ content.add(new Pair<>(entryName, data));
+ }
+
+ public void finished(DiagnosticsHandler handler) {
+ // Add meta information.
+ add(COMPILER_INFO_ENTRY_NAME, Version.getVersionString().getBytes(StandardCharsets.UTF_8));
+ add(OUTPUT_KIND_ENTRY_NAME, getKind().toString().getBytes(StandardCharsets.UTF_8));
+
+ // Size estimate to avoid reallocation of the byte output array.
+ final int zipHeaderOverhead = 500;
+ final int zipEntryOverhead = 200;
+ int estimatedZipSize =
+ zipHeaderOverhead
+ + ListUtils.fold(
+ content,
+ 0,
+ (acc, pair) ->
+ acc + pair.getFirst().length() + pair.getSecond().length + zipEntryOverhead);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(estimatedZipSize);
+ try (ZipOutputStream stream = new ZipOutputStream(baos)) {
+ for (Pair<String, byte[]> pair : content) {
+ ZipUtils.writeToZipStream(stream, pair.getFirst(), pair.getSecond(), ZipEntry.STORED);
+ // Clear out the bytes to avoid three copies when converting the boas.
+ pair.setSecond(null);
+ }
+ } catch (IOException e) {
+ handler.error(new ExceptionDiagnostic(e));
+ }
+ byte[] bytes = baos.toByteArray();
+ consumer.accept(bytes);
+ }
+
+ private static String getGlobalSyntheticFileName(String descriptor) {
+ assert descriptor != null && DescriptorUtils.isClassDescriptor(descriptor);
+ return DescriptorUtils.getClassBinaryNameFromDescriptor(descriptor)
+ + GLOBAL_SYNTHETIC_EXTENSION;
+ }
+
+ public static class InternalGlobalSyntheticsDexConsumer
+ extends InternalGlobalSyntheticsProgramConsumer implements DexFilePerClassFileConsumer {
+
+ public InternalGlobalSyntheticsDexConsumer(GlobalSyntheticsConsumer consumer) {
+ super(consumer);
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.DEX;
+ }
+
+ @Override
+ public void accept(
+ String primaryClassDescriptor,
+ ByteDataView data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ addGlobalSynthetic(primaryClassDescriptor, data.copyByteData());
+ }
+
+ @Override
+ public boolean combineSyntheticClassesWithPrimaryClass() {
+ return false;
+ }
+ }
+
+ public static class InternalGlobalSyntheticsCfConsumer
+ extends InternalGlobalSyntheticsProgramConsumer implements ClassFileConsumer {
+
+ public InternalGlobalSyntheticsCfConsumer(GlobalSyntheticsConsumer consumer) {
+ super(consumer);
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.CF;
+ }
+
+ @Override
+ public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+ addGlobalSynthetic(descriptor, data.copyByteData());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramProvider.java b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramProvider.java
new file mode 100644
index 0000000..6b2d828
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramProvider.java
@@ -0,0 +1,100 @@
+// 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.utils;
+
+import com.android.tools.r8.GlobalSyntheticsResourceProvider;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class InternalGlobalSyntheticsProgramProvider implements ProgramResourceProvider {
+
+ private final List<GlobalSyntheticsResourceProvider> providers;
+ private List<ProgramResource> resources = null;
+
+ public InternalGlobalSyntheticsProgramProvider(List<GlobalSyntheticsResourceProvider> providers) {
+ this.providers = providers;
+ }
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ if (resources == null) {
+ ensureResources();
+ }
+ return resources;
+ }
+
+ private synchronized void ensureResources() throws ResourceException {
+ if (resources != null) {
+ return;
+ }
+ List<ProgramResource> resources = new ArrayList<>();
+ Set<String> seen = new HashSet<>();
+ for (GlobalSyntheticsResourceProvider provider : providers) {
+ List<Function<Kind, ProgramResource>> delayedResouces = new ArrayList<>();
+ Kind providerKind = null;
+ try (ZipInputStream stream = new ZipInputStream(provider.getByteStream())) {
+ ZipEntry entry;
+ while (null != (entry = stream.getNextEntry())) {
+ String name = entry.getName();
+ if (name.equals(InternalGlobalSyntheticsProgramConsumer.OUTPUT_KIND_ENTRY_NAME)) {
+ providerKind =
+ Kind.valueOf(new String(ByteStreams.toByteArray(stream), StandardCharsets.UTF_8));
+ } else if (name.equals(
+ InternalGlobalSyntheticsProgramConsumer.COMPILER_INFO_ENTRY_NAME)) {
+ String version = new String(ByteStreams.toByteArray(stream), StandardCharsets.UTF_8);
+ if (!Version.getVersionString().equals(version)) {
+ throw new ResourceException(
+ provider.getOrigin(),
+ "Outdated or inconsistent global synthetics information."
+ + "\nGlobal synthetics information version: "
+ + version
+ + "\nCompiler version: "
+ + Version.getVersionString());
+ }
+ } else if (name.endsWith(FileUtils.GLOBAL_SYNTHETIC_EXTENSION) && seen.add(name)) {
+ ArchiveEntryOrigin origin = new ArchiveEntryOrigin(name, provider.getOrigin());
+ String descriptor = guessTypeDescriptor(name);
+ byte[] bytes = ByteStreams.toByteArray(stream);
+ Set<String> descriptors = Collections.singleton(descriptor);
+ delayedResouces.add(
+ kind -> OneShotByteResource.create(kind, origin, bytes, descriptors));
+ }
+ }
+ } catch (IOException e) {
+ throw new ResourceException(provider.getOrigin(), e);
+ }
+ if (providerKind == null) {
+ throw new ResourceException(
+ provider.getOrigin(),
+ "Invalid global synthetics provider does not specify its content kind.");
+ }
+ for (Function<Kind, ProgramResource> fn : delayedResouces) {
+ resources.add(fn.apply(providerKind));
+ }
+ }
+ this.resources = resources;
+ }
+
+ private String guessTypeDescriptor(String name) {
+ String noExt = name.substring(0, name.length() - FileUtils.GLOBAL_SYNTHETIC_EXTENSION.length());
+ String classExt = noExt + FileUtils.CLASS_EXTENSION;
+ return DescriptorUtils.guessTypeDescriptor(classExt);
+ }
+}
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 dc0a966..c11f715 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
import com.android.tools.r8.MapIdProvider;
import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.SourceFileProvider;
@@ -164,6 +165,8 @@
// TODO(zerny): Make this private-final once we have full program-consumer support.
public ProgramConsumer programConsumer = null;
+ private GlobalSyntheticsConsumer globalSyntheticsConsumer = null;
+
public DataResourceConsumer dataResourceConsumer;
public FeatureSplitConfiguration featureSplitConfiguration;
public StartupConfiguration startupConfiguration;
@@ -444,6 +447,18 @@
throw new UnsupportedOperationException("Cannot find internal output mode.");
}
+ public boolean hasGlobalSyntheticsConsumer() {
+ return globalSyntheticsConsumer != null;
+ }
+
+ public GlobalSyntheticsConsumer getGlobalSyntheticsConsumer() {
+ return globalSyntheticsConsumer;
+ }
+
+ public void setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalSyntheticsConsumer) {
+ this.globalSyntheticsConsumer = globalSyntheticsConsumer;
+ }
+
public boolean isAndroidPlatform() {
return minApiLevel == ANDROID_PLATFORM;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 8c3e592..a5bbfcb 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -79,8 +79,7 @@
private static DexProgramClass mergeClasses(
Reporter reporter, DexProgramClass a, DexProgramClass b) {
- if (a.type.isLegacySynthesizedTypeAllowedDuplication()
- || a.type.isSynthesizedTypeAllowedDuplication()) {
+ if (a.type.isLegacySynthesizedTypeAllowedDuplication()) {
assert assertEqualClasses(a, b);
return a;
}
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index dd19c1e..37f2a5f 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -29,6 +29,11 @@
JDK9("jdk9", 53),
JDK10("jdk10", 54),
JDK11("jdk11", 55),
+ JDK12("jdk12", 56),
+ JDK13("jdk13", 57),
+ JDK14("jdk14", 58),
+ JDK15("jdk15", 59),
+ JDK16("jdk16", 60),
JDK17("jdk17", 61),
JDK18("jdk18", 62),
;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 5ec9cbc..501370c 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
+import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
import com.android.tools.r8.compilerapi.mockdata.MockClass;
@@ -39,7 +40,7 @@
DesugarDependenciesTest.ApiTest.class);
private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
- ImmutableList.of();
+ ImmutableList.of(GlobalSyntheticsTest.ApiTest.class);
private final TemporaryFolder temp;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java b/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java
new file mode 100644
index 0000000..edc42c2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.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.compilerapi.globalsynthetics;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.GlobalSyntheticsResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class GlobalSyntheticsTest extends CompilerApiTestRunner {
+
+ public GlobalSyntheticsTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void testGlobalSynthetics() throws Exception {
+ new ApiTest(ApiTest.PARAMETERS).run();
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void run() throws Exception {
+ GlobalSyntheticsResourceProvider provider =
+ new GlobalSyntheticsResourceProvider() {
+ @Override
+ public Origin getOrigin() {
+ return Origin.unknown();
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ throw new IllegalStateException();
+ }
+ };
+ List<GlobalSyntheticsResourceProvider> providers = new ArrayList<>();
+ // Don't actually add the provider as we don't have any bytes to return.
+ if (false) {
+ providers.add(provider);
+ }
+ D8.run(
+ D8Command.builder()
+ .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setIntermediate(true)
+ .addGlobalSyntheticsFiles()
+ .addGlobalSyntheticsFiles(new ArrayList<>())
+ .addGlobalSyntheticsResourceProviders()
+ .addGlobalSyntheticsResourceProviders(providers)
+ .setGlobalSyntheticsConsumer(
+ new GlobalSyntheticsConsumer() {
+ @Override
+ public void accept(byte[] bytes) {
+ // Nothing is actually received here as MockClass does not give rise to
+ // globals.
+ }
+ })
+ .build());
+ }
+
+ @Test
+ public void testGlobalSynthetics() throws Exception {
+ run();
+ }
+ }
+}
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 8401196..cf3c955 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
@@ -4,15 +4,25 @@
package com.android.tools.r8.desugar.records;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsConsumerAndProvider;
import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -34,66 +44,93 @@
StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
private final TestParameters parameters;
- private final boolean intermediate;
- public RecordMergeTest(TestParameters parameters, boolean intermediate) {
+ public RecordMergeTest(TestParameters parameters) {
this.parameters = parameters;
- this.intermediate = intermediate;
}
- @Parameterized.Parameters(name = "{0}, intermediate: {1}")
- public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
- return buildParameters(
- getTestParameters()
- .withCustomRuntime(CfRuntime.getCheckedInJdk17())
- .withDexRuntimes()
- .withAllApiLevelsAlsoForCf()
- .build(),
- BooleanUtils.values());
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Test
+ public void testFailureWithoutGlobalSyntheticsConsumer() throws Exception {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA_1)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .setIntermediate(true)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(
+ diagnosticType(MissingGlobalSyntheticsConsumerDiagnostic.class))));
}
@Test
public void testMergeDesugaredInputs() throws Exception {
+ GlobalSyntheticsConsumerAndProvider globals1 = new GlobalSyntheticsConsumerAndProvider();
Path output1 =
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA_1)
.setMinApi(parameters.getApiLevel())
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .setIntermediate(intermediate)
+ .setIntermediate(true)
+ .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1))
.compile()
+ .inspect(this::assertDoesNotHaveRecordTag)
.writeToZip();
+
+ GlobalSyntheticsConsumerAndProvider globals2 = new GlobalSyntheticsConsumerAndProvider();
Path output2 =
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA_2)
.setMinApi(parameters.getApiLevel())
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .setIntermediate(intermediate)
+ .setIntermediate(true)
+ .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals2))
.compile()
+ .inspect(this::assertDoesNotHaveRecordTag)
.writeToZip();
+
+ assertTrue(globals1.hasBytes());
+ assertTrue(globals2.hasBytes());
+
D8TestCompileResult result =
testForD8(parameters.getBackend())
.addProgramFiles(output1, output2)
+ .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1, globals2))
.setMinApi(parameters.getApiLevel())
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile();
+ .compile()
+ .inspect(this::assertHasRecordTag);
+
result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
}
@Test
public void testMergeDesugaredAndNonDesugaredInputs() throws Exception {
+ GlobalSyntheticsConsumerAndProvider globals1 = new GlobalSyntheticsConsumerAndProvider();
Path output1 =
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA_1)
.setMinApi(parameters.getApiLevel())
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .setIntermediate(intermediate)
+ .setIntermediate(true)
+ .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1))
.compile()
.writeToZip();
+
D8TestCompileResult result =
testForD8(parameters.getBackend())
.addProgramFiles(output1)
+ .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1))
.addProgramClassFileData(PROGRAM_DATA_2)
.setMinApi(parameters.getApiLevel())
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
@@ -101,4 +138,48 @@
result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
}
+
+ @Test
+ public void testMergeNonIntermediates() throws Exception {
+ Path output1 =
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA_1)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .inspect(this::assertHasRecordTag)
+ .writeToZip();
+
+ Path output2 =
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA_2)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .inspect(this::assertHasRecordTag)
+ .writeToZip();
+
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8(parameters.getBackend())
+ .addProgramFiles(output1, output2)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(diagnosticType(DuplicateTypesDiagnostic.class))));
+ }
+
+ private void assertHasRecordTag(CodeInspector inspector) {
+ // Note: this should be asserting on record tag.
+ assertThat(inspector.clazz("java.lang.Record"), isPresent());
+ }
+
+ private void assertDoesNotHaveRecordTag(CodeInspector inspector) {
+ // Note: this should be asserting on record tag.
+ assertThat(inspector.clazz("java.lang.Record"), isAbsent());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index 56dab1e..0a8a585 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -4,16 +4,20 @@
package com.android.tools.r8.desugar.records;
+import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsConsumerAndProvider;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import com.android.tools.r8.utils.StringUtils;
import java.nio.file.Path;
import java.util.List;
-import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -35,23 +39,32 @@
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
+ // TODO(b/174431251): Remove once jdk14 or above is added to default parameters.
return buildParameters(
getTestParameters()
.withCustomRuntime(CfRuntime.getCheckedInJdk17())
- .withDexRuntimes()
+ .withAllRuntimes()
.withAllApiLevelsAlsoForCf()
.build());
}
+ private boolean isCfWithNativeRecordSupport() {
+ return parameters.isCfRuntime()
+ && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK14)
+ && parameters.getApiLevel().equals(AndroidApiLevel.B);
+ }
+
@Test
- public void testD8AndJvm() throws Exception {
- if (parameters.isCfRuntime()) {
- testForJvm()
- .addProgramClassFileData(PROGRAM_DATA)
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT);
- }
+ public void testReference() throws Exception {
+ assumeTrue(isCfWithNativeRecordSupport());
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
.setMinApi(parameters.getApiLevel())
@@ -66,10 +79,12 @@
@Test
public void testD8Intermediate() throws Exception {
- Assume.assumeTrue(parameters.isDexRuntime());
- Path path = compileIntermediate();
+ assumeTrue(parameters.isDexRuntime());
+ GlobalSyntheticsConsumerAndProvider globals = new GlobalSyntheticsConsumerAndProvider();
+ Path path = compileIntermediate(globals);
testForD8()
.addProgramFiles(path)
+ .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals))
.setMinApi(parameters.getApiLevel())
.setIncludeClassesChecksum(true)
.run(parameters.getRuntime(), MAIN_TYPE)
@@ -78,11 +93,13 @@
@Test
public void testD8IntermediateNoDesugaringInStep2() throws Exception {
- Assume.assumeTrue(parameters.isDexRuntime());
- Path path = compileIntermediate();
+ assumeTrue(parameters.isDexRuntime());
+ GlobalSyntheticsConsumerAndProvider globals = new GlobalSyntheticsConsumerAndProvider();
+ Path path = compileIntermediate(globals);
// In Android Studio they disable desugaring at this point to improve build speed.
testForD8()
.addProgramFiles(path)
+ .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals))
.setMinApi(parameters.getApiLevel())
.setIncludeClassesChecksum(true)
.disableDesugaring()
@@ -90,19 +107,22 @@
.assertSuccessWithOutput(EXPECTED_RESULT);
}
- private Path compileIntermediate() throws Exception {
+ private Path compileIntermediate(GlobalSyntheticsConsumer globalSyntheticsConsumer)
+ throws Exception {
return testForD8(Backend.DEX)
.addProgramClassFileData(PROGRAM_DATA)
.setMinApi(parameters.getApiLevel())
.addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
.setIntermediate(true)
.setIncludeClassesChecksum(true)
+ .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globalSyntheticsConsumer))
.compile()
.writeToZip();
}
@Test
public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isCfWithNativeRecordSupport());
R8FullTestBuilder builder =
testForR8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
@@ -129,6 +149,7 @@
@Test
public void testR8NoMinification() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isCfWithNativeRecordSupport());
R8FullTestBuilder builder =
testForR8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java
new file mode 100644
index 0000000..57f5622
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java
@@ -0,0 +1,41 @@
+// 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.synthesis.globals;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.GlobalSyntheticsResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.origin.Origin;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+public class GlobalSyntheticsConsumerAndProvider
+ implements GlobalSyntheticsConsumer, GlobalSyntheticsResourceProvider {
+
+ private byte[] bytes;
+
+ @Override
+ public void accept(byte[] bytes) {
+ assertNull(this.bytes);
+ assertNotNull(bytes);
+ this.bytes = bytes;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return Origin.unknown();
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ return new ByteArrayInputStream(bytes);
+ }
+
+ public boolean hasBytes() {
+ return true;
+ }
+}