Merge commit '151b9a12b0b81a300c9801b567e0424ae61b55a4' into dev-release
diff --git a/.gitignore b/.gitignore
index bd447f4..b3f67f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,8 +94,6 @@
third_party/gradle/gradle
third_party/gradle/gradle.tar.gz
third_party/internal/*
-third_party/internal-apps/youtube_15_33
-third_party/internal-apps/youtube_15_33.tar.gz
third_party/iosched_2019
third_party/iosched_2019.tar.gz
third_party/jacoco/0.8.2/*
@@ -250,8 +248,6 @@
third_party/tachiyomi
third_party/tachiyomi.tar.gz
third_party/youtube/*
-third_party/youtube-developer/20200415
-third_party/youtube-developer/20200415.tar.gz
tmp/
tools/*.pyc
tools/__pycache__
diff --git a/build.gradle b/build.gradle
index 9b2772c..5d4868a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -460,7 +460,6 @@
"proto",
"protobuf-lite",
"retrace_internal",
- "youtube/youtube.android_15.33",
"youtube/youtube.android_16.20",
"youtube/youtube.android_17.19"
],
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index b2e0f6d..7e93145 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -54,6 +54,7 @@
private final DumpInputFlags dumpInputFlags;
private final MapIdProvider mapIdProvider;
private final SourceFileProvider sourceFileProvider;
+ private final boolean isAndroidPlatformBuild;
BaseCompilerCommand(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
@@ -72,6 +73,7 @@
dumpInputFlags = DumpInputFlags.noDump();
mapIdProvider = null;
sourceFileProvider = null;
+ isAndroidPlatformBuild = false;
}
BaseCompilerCommand(
@@ -90,7 +92,8 @@
int threadCount,
DumpInputFlags dumpInputFlags,
MapIdProvider mapIdProvider,
- SourceFileProvider sourceFileProvider) {
+ SourceFileProvider sourceFileProvider,
+ boolean isAndroidPlatformBuild) {
super(app);
assert minApiLevel > 0;
assert mode != null;
@@ -109,6 +112,7 @@
this.dumpInputFlags = dumpInputFlags;
this.mapIdProvider = mapIdProvider;
this.sourceFileProvider = sourceFileProvider;
+ this.isAndroidPlatformBuild = isAndroidPlatformBuild;
}
/**
@@ -131,6 +135,9 @@
.setOptimizeMultidexForLinearAlloc(isOptimizeMultidexForLinearAlloc())
.setThreadCount(getThreadCount())
.setDesugarState(getDesugarState());
+ if (getAndroidPlatformBuild()) {
+ builder.setAndroidPlatformBuild(true);
+ }
}
/**
@@ -197,6 +204,10 @@
return threadCount;
}
+ public boolean getAndroidPlatformBuild() {
+ return isAndroidPlatformBuild;
+ }
+
DumpInputFlags getDumpInputFlags() {
return dumpInputFlags;
}
@@ -237,6 +248,7 @@
private DumpInputFlags dumpInputFlags = DumpInputFlags.noDump();
private MapIdProvider mapIdProvider = null;
private SourceFileProvider sourceFileProvider = null;
+ private boolean isAndroidPlatformBuild = false;
abstract CompilationMode defaultCompilationMode();
@@ -635,6 +647,23 @@
return self();
}
+ /**
+ * Configure the present build as a "Android platform build".
+ *
+ * <p>A platform build, is a build where the runtime "bootclasspath" is known at compile time.
+ * In other words, the specified <i>min-api</i> is also known to be the <i>max-api</i>. In this
+ * mode the compiler will disable various features that provide support for newer runtimes as
+ * well as disable workarounds for older runtimes.
+ */
+ public B setAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+ this.isAndroidPlatformBuild = isAndroidPlatformBuild;
+ return self();
+ }
+
+ public boolean getAndroidPlatformBuild() {
+ return isAndroidPlatformBuild;
+ }
+
B dumpInputToFile(Path file) {
dumpInputFlags = DumpInputFlags.dumpToFile(file);
return self();
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 1495071..203b1d6 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -425,6 +425,7 @@
getMapIdProvider(),
proguardMapConsumer,
enableMissingLibraryApiModeling,
+ getAndroidPlatformBuild(),
factory);
}
}
@@ -516,6 +517,7 @@
MapIdProvider mapIdProvider,
StringConsumer proguardMapConsumer,
boolean enableMissingLibraryApiModeling,
+ boolean isAndroidPlatformBuild,
DexItemFactory factory) {
super(
inputApp,
@@ -533,7 +535,8 @@
threadCount,
dumpInputFlags,
mapIdProvider,
- null);
+ null,
+ isAndroidPlatformBuild);
this.intermediate = intermediate;
this.globalSyntheticsConsumer = globalSyntheticsConsumer;
this.desugarGraphConsumer = desugarGraphConsumer;
@@ -654,6 +657,8 @@
horizontalClassMergerOptions.disable();
}
+ internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
+
internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
internal.dumpOptions = dumpOptions();
@@ -667,6 +672,7 @@
.setIntermediate(intermediate)
.setDesugaredLibraryConfiguration(desugaredLibrarySpecification)
.setMainDexKeepRules(mainDexKeepRules)
+ .setEnableMissingLibraryApiModeling(enableMissingLibraryApiModeling)
.build();
}
}
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
deleted file mode 100644
index 43b8e9a..0000000
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8;
-
-import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-
-import com.android.tools.r8.DexIndexedConsumer.DirectoryConsumer;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.dex.ApplicationWriter;
-import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.LazyLoadedDexApplication;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
-import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FeatureClassMapping;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.DesugarState;
-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.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-@Keep
-public final class DexSplitterHelper {
-
- public static void run(
- D8Command command, FeatureClassMapping featureClassMapping, String output, String proguardMap)
- throws CompilationFailedException {
- ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
- try {
- ExceptionUtils.withCompilationHandler(
- command.getReporter(),
- () -> run(command, featureClassMapping, output, proguardMap, executor));
- } finally {
- executor.shutdown();
- }
- }
-
- public static void run(
- D8Command command,
- FeatureClassMapping featureClassMapping,
- String output,
- String proguardMap,
- ExecutorService executor)
- throws IOException {
- InternalOptions options = command.getInternalOptions();
- options.desugarState = DesugarState.OFF;
- options.enableMainDexListCheck = false;
- options.ignoreMainDexMissingClasses = true;
- options.minimalMainDex = false;
- assert !options.isMinifying();
- options.inlinerOptions().enableInlining = false;
- options.outline.enabled = false;
-
- try {
- Timing timing = new Timing("DexSplitter");
- ApplicationReader applicationReader =
- new ApplicationReader(command.getInputApp(), options, timing);
- DexApplication app = applicationReader.read(executor);
- MainDexInfo mainDexInfo = applicationReader.readMainDexClasses(app);
-
- List<Marker> markers = app.dexItemFactory.extractMarkers();
-
- ClassNameMapper mapper = null;
- if (proguardMap != null) {
- mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap));
- }
- Map<String, LazyLoadedDexApplication.Builder> applications =
- getDistribution(app, featureClassMapping, mapper);
- for (Entry<String, LazyLoadedDexApplication.Builder> entry : applications.entrySet()) {
- String feature = entry.getKey();
- timing.begin("Feature " + feature);
- DexApplication featureApp = entry.getValue().build();
- assert !options.hasMethodsFilter();
-
- // If this is the base, we add the main dex list.
- AppInfo appInfo =
- feature.equals(featureClassMapping.getBaseName())
- ? AppInfo.createInitialAppInfo(
- featureApp, GlobalSyntheticsStrategy.forNonSynthesizing(), mainDexInfo)
- : AppInfo.createInitialAppInfo(
- featureApp, GlobalSyntheticsStrategy.forNonSynthesizing());
- AppView<AppInfo> appView = AppView.createForD8(appInfo);
-
- // Run d8 optimize to ensure jumbo strings are handled.
- D8.optimize(appView, options, timing, executor);
-
- // We create a specific consumer for each split.
- Path outputDir = Paths.get(output).resolve(entry.getKey());
- if (!Files.exists(outputDir)) {
- Files.createDirectory(outputDir);
- }
- DexIndexedConsumer consumer = new DirectoryConsumer(outputDir);
-
- try {
- new ApplicationWriter(
- appView,
- markers,
- consumer)
- .write(executor);
- options.printWarnings();
- } finally {
- consumer.finished(options.reporter);
- }
- timing.end();
- }
- } catch (ExecutionException e) {
- throw unwrapExecutionException(e);
- } catch (FeatureMappingException e) {
- options.reporter.error(e.getMessage());
- } finally {
- options.signalFinishedToConsumers();
- }
- }
-
- private static Map<String, LazyLoadedDexApplication.Builder> getDistribution(
- DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper)
- throws FeatureMappingException {
- Map<String, LazyLoadedDexApplication.Builder> applications = new HashMap<>();
- for (DexProgramClass clazz : app.classes()) {
- String clazzName =
- mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString();
- String feature = featureClassMapping.featureForClass(clazzName);
- LazyLoadedDexApplication.Builder featureApplication = applications.get(feature);
- if (featureApplication == null) {
- featureApplication = DexApplication.builder(app.options, app.timing);
- applications.put(feature, featureApplication);
- }
- featureApplication.addProgramClass(clazz);
- }
- return applications;
- }
-
- public static void runD8ForTesting(D8Command command, boolean dontCreateMarkerInD8)
- throws CompilationFailedException {
- InternalOptions options = command.getInternalOptions();
- options.testing.dontCreateMarkerInD8 = dontCreateMarkerInD8;
- D8.runForTesting(command.getInputApp(), options);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 2b50253..dce9604 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -116,7 +116,8 @@
threadCount,
dumpInputFlags,
mapIdProvider,
- null);
+ null,
+ false);
this.d8Command = d8Command;
this.r8Command = r8Command;
this.desugaredLibrarySpecification = desugaredLibrarySpecification;
@@ -302,6 +303,11 @@
}
@Override
+ public Builder setAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+ throw getReporter().fatalError("L8 does not support configuring Android platform builds.");
+ }
+
+ @Override
void validate() {
if (isPrintHelp()) {
return;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d70426f..2fd907e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -441,7 +441,7 @@
assert appView.appInfo().hasLiveness();
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- new StartupInstrumentation(appView).instrumentClasses(executorService);
+ new StartupInstrumentation(appView).instrumentAllClasses(executorService);
assert verifyNoJarApplicationReaders(appView.appInfo().classes());
assert appView.checkForTesting(() -> allReferencesAssignedApiLevel(appViewWithLiveness));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8d323ab..309a475 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -634,7 +634,8 @@
getDumpInputFlags(),
getMapIdProvider(),
getSourceFileProvider(),
- enableMissingLibraryApiModeling);
+ enableMissingLibraryApiModeling,
+ getAndroidPlatformBuild());
if (inputDependencyGraphConsumer != null) {
inputDependencyGraphConsumer.finished();
@@ -821,7 +822,8 @@
DumpInputFlags dumpInputFlags,
MapIdProvider mapIdProvider,
SourceFileProvider sourceFileProvider,
- boolean enableMissingLibraryApiModeling) {
+ boolean enableMissingLibraryApiModeling,
+ boolean isAndroidPlatformBuild) {
super(
inputApp,
mode,
@@ -838,7 +840,8 @@
threadCount,
dumpInputFlags,
mapIdProvider,
- sourceFileProvider);
+ sourceFileProvider,
+ isAndroidPlatformBuild);
assert proguardConfiguration != null;
assert mainDexKeepRules != null;
this.mainDexKeepRules = mainDexKeepRules;
@@ -1038,6 +1041,8 @@
SourceFileRewriter.computeSourceFileProvider(
getSourceFileProvider(), proguardConfiguration, internal);
+ internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
+
if (!DETERMINISTIC_DEBUGGING) {
assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
internal.threadCount = getThreadCount();
@@ -1086,6 +1091,7 @@
.setProguardConfiguration(proguardConfiguration)
.setMainDexKeepRules(mainDexKeepRules)
.setDesugaredLibraryConfiguration(desugaredLibrarySpecification)
+ .setEnableMissingLibraryApiModeling(enableMissingLibraryApiModeling)
.build();
}
}
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index dadb96a..3b80233 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -165,6 +165,7 @@
Path outputPath = state.outputPath != null ? state.outputPath : Paths.get(".");
OutputMode outputMode = state.outputMode != null ? state.outputMode : OutputMode.DexIndexed;
builder.setOutput(outputPath, outputMode, state.includeDataResources);
+ builder.setEnableExperimentalMissingLibraryApiModeling(true);
return builder;
}
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index 2d836ae..2e44e14 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.bisect.Bisect;
import com.android.tools.r8.cf.CfVerifierTool;
import com.android.tools.r8.compatproguard.CompatProguard;
-import com.android.tools.r8.dexsplitter.DexSplitter;
import com.android.tools.r8.relocator.RelocatorCommandLine;
import com.android.tools.r8.tracereferences.TraceReferences;
import java.util.Arrays;
@@ -41,9 +40,6 @@
case "dexsegments":
DexSegments.main(shift(args));
break;
- case "dexsplitter":
- DexSplitter.main(shift(args));
- break;
case "disasm":
Disassemble.main(shift(args));
break;
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index 19bf467..967453b 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -8,20 +8,24 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfo.PcBasedDebugInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.LebUtils;
import com.android.tools.r8.utils.LineNumberOptimizer;
import com.android.tools.r8.utils.StringUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntIterators;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
@@ -34,12 +38,12 @@
public interface DebugRepresentationPredicate {
- int getDexPcEncodingCutoff(DexProgramClass holder, DexEncodedMethod method);
+ int getDexPcEncodingCutoff(ProgramMethod method);
}
public static DebugRepresentationPredicate none(InternalOptions options) {
assert !options.canUseDexPc2PcAsDebugInformation();
- return (holder, method) -> NO_PC_ENCODING;
+ return method -> NO_PC_ENCODING;
}
public static DebugRepresentationPredicate fromFiles(
@@ -48,29 +52,33 @@
return none(options);
}
if (options.canUseNativeDexPcInsteadOfDebugInfo()) {
- return (holder, method) -> ALWAYS_PC_ENCODING;
+ return method -> ALWAYS_PC_ENCODING;
}
// TODO(b/220999985): Avoid the need to maintain a class-to-file map.
Map<DexProgramClass, VirtualFile> classMapping = new IdentityHashMap<>();
for (VirtualFile file : files) {
+ if (options.testing.debugRepresentationCallback != null) {
+ options.testing.debugRepresentationCallback.accept(file.getDebugRepresentation());
+ }
file.classes().forEach(c -> classMapping.put(c, file));
}
- return (holder, method) -> {
- if (!isPcCandidate(method, options)) {
+ return method -> {
+ if (!isPcCandidate(method.getDefinition(), options)) {
return NO_PC_ENCODING;
}
- VirtualFile file = classMapping.get(holder);
+ VirtualFile file = classMapping.get(method.getHolder());
DebugRepresentation cutoffs = file.getDebugRepresentation();
int maxPc = cutoffs.getDexPcEncodingCutoff(method);
assert maxPc == NO_PC_ENCODING
- || verifyLastExecutableInstructionWithinBound(method.getCode().asDexCode(), maxPc);
+ || verifyLastExecutableInstructionWithinBound(
+ method.getDefinition().getCode().asDexCode(), maxPc);
return maxPc;
};
}
- private final Int2ReferenceMap<CostSummary> paramToInfo;
+ private final Int2ReferenceMap<ConversionInfo> paramToInfo;
- private DebugRepresentation(Int2ReferenceMap<CostSummary> paramToInfo) {
+ private DebugRepresentation(Int2ReferenceMap<ConversionInfo> paramToInfo) {
this.paramToInfo = paramToInfo;
}
@@ -113,23 +121,31 @@
int lastPc = lastInstruction.getOffset();
int debugInfoCost = estimatedDebugInfoSize(debugInfo);
paramCountToCosts
- .computeIfAbsent(debugInfo.getParameterCount(), DebugRepresentation.CostSummary::new)
+ .computeIfAbsent(debugInfo.getParameterCount(), CostSummary::new)
.addCost(lastPc, debugInfoCost);
}
}
// Second compute the cost of converting to a pc encoding.
- paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts(appView));
+ Int2ReferenceMap<ConversionInfo> conversions =
+ new Int2ReferenceOpenHashMap<>(paramCountToCosts.size());
+ paramCountToCosts.forEach(
+ (param, summary) -> conversions.put(param, summary.computeConversionCosts(appView)));
// The result is stored on the virtual files for thread safety.
// TODO(b/220999985): Consider just passing this to the line number optimizer once fixed.
- file.setDebugRepresentation(new DebugRepresentation(paramCountToCosts));
+ file.setDebugRepresentation(new DebugRepresentation(conversions));
}
- private int getDexPcEncodingCutoff(DexEncodedMethod method) {
- DexCode code = method.getCode().asDexCode();
+ private int getDexPcEncodingCutoff(ProgramMethod method) {
+ if (paramToInfo.isEmpty()) {
+ // This should only be the case if the method has overloads and thus *cannot* use pc encoding.
+ assert verifyMethodHasOverloads(method);
+ return NO_PC_ENCODING;
+ }
+ DexCode code = method.getDefinition().getCode().asDexCode();
int paramCount = method.getParameters().size();
assert code.getDebugInfo() == null || code.getDebugInfo().getParameterCount() == paramCount;
- CostSummary conversionInfo = paramToInfo.get(paramCount);
- if (conversionInfo == null || conversionInfo.cutoff < 0) {
+ ConversionInfo conversionInfo = paramToInfo.get(paramCount);
+ if (conversionInfo == null || !conversionInfo.hasConversions()) {
// We expect all methods calling this to have computed conversion info.
assert conversionInfo != null;
return NO_PC_ENCODING;
@@ -139,14 +155,24 @@
return NO_PC_ENCODING;
}
int maxPc = lastInstruction.getOffset();
- return maxPc <= conversionInfo.cutoff ? conversionInfo.cutoff : NO_PC_ENCODING;
+ return conversionInfo.getConversionPointFor(maxPc);
+ }
+
+ private boolean verifyMethodHasOverloads(ProgramMethod method) {
+ assert 1
+ < IterableUtils.size(method.getHolder().methods(m -> m.getName().equals(method.getName())));
+ return true;
}
@Override
public String toString() {
- List<CostSummary> sorted = new ArrayList<>(paramToInfo.values());
+ return toString(false);
+ }
+
+ public String toString(boolean printCostSummary) {
+ List<ConversionInfo> sorted = new ArrayList<>(paramToInfo.values());
sorted.sort(Comparator.comparing(i -> i.paramCount));
- return StringUtils.join("\n", sorted, CostSummary::toString);
+ return StringUtils.join("\n", sorted, c -> c.toString(printCostSummary));
}
private static boolean isPcCandidate(DexEncodedMethod method, InternalOptions options) {
@@ -157,14 +183,32 @@
return LineNumberOptimizer.mustHaveResidualDebugInfo(code, options);
}
- /** The cost of representing normal debug info for all methods with this max pc value. */
- private static class PcNormalCost {
-
+ /** Cost information for debug info at a given PC. */
+ private static class PcCostInfo {
+ // PC point for which the information pertains to.
final int pc;
- int cost;
- int methods;
- public PcNormalCost(int pc) {
+ // Normal debug-info encoding cost.
+ int cost = 0;
+
+ // Number of methods this information pertains to.
+ int methods = 0;
+
+ @Override
+ public String toString() {
+ return "pc="
+ + pc
+ + ", cost="
+ + cost
+ + ", methods="
+ + methods
+ + ", saved="
+ + (cost - pc)
+ + ", overhead="
+ + getExpansionOverhead(pc, methods, cost);
+ }
+
+ public PcCostInfo(int pc) {
assert pc >= 0;
this.pc = pc;
}
@@ -176,20 +220,76 @@
}
}
+ /** Conversion judgment up to a given PC bound (the lower bound is context dependent). */
+ private static class PcConversionInfo {
+ static final PcConversionInfo NO_CONVERSION = new PcConversionInfo(-1, false, 0, 0);
+
+ // PC bound for which the information pertains to.
+ final int pc;
+
+ // Judgment of whether the methods in this grouping should be converted to pc2pc encoding.
+ final boolean converted;
+
+ // Info for debugging.
+ private final int methods;
+ private final int normalCost;
+
+ public PcConversionInfo(int pc, boolean converted, int methods, int normalCost) {
+ this.pc = pc;
+ this.converted = converted;
+ this.methods = methods;
+ this.normalCost = normalCost;
+ }
+
+ @Override
+ public String toString() {
+ return "pc="
+ + pc
+ + ", converted="
+ + converted
+ + ", cost="
+ + normalCost
+ + ", methods="
+ + methods
+ + ", saved="
+ + (normalCost - pc)
+ + ", overhead="
+ + getExpansionOverhead(pc, methods, normalCost);
+ }
+ }
+
+ // A pc2pc stream is approximately one event more than the pc.
+ private static int pcEventCount(int pc) {
+ return pc + 1;
+ }
+
+ /**
+ * Figure for the overhead that the pc2pc encoding can result in.
+ *
+ * <p>This overhead is not in the encoding size but rather if the encoding is expanded at each
+ * method referencing the shared PC encoding.
+ */
+ private static int getExpansionOverhead(int currentPc, int methodCount, int normalCost) {
+ long expansion = ((long) pcEventCount(currentPc)) * methodCount;
+ long cost = expansion - normalCost;
+ return cost > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) cost;
+ }
+
+ private static boolean isWithinExpansionThreshold(
+ int threshold, int currentPc, int methodCount, int normalCost) {
+ // A negative threshold denotes unbounded.
+ if (threshold < 0) {
+ return true;
+ }
+ return getExpansionOverhead(currentPc, methodCount, normalCost) <= threshold;
+ }
+
/** The summary of normal costs for all debug info with a particular parameter size. */
private static class CostSummary {
-
private final int paramCount;
// Values for the normal encoding costs per-pc.
- private final Int2ReferenceMap<PcNormalCost> pcToCost = new Int2ReferenceOpenHashMap<>();
- private int minPc = Integer.MAX_VALUE;
- private int maxPc = Integer.MIN_VALUE;
-
- // Values for the conversion costs. These are computed only after all per-pc costs are known.
- private int cutoff;
- private int normalPreCutoffCost;
- private int normalPostCutoffCost;
+ private Int2ReferenceMap<PcCostInfo> pcToCost = new Int2ReferenceOpenHashMap<>();
private CostSummary(int paramCount) {
assert paramCount >= 0;
@@ -198,82 +298,166 @@
private void addCost(int pc, int cost) {
assert pc >= 0;
- pcToCost.computeIfAbsent(pc, PcNormalCost::new).add(cost);
- minPc = Math.min(minPc, pc);
- maxPc = Math.max(maxPc, pc);
+ pcToCost.computeIfAbsent(pc, PcCostInfo::new).add(cost);
}
- private void computeConversionCosts(AppView<?> appView) {
+ private static class ConversionState {
+ Int2ReferenceSortedMap<PcConversionInfo> groups = new Int2ReferenceAVLTreeMap<>();
+ PcConversionInfo converted = PcConversionInfo.NO_CONVERSION;
+ int flushedPc = 0;
+ int unconvertedPc = 0;
+ int normalCost = 0;
+ int methods = 0;
+
+ void reset() {
+ converted = PcConversionInfo.NO_CONVERSION;
+ unconvertedPc = 0;
+ normalCost = 0;
+ methods = 0;
+ }
+
+ void add(PcCostInfo costInfo) {
+ methods += costInfo.methods;
+ normalCost += costInfo.cost;
+ }
+
+ void flush() {
+ if (flushedPc < converted.pc) {
+ groups.put(converted.pc, converted);
+ flushedPc = converted.pc;
+ }
+ if (flushedPc < unconvertedPc) {
+ if (0 < flushedPc && !groups.get(flushedPc).converted) {
+ groups.remove(flushedPc);
+ }
+ PcConversionInfo unconverted =
+ new PcConversionInfo(unconvertedPc, false, methods, normalCost);
+ groups.put(unconvertedPc, unconverted);
+ flushedPc = unconvertedPc;
+ }
+ reset();
+ }
+
+ void update(int currentPc, boolean convertToPc) {
+ if (convertToPc) {
+ converted = new PcConversionInfo(currentPc, true, methods, normalCost);
+ unconvertedPc = 0;
+ } else {
+ unconvertedPc = currentPc;
+ }
+ }
+
+ public Int2ReferenceSortedMap<PcConversionInfo> getFinalConversions() {
+ // If there is only a single group check it is actually a converted range.
+ if (groups.size() > 1
+ || (groups.size() == 1 && groups.values().iterator().next().converted)) {
+ return groups;
+ }
+ return null;
+ }
+ }
+
+ private ConversionInfo computeConversionCosts(AppView<?> appView) {
+ int threshold = appView.options().testing.pcBasedDebugEncodingOverheadThreshold;
boolean forcePcBasedEncoding = appView.options().testing.forcePcBasedEncoding;
assert !pcToCost.isEmpty();
- // Point at which it is estimated that conversion to PC-encoding is viable.
- int currentConvertedPc = -1;
- // The normal cost of the part that is viable for conversion (this is just for debugging).
- int normalConvertedCost = 0;
- // The normal cost of the part that is not yet part of the converted range.
- int normalOutstandingCost = 0;
// Iterate in ascending order as the point conversion cost is the sum of the preceding costs.
int[] sortedPcs = new int[pcToCost.size()];
IntIterators.unwrap(pcToCost.keySet().iterator(), sortedPcs);
Arrays.sort(sortedPcs);
+ ConversionState state = new ConversionState();
for (int currentPc : sortedPcs) {
- PcNormalCost pcSummary = pcToCost.get(currentPc);
- // The cost of the debug info unconverted is the sum of the unconverted up to this point.
- normalOutstandingCost += pcSummary.cost;
- // The cost of the conversion is the delta between the already converted and the current.
- // This does not account for the header overhead on converting the first point. However,
- // the few bytes overhead per param-count should not affect much.
- int costToConvert = currentPc - currentConvertedPc;
- // If the estimated cost is larger we convert. The order here could be either way as
- // both the normal cost and converted cost are estimates. Canonicalization could reduce
- // the former and compaction could reduce the latter.
- if (forcePcBasedEncoding || normalOutstandingCost > costToConvert) {
- normalConvertedCost += normalOutstandingCost;
- normalOutstandingCost = 0;
- currentConvertedPc = currentPc;
+ PcCostInfo current = pcToCost.get(currentPc);
+ assert currentPc == current.pc;
+ // Don't extend the conversion group as it could potentially become too large if expanded.
+ // Any pending conversion can be flushed now.
+ if (!isWithinExpansionThreshold(
+ threshold,
+ currentPc,
+ state.methods + current.methods,
+ state.normalCost + current.cost)) {
+ state.flush();
}
+ state.add(current);
+ int costToConvert = pcEventCount(currentPc);
+ boolean canExpand =
+ isWithinExpansionThreshold(threshold, currentPc, state.methods, state.normalCost);
+ state.update(
+ currentPc, canExpand && (forcePcBasedEncoding || state.normalCost > costToConvert));
}
- cutoff = currentConvertedPc;
- normalPreCutoffCost = normalConvertedCost;
- normalPostCutoffCost = normalOutstandingCost;
- assert cutoff >= -1;
- assert normalPreCutoffCost >= 0;
- assert normalPostCutoffCost >= 0;
- assert preCutoffPcCost() >= 0;
- assert postCutoffPcCost() >= 0;
- }
-
- private int preCutoffPcCost() {
- return cutoff > 0 ? PcBasedDebugInfo.estimatedWriteSize(paramCount, cutoff) : 0;
- }
-
- private int postCutoffPcCost() {
- return cutoff < maxPc ? PcBasedDebugInfo.estimatedWriteSize(paramCount, maxPc - cutoff) : 0;
+ state.flush();
+ return new ConversionInfo(
+ paramCount,
+ state.getFinalConversions(),
+ appView.options().testing.debugRepresentationCallback != null ? this : null);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder
- .append("p:")
- .append(paramCount)
- .append(", c:")
- .append(cutoff)
- .append(", m:")
- .append(maxPc);
- if (cutoff > 0) {
- builder
- .append(", preCutNormal:")
- .append(normalPreCutoffCost)
- .append(", preCutPC:")
- .append(preCutoffPcCost());
+ builder.append("params:").append(paramCount).append('\n');
+ Collection<Integer> keys = CollectionUtils.sort(pcToCost.keySet(), Integer::compareTo);
+ for (int key : keys) {
+ builder.append(pcToCost.get(key)).append('\n');
}
- if (cutoff < maxPc) {
- builder
- .append(", postCutNormal:")
- .append(normalPostCutoffCost)
- .append(", postCutPC:")
- .append(postCutoffPcCost());
+ return builder.toString();
+ }
+ }
+
+ /** The computed conversion points all debug info with a particular parameter size. */
+ private static class ConversionInfo {
+ private final int paramCount;
+ private final Int2ReferenceSortedMap<PcConversionInfo> conversions;
+
+ // For debugging purposes.
+ private final CostSummary costSummary;
+
+ private ConversionInfo(
+ int paramCount,
+ Int2ReferenceSortedMap<PcConversionInfo> conversions,
+ CostSummary costSummary) {
+ assert paramCount >= 0;
+ this.paramCount = paramCount;
+ this.conversions = conversions;
+ this.costSummary = costSummary;
+ }
+
+ boolean hasConversions() {
+ return conversions != null;
+ }
+
+ int getConversionPointFor(int pc) {
+ Int2ReferenceSortedMap<PcConversionInfo> tailMap = conversions.tailMap(pc);
+ if (tailMap.isEmpty()) {
+ return -1;
+ }
+ int pcGroupBound = tailMap.firstIntKey();
+ PcConversionInfo entryUpToIncludingMax = conversions.get(pcGroupBound);
+ if (entryUpToIncludingMax.converted) {
+ assert pcGroupBound == entryUpToIncludingMax.pc;
+ return pcGroupBound;
+ }
+ return -1;
+ }
+
+ @Override
+ public String toString() {
+ return toString(false);
+ }
+
+ public String toString(boolean printCostSummaries) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("params:").append(paramCount).append('\n');
+ if (conversions != null) {
+ for (PcConversionInfo group : conversions.values()) {
+ builder.append(group).append('\n');
+ }
+ } else {
+ builder.append(" no conversions").append('\n');
+ }
+ if (printCostSummaries && costSummary != null) {
+ builder.append("Cost summaries:\n");
+ builder.append(costSummary);
}
return builder.toString();
}
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 d4f1195..3e59e50 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -727,16 +727,18 @@
options.itemFactory));
}
- if (clazz.isNestHost()) {
- annotations.add(
- DexAnnotation.createNestMembersAnnotation(
- clazz.getNestMembersClassAttributes(), options.itemFactory));
- }
+ if (options.emitNestAnnotationsInDex) {
+ if (clazz.isNestHost()) {
+ annotations.add(
+ DexAnnotation.createNestMembersAnnotation(
+ clazz.getNestMembersClassAttributes(), options.itemFactory));
+ }
- if (clazz.isNestMember()) {
- annotations.add(
- DexAnnotation.createNestHostAnnotation(
- clazz.getNestHostClassAttribute(), options.itemFactory));
+ if (clazz.isNestMember()) {
+ annotations.add(
+ DexAnnotation.createNestHostAnnotation(
+ clazz.getNestHostClassAttribute(), options.itemFactory));
+ }
}
if (clazz.hasPermittedSubclassAttributes() && options.canUseSealedClasses()) {
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java
index 5454b0f..7143d6b 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java
@@ -29,11 +29,22 @@
.getStartupOrder()
.toStartupOrderForWriting(appView)
: StartupOrder.empty();
- if (startupOrderForWriting.isEmpty()) {
- return new DefaultMixedSectionLayoutStrategy(appView, mixedSectionOffsets);
- }
- return new StartupMixedSectionLayoutStrategy(
- appView, mixedSectionOffsets, startupOrderForWriting, virtualFile);
+ MixedSectionLayoutStrategy mixedSectionLayoutStrategy =
+ startupOrderForWriting.isEmpty()
+ ? new DefaultMixedSectionLayoutStrategy(appView, mixedSectionOffsets)
+ : new StartupMixedSectionLayoutStrategy(
+ appView, mixedSectionOffsets, startupOrderForWriting, virtualFile);
+ return wrapForTesting(appView, mixedSectionLayoutStrategy, virtualFile);
+ }
+
+ private static MixedSectionLayoutStrategy wrapForTesting(
+ AppView<?> appView,
+ MixedSectionLayoutStrategy mixedSectionLayoutStrategy,
+ VirtualFile virtualFile) {
+ return appView
+ .testing()
+ .mixedSectionLayoutStrategyInspector
+ .apply(mixedSectionLayoutStrategy, virtualFile);
}
public abstract Collection<DexAnnotation> getAnnotationLayout();
diff --git a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
index b5ccc6b..eb03451 100644
--- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.dex;
import com.android.tools.r8.dex.FileWriter.MixedSectionOffsets;
+import com.android.tools.r8.experimental.startup.StartupClass;
import com.android.tools.r8.experimental.startup.StartupOrder;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
@@ -30,7 +31,6 @@
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
-import java.util.Set;
public class StartupMixedSectionLayoutStrategy extends DefaultMixedSectionLayoutStrategy {
@@ -79,8 +79,9 @@
virtualFile.classes().size());
LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView, true);
StartupIndexedItemCollection indexedItemCollection = new StartupIndexedItemCollection();
- for (DexType startupClass : startupOrderForWriting.getClasses()) {
- DexProgramClass definition = virtualFileDefinitions.get(startupClass);
+ for (StartupClass<DexType> startupClass : startupOrderForWriting.getClasses()) {
+ assert !startupClass.isSynthetic();
+ DexProgramClass definition = virtualFileDefinitions.get(startupClass.getReference());
if (definition != null) {
definition.collectIndexedItems(appView, indexedItemCollection, rewriter);
}
@@ -120,10 +121,7 @@
@Override
public Collection<ProgramMethod> getCodeLayout() {
- Set<DexProgramClass> nonStartupClasses =
- new LinkedHashSet<>(mixedSectionOffsets.getClassesWithData());
- nonStartupClasses.removeIf(clazz -> startupOrderForWriting.contains(clazz.getType()));
- return amendStartupLayout(codeLayout, super.getCodeLayoutForClasses(nonStartupClasses));
+ return amendStartupLayout(codeLayout, super.getCodeLayout());
}
@Override
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 80b9a6c..839545d 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -10,7 +10,10 @@
import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.experimental.startup.StartupOrder;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -44,6 +47,7 @@
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;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
@@ -407,15 +411,17 @@
return ImmutableMap.of();
}
+ AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+ appView.withClassHierarchy();
ClassToFeatureSplitMap classToFeatureSplitMap =
- appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap();
+ appViewWithClassHierarchy.appInfo().getClassToFeatureSplitMap();
if (classToFeatureSplitMap.isEmpty()) {
return ImmutableMap.of();
}
// Pull out the classes that should go into feature splits.
Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
- classToFeatureSplitMap.getFeatureSplitClasses(classes, appView.getSyntheticItems());
+ classToFeatureSplitMap.getFeatureSplitClasses(classes, appViewWithClassHierarchy);
if (featureSplitClasses.size() > 0) {
for (Set<DexProgramClass> featureClasses : featureSplitClasses.values()) {
classes.removeAll(featureClasses);
@@ -939,11 +945,19 @@
if (!appView.hasClassHierarchy()) {
return alwaysFalse();
}
- ClassToFeatureSplitMap classToFeatureSplitMap =
- appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap();
+ StartupOrder startupOrder = appView.appInfoWithClassHierarchy().getStartupOrder();
SyntheticItems syntheticItems = appView.getSyntheticItems();
- return clazz ->
- classToFeatureSplitMap.getFeatureSplit(clazz, syntheticItems).isStartupBase();
+ return clazz -> {
+ if (syntheticItems.isSyntheticClass(clazz)) {
+ return Iterables.any(
+ syntheticItems.getSynthesizingContextTypes(clazz.getType()),
+ startupOrder::containsSyntheticClassesSynthesizedFrom);
+ } else {
+ StartupClass<DexType> startupClass =
+ StartupClass.<DexType>builder().setReference(clazz.getType()).build();
+ return startupOrder.contains(startupClass);
+ }
+ };
}
public List<DexProgramClass> getStartupClasses() {
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
deleted file mode 100644
index 4ee416c..0000000
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ /dev/null
@@ -1,377 +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.dexsplitter;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DexSplitterHelper;
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FeatureClassMapping;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.OptionsParsing;
-import com.android.tools.r8.utils.OptionsParsing.ParseContext;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-@Keep
-public final class DexSplitter {
-
- private static final String DEFAULT_OUTPUT_DIR = "output";
- private static final String DEFAULT_BASE_NAME = "base";
-
- private static final boolean PRINT_ARGS = false;
-
- public static class FeatureJar {
- private String jar;
- private String outputName;
-
- public FeatureJar(String jar, String outputName) {
- this.jar = jar;
- this.outputName = outputName;
- }
-
- public FeatureJar(String jar) {
- this(jar, featureNameFromJar(jar));
- }
-
- public String getJar() {
- return jar;
- }
-
- public String getOutputName() {
- return outputName;
- }
-
- private static String featureNameFromJar(String jar) {
- Path jarPath = Paths.get(jar);
- String featureName = jarPath.getFileName().toString();
- if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) {
- featureName = featureName.substring(0, featureName.length() - 4);
- }
- return featureName;
- }
- }
-
- private static class ZipFileOrigin extends PathOrigin {
-
- public ZipFileOrigin(Path path) {
- super(path);
- }
-
- @Override
- public String part() {
- return "splitting of file '" + super.part() + "'";
- }
- }
-
- @Keep
- public static final class Options {
- private final DiagnosticsHandler diagnosticsHandler;
- private List<String> inputArchives = new ArrayList<>();
- private List<FeatureJar> featureJars = new ArrayList<>();
- private List<String> baseJars = new ArrayList<>();
- private String baseOutputName = DEFAULT_BASE_NAME;
- private String output = DEFAULT_OUTPUT_DIR;
- private String featureSplitMapping;
- private String proguardMap;
- private String mainDexList;
- private boolean splitNonClassResources = false;
-
- public Options() {
- this(new DiagnosticsHandler() {});
- }
-
- public Options(DiagnosticsHandler diagnosticsHandler) {
- this.diagnosticsHandler = diagnosticsHandler;
- }
-
- public DiagnosticsHandler getDiagnosticsHandler() {
- return diagnosticsHandler;
- }
-
- public String getMainDexList() {
- return mainDexList;
- }
-
- public void setMainDexList(String mainDexList) {
- this.mainDexList = mainDexList;
- }
-
- public String getOutput() {
- return output;
- }
-
- public void setOutput(String output) {
- this.output = output;
- }
-
- public String getFeatureSplitMapping() {
- return featureSplitMapping;
- }
-
- public void setFeatureSplitMapping(String featureSplitMapping) {
- this.featureSplitMapping = featureSplitMapping;
- }
-
- public String getProguardMap() {
- return proguardMap;
- }
-
- public void setProguardMap(String proguardMap) {
- this.proguardMap = proguardMap;
- }
-
- public String getBaseOutputName() {
- return baseOutputName;
- }
-
- public void setBaseOutputName(String baseOutputName) {
- this.baseOutputName = baseOutputName;
- }
-
- public void addInputArchive(String inputArchive) {
- inputArchives.add(inputArchive);
- }
-
- public void addBaseJar(String baseJar) {
- baseJars.add(baseJar);
- }
-
- private void addFeatureJar(FeatureJar featureJar) {
- featureJars.add(featureJar);
- }
-
- public void addFeatureJar(String jar) {
- featureJars.add(new FeatureJar(jar));
- }
-
- public void addFeatureJar(String jar, String outputName) {
- featureJars.add(new FeatureJar(jar, outputName));
- }
-
- public void setSplitNonClassResources(boolean value) {
- splitNonClassResources = value;
- }
-
- public ImmutableList<String> getInputArchives() {
- return ImmutableList.copyOf(inputArchives);
- }
-
- ImmutableList<FeatureJar> getFeatureJars() {
- return ImmutableList.copyOf(featureJars);
- }
-
- ImmutableList<String> getBaseJars() {
- return ImmutableList.copyOf(baseJars);
- }
-
- // Shorthand error messages.
- public Diagnostic error(String msg) {
- StringDiagnostic error = new StringDiagnostic(msg);
- diagnosticsHandler.error(error);
- return error;
- }
- }
-
- /**
- * Parse a feature jar argument and return the corresponding FeatureJar representation.
- * Default to use the name of the jar file if the argument contains no ':', if the argument
- * contains ':', then use the value after the ':' as the name.
- * @param argument
- */
- private static FeatureJar parseFeatureJarArgument(String argument) {
- if (argument.contains(":")) {
- String[] parts = argument.split(":");
- if (parts.length > 2) {
- throw new RuntimeException("--feature-jar argument contains more than one :");
- }
- return new FeatureJar(parts[0], parts[1]);
- }
- return new FeatureJar(argument);
- }
-
- private static Options parseArguments(String[] args) {
- Options options = new Options();
- ParseContext context = new ParseContext(args);
- while (context.head() != null) {
- List<String> inputs = OptionsParsing.tryParseMulti(context, "--input");
- if (inputs != null) {
- inputs.forEach(options::addInputArchive);
- continue;
- }
- List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar");
- if (featureJars != null) {
- featureJars.forEach((feature) -> options.addFeatureJar(parseFeatureJarArgument(feature)));
- continue;
- }
- List<String> baseJars = OptionsParsing.tryParseMulti(context, "--base-jar");
- if (baseJars != null) {
- baseJars.forEach(options::addBaseJar);
- continue;
- }
- String output = OptionsParsing.tryParseSingle(context, "--output", "-o");
- if (output != null) {
- options.setOutput(output);
- continue;
- }
-
- String mainDexList= OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
- if (mainDexList!= null) {
- options.setMainDexList(mainDexList);
- continue;
- }
-
- String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
- if (proguardMap != null) {
- options.setProguardMap(proguardMap);
- continue;
- }
- String baseOutputName = OptionsParsing.tryParseSingle(context, "--base-output-name", null);
- if (baseOutputName != null) {
- options.setBaseOutputName(baseOutputName);
- continue;
- }
- String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
- if (featureSplit != null) {
- options.setFeatureSplitMapping(featureSplit);
- continue;
- }
- Boolean b = OptionsParsing.tryParseBoolean(context, "--split-non-class-resources");
- if (b != null) {
- options.setSplitNonClassResources(b);
- continue;
- }
- throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
- }
- return options;
- }
-
- private static FeatureClassMapping createFeatureClassMapping(Options options)
- throws FeatureMappingException {
- if (options.getFeatureSplitMapping() != null) {
- return FeatureClassMapping.fromSpecification(
- Paths.get(options.getFeatureSplitMapping()), options.getDiagnosticsHandler());
- }
- assert !options.getFeatureJars().isEmpty();
- return FeatureClassMapping.Internal.fromJarFiles(options.getFeatureJars(),
- options.getBaseJars(), options.getBaseOutputName(), options.getDiagnosticsHandler());
- }
-
- private static void run(String[] args)
- throws CompilationFailedException, FeatureMappingException {
- Options options = parseArguments(args);
- run(options);
- }
-
- public static void run(Options options)
- throws FeatureMappingException, CompilationFailedException {
- Diagnostic error = null;
- if (options.getInputArchives().isEmpty()) {
- error = options.error("Need at least one --input");
- }
- if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
- error = options.error("You must supply a feature split mapping or feature jars");
- }
- if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
- error = options.error("You can't supply both a feature split mapping and feature jars");
- }
- if (error != null) {
- throw new AbortException(error);
- }
-
- D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler);
-
-
- for (String s : options.inputArchives) {
- builder.addProgramFiles(Paths.get(s));
- }
- // We set the actual consumer on the ApplicationWriter when we have calculated the distribution
- // since we don't yet know the distribution.
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- if (options.getMainDexList() != null) {
- builder.addMainDexListFiles(Paths.get(options.getMainDexList()));
- }
-
- FeatureClassMapping featureClassMapping = createFeatureClassMapping(options);
-
- DexSplitterHelper.run(
- builder.build(), featureClassMapping, options.getOutput(), options.getProguardMap());
-
- if (options.splitNonClassResources) {
- splitNonClassResources(options, featureClassMapping);
- }
- }
-
- private static void splitNonClassResources(Options options,
- FeatureClassMapping featureClassMapping) {
- for (String s : options.inputArchives) {
- try (ZipFile zipFile = new ZipFile(s, StandardCharsets.UTF_8)) {
- Enumeration<? extends ZipEntry> entries = zipFile.entries();
- while (entries.hasMoreElements()) {
- ZipEntry entry = entries.nextElement();
- String name = entry.getName();
- if (!ZipUtils.isDexFile(name) && !ZipUtils.isClassFile(name)) {
- String feature = featureClassMapping.featureForNonClass(name);
- Path outputDir = Paths.get(options.getOutput()).resolve(feature);
- try (InputStream stream = zipFile.getInputStream(entry)) {
- Path outputFile = outputDir.resolve(name);
- Path parent = outputFile.getParent();
- if (parent != null) {
- Files.createDirectories(parent);
- }
- Files.copy(stream, outputFile);
- }
- }
- }
- } catch (IOException e) {
- ExceptionDiagnostic error = new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s)));
- options.getDiagnosticsHandler().error(error);
- throw new AbortException(error);
- }
- }
- }
-
- public static void main(String[] args) {
- if (PRINT_ARGS) {
- printArgs(args);
- }
- ExceptionUtils.withMainProgramHandler(
- () -> {
- try {
- run(args);
- } catch (FeatureMappingException e) {
- // TODO(ricow): Report feature mapping errors via the reporter.
- throw new RuntimeException("Splitting failed: " + e.getMessage());
- }
- });
- }
-
- private static void printArgs(String[] args) {
- System.err.printf("r8.DexSplitter");
- for (String s : args) {
- System.err.printf(" %s", s);
- }
- System.err.println("");
- }
-}
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 224d24a..a604608 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -40,6 +40,9 @@
private static final String MINIFICATION_KEY = "minification";
private static final String FORCE_PROGUARD_COMPATIBILITY_KEY = "force-proguard-compatibility";
private static final String SYSTEM_PROPERTY_PREFIX = "system-property-";
+ private static final String ENABLE_MISSING_LIBRARY_API_MODELING =
+ "enable-missing-library-api-modeling";
+ private static final String ANDROID_PLATFORM_BUILD = "android-platform-build";
private final Tool tool;
private final CompilationMode compilationMode;
@@ -58,6 +61,8 @@
private final FeatureSplitConfiguration featureSplitConfiguration;
private final ProguardConfiguration proguardConfiguration;
private final List<ProguardConfigurationRule> mainDexKeepRules;
+ private final boolean enableMissingLibraryApiModeling;
+ private final boolean isAndroidPlatformBuild;
private final Map<String, String> systemProperties;
@@ -80,6 +85,8 @@
FeatureSplitConfiguration featureSplitConfiguration,
ProguardConfiguration proguardConfiguration,
List<ProguardConfigurationRule> mainDexKeepRules,
+ boolean enableMissingLibraryApiModeling,
+ boolean isAndroidPlatformBuild,
Map<String, String> systemProperties,
boolean dumpInputToFile) {
this.tool = tool;
@@ -97,6 +104,8 @@
this.featureSplitConfiguration = featureSplitConfiguration;
this.proguardConfiguration = proguardConfiguration;
this.mainDexKeepRules = mainDexKeepRules;
+ this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
+ this.isAndroidPlatformBuild = isAndroidPlatformBuild;
this.systemProperties = systemProperties;
this.dumpInputToFile = dumpInputToFile;
}
@@ -115,6 +124,10 @@
addDumpEntry(builder, THREAD_COUNT_KEY, threadCount);
}
addDumpEntry(builder, DESUGAR_STATE_KEY, desugarState);
+ addDumpEntry(builder, ENABLE_MISSING_LIBRARY_API_MODELING, enableMissingLibraryApiModeling);
+ if (isAndroidPlatformBuild) {
+ addDumpEntry(builder, ANDROID_PLATFORM_BUILD, isAndroidPlatformBuild);
+ }
addOptionalDumpEntry(builder, INTERMEDIATE_KEY, intermediate);
addOptionalDumpEntry(builder, INCLUDE_DATA_RESOURCES_KEY, includeDataResources);
addOptionalDumpEntry(builder, TREE_SHAKING_KEY, treeShaking);
@@ -273,6 +286,9 @@
private ProguardConfiguration proguardConfiguration;
private List<ProguardConfigurationRule> mainDexKeepRules;
+ private boolean enableMissingLibraryApiModeling = false;
+ private boolean isAndroidPlatformBuild = false;
+
private Map<String, String> systemProperties = new HashMap<>();
// Reporting only.
@@ -362,6 +378,16 @@
return this;
}
+ public Builder setEnableMissingLibraryApiModeling(boolean value) {
+ enableMissingLibraryApiModeling = value;
+ return this;
+ }
+
+ public Builder setAndroidPlatformBuild(boolean value) {
+ isAndroidPlatformBuild = value;
+ return this;
+ }
+
public Builder setSystemProperty(String key, String value) {
this.systemProperties.put(key, value);
return this;
@@ -398,6 +424,8 @@
featureSplitConfiguration,
proguardConfiguration,
mainDexKeepRules,
+ enableMissingLibraryApiModeling,
+ isAndroidPlatformBuild,
systemProperties,
dumpInputToFile);
}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
index 7279ee7..00f0db2 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
import java.util.Collection;
import java.util.Collections;
@@ -16,12 +17,17 @@
EmptyStartupOrder() {}
@Override
- public boolean contains(DexType type) {
+ public boolean contains(StartupClass<DexType> startupClass) {
return false;
}
@Override
- public Collection<DexType> getClasses() {
+ public boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType) {
+ return false;
+ }
+
+ @Override
+ public Collection<StartupClass<DexType>> getClasses() {
return Collections.emptyList();
}
@@ -41,7 +47,8 @@
}
@Override
- public EmptyStartupOrder withoutPrunedItems(PrunedItems prunedItems) {
+ public EmptyStartupOrder withoutPrunedItems(
+ PrunedItems prunedItems, SyntheticItems syntheticItems) {
return this;
}
}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
index 0427fc3..1b8eab9 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
@@ -5,33 +5,48 @@
package com.android.tools.r8.experimental.startup;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.LazyBox;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
public class NonEmptyStartupOrder extends StartupOrder {
- private final LinkedHashSet<DexType> startupClasses;
+ private final LinkedHashSet<StartupClass<DexType>> startupClasses;
- NonEmptyStartupOrder(LinkedHashSet<DexType> startupClasses) {
+ NonEmptyStartupOrder(LinkedHashSet<StartupClass<DexType>> startupClasses) {
assert !startupClasses.isEmpty();
this.startupClasses = startupClasses;
}
@Override
- public boolean contains(DexType type) {
- return startupClasses.contains(type);
+ public boolean contains(StartupClass<DexType> startupClass) {
+ return startupClasses.contains(startupClass);
}
@Override
- public Collection<DexType> getClasses() {
+ public boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType) {
+ return contains(
+ StartupClass.<DexType>builder()
+ .setReference(synthesizingContextType)
+ .setSynthetic()
+ .build());
+ }
+
+ @Override
+ public Collection<StartupClass<DexType>> getClasses() {
return startupClasses;
}
@@ -42,17 +57,22 @@
@Override
public StartupOrder rewrittenWithLens(GraphLens graphLens) {
- LinkedHashSet<DexType> rewrittenStartupClasses = new LinkedHashSet<>(startupClasses.size());
- for (DexType startupClass : startupClasses) {
- DexType rewrittenStartupClass = graphLens.lookupType(startupClass);
- rewrittenStartupClasses.add(rewrittenStartupClass);
+ LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+ new LinkedHashSet<>(startupClasses.size());
+ for (StartupClass<DexType> startupClass : startupClasses) {
+ rewrittenStartupClasses.add(
+ StartupClass.<DexType>builder()
+ .setFlags(startupClass.getFlags())
+ .setReference(graphLens.lookupType(startupClass.getReference()))
+ .build());
}
return createNonEmpty(rewrittenStartupClasses);
}
@Override
public StartupOrder toStartupOrderForWriting(AppView<?> appView) {
- LinkedHashSet<DexType> rewrittenStartupClasses = new LinkedHashSet<>(startupClasses.size());
+ LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+ new LinkedHashSet<>(startupClasses.size());
Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
new IdentityHashMap<>();
for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -65,81 +85,98 @@
}
}
}
- for (DexType startupClass : startupClasses) {
- addClassAndParentClasses(
+ for (StartupClass<DexType> startupClass : startupClasses) {
+ addStartupClass(
startupClass, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
}
+ assert rewrittenStartupClasses.stream().noneMatch(StartupClass::isSynthetic);
return createNonEmpty(rewrittenStartupClasses);
}
+ private static void addStartupClass(
+ StartupClass<DexType> startupClass,
+ LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
+ Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
+ AppView<?> appView) {
+ if (startupClass.isSynthetic()) {
+ List<DexProgramClass> syntheticClassesForContext =
+ syntheticContextsToSyntheticClasses.getOrDefault(
+ startupClass.getReference(), Collections.emptyList());
+ for (DexProgramClass clazz : syntheticClassesForContext) {
+ addClassAndParentClasses(clazz, rewrittenStartupClasses, appView);
+ }
+ } else {
+ addClassAndParentClasses(startupClass.getReference(), rewrittenStartupClasses, appView);
+ }
+ }
+
private static boolean addClass(
- DexProgramClass clazz, LinkedHashSet<DexType> rewrittenStartupClasses) {
- return rewrittenStartupClasses.add(clazz.getType());
+ DexProgramClass clazz, LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses) {
+ return rewrittenStartupClasses.add(
+ StartupClass.<DexType>builder().setReference(clazz.getType()).build());
}
private static void addClassAndParentClasses(
DexType type,
- LinkedHashSet<DexType> rewrittenStartupClasses,
- Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
+ LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
AppView<?> appView) {
DexProgramClass definition = appView.app().programDefinitionFor(type);
if (definition != null) {
- addClassAndParentClasses(
- definition, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
+ addClassAndParentClasses(definition, rewrittenStartupClasses, appView);
}
}
private static void addClassAndParentClasses(
DexProgramClass clazz,
- LinkedHashSet<DexType> rewrittenStartupClasses,
- Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
+ LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
AppView<?> appView) {
if (addClass(clazz, rewrittenStartupClasses)) {
- addSyntheticClassesAndParentClasses(
- clazz, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
- addParentClasses(
- clazz, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
- }
- }
-
- private static void addSyntheticClassesAndParentClasses(
- DexProgramClass clazz,
- LinkedHashSet<DexType> rewrittenStartupClasses,
- Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
- AppView<?> appView) {
- List<DexProgramClass> derivedClasses =
- syntheticContextsToSyntheticClasses.remove(clazz.getType());
- if (derivedClasses != null) {
- for (DexProgramClass derivedClass : derivedClasses) {
- addClassAndParentClasses(
- derivedClass, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
- }
+ addParentClasses(clazz, rewrittenStartupClasses, appView);
}
}
private static void addParentClasses(
DexProgramClass clazz,
- LinkedHashSet<DexType> rewrittenStartupClasses,
- Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
+ LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
AppView<?> appView) {
clazz.forEachImmediateSupertype(
- supertype ->
- addClassAndParentClasses(
- supertype, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView));
+ supertype -> addClassAndParentClasses(supertype, rewrittenStartupClasses, appView));
}
@Override
- public StartupOrder withoutPrunedItems(PrunedItems prunedItems) {
- LinkedHashSet<DexType> rewrittenStartupClasses = new LinkedHashSet<>(startupClasses.size());
- for (DexType startupClass : startupClasses) {
- if (!prunedItems.isRemoved(startupClass)) {
+ public StartupOrder withoutPrunedItems(PrunedItems prunedItems, SyntheticItems syntheticItems) {
+ LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+ new LinkedHashSet<>(startupClasses.size());
+ LazyBox<Set<DexType>> contextsOfLiveSynthetics =
+ new LazyBox<>(
+ () -> computeContextsOfLiveSynthetics(prunedItems.getPrunedApp(), syntheticItems));
+ for (StartupClass<DexType> startupClass : startupClasses) {
+ // Only prune non-synthetic classes, since the pruning of a class does not imply that all
+ // classes synthesized from it have been pruned.
+ if (startupClass.isSynthetic()) {
+ if (contextsOfLiveSynthetics.computeIfAbsent().contains(startupClass.getReference())) {
+ rewrittenStartupClasses.add(startupClass);
+ }
+ } else if (!prunedItems.isRemoved(startupClass.getReference())) {
rewrittenStartupClasses.add(startupClass);
}
}
return createNonEmpty(rewrittenStartupClasses);
}
- private StartupOrder createNonEmpty(LinkedHashSet<DexType> startupClasses) {
+ private Set<DexType> computeContextsOfLiveSynthetics(
+ DexApplication app, SyntheticItems syntheticItems) {
+ Set<DexType> contextsOfLiveSynthetics = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : app.classes()) {
+ if (syntheticItems.isSyntheticClass(clazz)) {
+ contextsOfLiveSynthetics.addAll(
+ syntheticItems.getSynthesizingContextTypes(clazz.getType()));
+ }
+ }
+ return contextsOfLiveSynthetics;
+ }
+
+ private StartupOrder createNonEmpty(LinkedHashSet<StartupClass<DexType>> startupClasses) {
if (startupClasses.isEmpty()) {
assert false;
return empty();
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
new file mode 100644
index 0000000..9338d5f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.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.experimental.startup;
+
+public class StartupClass<T> {
+
+ private static final int FLAG_SYNTHETIC = 1;
+
+ private final int flags;
+ private final T reference;
+
+ public StartupClass(int flags, T reference) {
+ this.flags = flags;
+ this.reference = reference;
+ }
+
+ public static <T> Builder<T> builder() {
+ return new Builder<>();
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ public T getReference() {
+ return reference;
+ }
+
+ public boolean isSynthetic() {
+ return (flags & FLAG_SYNTHETIC) != 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ StartupClass<?> startupClass = (StartupClass<?>) obj;
+ return flags == startupClass.flags && reference.equals(startupClass.reference);
+ }
+
+ @Override
+ public int hashCode() {
+ assert flags <= 1;
+ return (reference.hashCode() << 1) | flags;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (isSynthetic()) {
+ builder.append('S');
+ }
+ builder.append(reference);
+ return builder.toString();
+ }
+
+ public static class Builder<T> {
+
+ private int flags;
+ private T reference;
+
+ public Builder<T> setFlags(int flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ public Builder<T> setReference(T reference) {
+ this.reference = reference;
+ return this;
+ }
+
+ public Builder<T> setSynthetic() {
+ this.flags |= FLAG_SYNTHETIC;
+ return this;
+ }
+
+ public StartupClass<T> build() {
+ return new StartupClass<>(flags, reference);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
index 7e78009..9f16dce 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
@@ -13,22 +13,29 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
public class StartupConfiguration {
- private final List<DexType> startupClasses;
+ private final List<StartupClass<DexType>> startupClasses;
private final List<DexMethod> startupMethods;
- public StartupConfiguration(List<DexType> startupClasses, List<DexMethod> startupMethods) {
+ public StartupConfiguration(
+ List<StartupClass<DexType>> startupClasses, List<DexMethod> startupMethods) {
this.startupClasses = startupClasses;
this.startupMethods = startupMethods;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
/**
* Parses the supplied startup configuration, if any. The startup configuration is a list of class
* and method descriptors.
@@ -67,18 +74,26 @@
return null;
}
- List<DexType> startupClasses = new ArrayList<>();
+ return createStartupConfigurationFromLines(dexItemFactory, reporter, startupDescriptors);
+ }
+
+ public static StartupConfiguration createStartupConfigurationFromLines(
+ DexItemFactory dexItemFactory, Reporter reporter, List<String> startupDescriptors) {
+ List<StartupClass<DexType>> startupClasses = new ArrayList<>();
List<DexMethod> startupMethods = new ArrayList<>();
for (String startupDescriptor : startupDescriptors) {
if (startupDescriptor.isEmpty()) {
continue;
}
+ StartupClass.Builder<DexType> startupClassBuilder = StartupClass.builder();
+ startupDescriptor = parseSyntheticFlag(startupDescriptor, startupClassBuilder);
int methodNameStartIndex = getMethodNameStartIndex(startupDescriptor);
if (methodNameStartIndex >= 0) {
DexMethod startupMethod =
parseStartupMethodDescriptor(startupDescriptor, methodNameStartIndex, dexItemFactory);
if (startupMethod != null) {
- startupClasses.add(startupMethod.getHolderType());
+ startupClasses.add(
+ startupClassBuilder.setReference(startupMethod.getHolderType()).build());
startupMethods.add(startupMethod);
} else {
reporter.warning(
@@ -87,7 +102,7 @@
} else {
DexType startupClass = parseStartupClassDescriptor(startupDescriptor, dexItemFactory);
if (startupClass != null) {
- startupClasses.add(startupClass);
+ startupClasses.add(startupClassBuilder.setReference(startupClass).build());
} else {
reporter.warning(
new StringDiagnostic("Invalid descriptor for startup class: " + startupDescriptor));
@@ -97,6 +112,15 @@
return new StartupConfiguration(startupClasses, startupMethods);
}
+ public static String parseSyntheticFlag(
+ String startupDescriptor, StartupClass.Builder<?> startupClassBuilder) {
+ if (!startupDescriptor.isEmpty() && startupDescriptor.charAt(0) == 'S') {
+ startupClassBuilder.setSynthetic();
+ return startupDescriptor.substring(1);
+ }
+ return startupDescriptor;
+ }
+
private static int getMethodNameStartIndex(String startupDescriptor) {
int arrowIndex = startupDescriptor.indexOf("->");
return arrowIndex >= 0 ? arrowIndex + 2 : arrowIndex;
@@ -147,7 +171,28 @@
return !startupClasses.isEmpty();
}
- public List<DexType> getStartupClasses() {
+ public List<StartupClass<DexType>> getStartupClasses() {
return startupClasses;
}
+
+ public static class Builder {
+
+ private final ImmutableList.Builder<StartupClass<DexType>> startupClassesBuilder =
+ ImmutableList.builder();
+ private final ImmutableList.Builder<DexMethod> startupMethodsBuilder = ImmutableList.builder();
+
+ public Builder addStartupClass(StartupClass<DexType> startupClass) {
+ this.startupClassesBuilder.add(startupClass);
+ return this;
+ }
+
+ public Builder apply(Consumer<Builder> consumer) {
+ consumer.accept(this);
+ return this;
+ }
+
+ public StartupConfiguration build() {
+ return new StartupConfiguration(startupClassesBuilder.build(), startupMethodsBuilder.build());
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
index 8995dbc..aa97fa6 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
@@ -19,11 +19,15 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -41,12 +45,18 @@
this.options = appView.options().getStartupOptions();
}
- public void instrumentClasses(ExecutorService executorService) throws ExecutionException {
+ public void instrumentAllClasses(ExecutorService executorService) throws ExecutionException {
+ instrumentClasses(appView.appInfo().classes(), executorService);
+ }
+
+ public boolean instrumentClasses(
+ Collection<DexProgramClass> classes, ExecutorService executorService)
+ throws ExecutionException {
if (!appView.options().getStartupOptions().isStartupInstrumentationEnabled()) {
- return;
+ return false;
}
- ThreadUtils.processItems(
- appView.appInfo().classes(), this::internalInstrumentClass, executorService);
+ ThreadUtils.processItems(classes, this::internalInstrumentClass, executorService);
+ return true;
}
public void instrumentClass(DexProgramClass clazz) {
@@ -90,20 +100,31 @@
return;
}
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ DexString message;
+ if (syntheticItems.isSyntheticClass(classInitializer.getHolder())) {
+ Collection<DexType> synthesizingContexts =
+ syntheticItems.getSynthesizingContextTypes(classInitializer.getHolderType());
+ assert synthesizingContexts.size() == 1;
+ message = synthesizingContexts.iterator().next().getDescriptor().prepend("S", dexItemFactory);
+ } else {
+ message = classInitializer.getHolderType().getDescriptor();
+ }
+
CfCode cfCode = code.asCfCode();
List<CfInstruction> instructions;
if (options.hasStartupInstrumentationTag()) {
instructions = new ArrayList<>(4 + cfCode.getInstructions().size());
instructions.add(
new CfConstString(dexItemFactory.createString(options.getStartupInstrumentationTag())));
- instructions.add(new CfConstString(classInitializer.getHolderType().getDescriptor()));
+ instructions.add(new CfConstString(message));
instructions.add(
new CfInvoke(Opcodes.INVOKESTATIC, dexItemFactory.androidUtilLogMembers.i, false));
instructions.add(new CfStackInstruction(Opcode.Pop));
} else {
instructions = new ArrayList<>(3 + cfCode.getInstructions().size());
instructions.add(new CfStaticFieldRead(dexItemFactory.javaLangSystemMembers.out));
- instructions.add(new CfConstString(classInitializer.getHolderType().getDescriptor()));
+ instructions.add(new CfConstString(message));
instructions.add(
new CfInvoke(
Opcodes.INVOKEVIRTUAL,
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
index c633722..15fbf6c 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
@@ -30,12 +30,17 @@
return startupInstrumentationTag;
}
+ public StartupOptions setStartupInstrumentationTag(String startupInstrumentationTag) {
+ this.startupInstrumentationTag = startupInstrumentationTag;
+ return this;
+ }
+
public boolean isMinimalStartupDexEnabled() {
return enableMinimalStartupDex;
}
- public StartupOptions setEnableMinimalStartupDex() {
- enableMinimalStartupDex = true;
+ public StartupOptions setEnableMinimalStartupDex(boolean enableMinimalStartupDex) {
+ this.enableMinimalStartupDex = enableMinimalStartupDex;
return this;
}
@@ -65,7 +70,8 @@
return startupConfiguration;
}
- public void setStartupConfiguration(StartupConfiguration startupConfiguration) {
+ public StartupOptions setStartupConfiguration(StartupConfiguration startupConfiguration) {
this.startupConfiguration = startupConfiguration;
+ return this;
}
}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
index da68a15..78126fd 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Collection;
import java.util.LinkedHashSet;
@@ -32,9 +33,11 @@
return new EmptyStartupOrder();
}
- public abstract boolean contains(DexType type);
+ public abstract boolean contains(StartupClass<DexType> startupClass);
- public abstract Collection<DexType> getClasses();
+ public abstract boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType);
+
+ public abstract Collection<StartupClass<DexType>> getClasses();
public abstract boolean isEmpty();
@@ -42,5 +45,6 @@
public abstract StartupOrder toStartupOrderForWriting(AppView<?> appView);
- public abstract StartupOrder withoutPrunedItems(PrunedItems prunedItems);
+ public abstract StartupOrder withoutPrunedItems(
+ PrunedItems prunedItems, SyntheticItems syntheticItems);
}
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 82b0243..b721f6f 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -8,7 +8,10 @@
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.experimental.startup.StartupClass;
import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.experimental.startup.StartupOrder;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
@@ -93,20 +96,25 @@
FeatureSplit baseStartup;
if (startupConfiguration != null && startupConfiguration.hasStartupClasses()) {
- DexType representativeType = null;
- for (DexType startupClass : startupConfiguration.getStartupClasses()) {
- if (classToFeatureSplitMap.containsKey(startupClass)) {
+ StartupClass<DexType> representativeStartupClass = null;
+ for (StartupClass<DexType> startupClass : startupConfiguration.getStartupClasses()) {
+ if (startupClass.isSynthetic()
+ || classToFeatureSplitMap.containsKey(startupClass.getReference())) {
continue;
}
- classToFeatureSplitMap.put(startupClass, FeatureSplit.BASE_STARTUP);
- if (representativeType == null
- || startupClass.getDescriptor().compareTo(representativeType.getDescriptor()) > 0) {
- representativeType = startupClass;
+ classToFeatureSplitMap.put(startupClass.getReference(), FeatureSplit.BASE_STARTUP);
+ if (representativeStartupClass == null
+ || startupClass
+ .getReference()
+ .getDescriptor()
+ .compareTo(representativeStartupClass.getReference().getDescriptor())
+ > 0) {
+ representativeStartupClass = startupClass;
}
}
baseStartup = FeatureSplit.BASE_STARTUP;
representativeStringsForFeatureSplit.put(
- baseStartup, representativeType.toDescriptorString());
+ baseStartup, representativeStartupClass.getReference().toDescriptorString());
} else {
baseStartup = FeatureSplit.BASE;
}
@@ -141,10 +149,16 @@
}
public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
- Set<DexProgramClass> classes, SyntheticItems syntheticItems) {
+ Set<DexProgramClass> classes, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return getFeatureSplitClasses(
+ classes, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+ }
+
+ public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
+ Set<DexProgramClass> classes, StartupOrder startupOrder, SyntheticItems syntheticItems) {
Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
for (DexProgramClass clazz : classes) {
- FeatureSplit featureSplit = getFeatureSplit(clazz, syntheticItems);
+ FeatureSplit featureSplit = getFeatureSplit(clazz, startupOrder, syntheticItems);
if (featureSplit != null && !featureSplit.isBase()) {
result.computeIfAbsent(featureSplit, ignore -> Sets.newIdentityHashSet()).add(clazz);
}
@@ -152,18 +166,39 @@
return result;
}
- public FeatureSplit getFeatureSplit(ProgramDefinition clazz, SyntheticItems syntheticItems) {
- return getFeatureSplit(clazz.getContextType(), syntheticItems);
+ public FeatureSplit getFeatureSplit(
+ ProgramDefinition definition, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return getFeatureSplit(
+ definition, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
}
- public FeatureSplit getFeatureSplit(DexType type, SyntheticItems syntheticItems) {
+ public FeatureSplit getFeatureSplit(
+ ProgramDefinition definition, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+ return getFeatureSplit(definition.getContextType(), startupOrder, syntheticItems);
+ }
+
+ public FeatureSplit getFeatureSplit(
+ DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return getFeatureSplit(type, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+ }
+
+ public FeatureSplit getFeatureSplit(
+ DexType type, StartupOrder startupOrder, SyntheticItems syntheticItems) {
FeatureSplit feature = classToFeatureSplitMap.get(type);
if (feature != null) {
+ assert !syntheticItems.isSyntheticClass(type);
return feature;
}
- feature = syntheticItems.getContextualFeatureSplit(type, this);
- if (feature != null) {
- return feature;
+ if (syntheticItems != null) {
+ feature = syntheticItems.getContextualFeatureSplit(type, this);
+ if (feature != null && !feature.isBase()) {
+ return feature;
+ }
+ for (DexType context : syntheticItems.getSynthesizingContextTypes(type)) {
+ if (startupOrder.containsSyntheticClassesSynthesizedFrom(context)) {
+ return FeatureSplit.BASE_STARTUP;
+ }
+ }
}
return FeatureSplit.BASE;
}
@@ -175,33 +210,79 @@
return classToFeatureSplitMap.isEmpty();
}
- public boolean isInBase(DexProgramClass clazz, SyntheticItems syntheticItems) {
- return getFeatureSplit(clazz, syntheticItems).isBase();
+ public boolean isInBase(
+ DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return isInBase(clazz, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+ }
+
+ public boolean isInBase(
+ DexProgramClass clazz, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+ return getFeatureSplit(clazz, startupOrder, syntheticItems).isBase();
}
public boolean isInBaseOrSameFeatureAs(
- DexProgramClass clazz, ProgramDefinition context, SyntheticItems syntheticItems) {
- return isInBaseOrSameFeatureAs(clazz.getContextType(), context, syntheticItems);
+ DexProgramClass clazz,
+ ProgramDefinition context,
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return isInBaseOrSameFeatureAs(
+ clazz, context, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
}
public boolean isInBaseOrSameFeatureAs(
- DexType clazz, ProgramDefinition context, SyntheticItems syntheticItems) {
- FeatureSplit split = getFeatureSplit(clazz, syntheticItems);
- return split.isBase() || split == getFeatureSplit(context, syntheticItems);
+ DexProgramClass clazz,
+ ProgramDefinition context,
+ StartupOrder startupOrder,
+ SyntheticItems syntheticItems) {
+ return isInBaseOrSameFeatureAs(clazz.getContextType(), context, startupOrder, syntheticItems);
}
- public boolean isInFeature(DexProgramClass clazz, SyntheticItems syntheticItems) {
- return !isInBase(clazz, syntheticItems);
+ public boolean isInBaseOrSameFeatureAs(
+ DexType clazz,
+ ProgramDefinition context,
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return isInBaseOrSameFeatureAs(
+ clazz, context, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+ }
+
+ public boolean isInBaseOrSameFeatureAs(
+ DexType clazz,
+ ProgramDefinition context,
+ StartupOrder startupOrder,
+ SyntheticItems syntheticItems) {
+ FeatureSplit split = getFeatureSplit(clazz, startupOrder, syntheticItems);
+ return split.isBase() || split == getFeatureSplit(context, startupOrder, syntheticItems);
+ }
+
+ public boolean isInFeature(
+ DexProgramClass clazz, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+ return !isInBase(clazz, startupOrder, syntheticItems);
}
public boolean isInSameFeatureOrBothInSameBase(
- ProgramMethod a, ProgramMethod b, SyntheticItems syntheticItems) {
- return isInSameFeatureOrBothInSameBase(a.getHolder(), b.getHolder(), syntheticItems);
+ ProgramMethod a, ProgramMethod b, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return isInSameFeatureOrBothInSameBase(
+ a, b, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
}
public boolean isInSameFeatureOrBothInSameBase(
- DexProgramClass a, DexProgramClass b, SyntheticItems syntheticItems) {
- return getFeatureSplit(a, syntheticItems) == getFeatureSplit(b, syntheticItems);
+ ProgramMethod a, ProgramMethod b, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+ return isInSameFeatureOrBothInSameBase(
+ a.getHolder(), b.getHolder(), startupOrder, syntheticItems);
+ }
+
+ public boolean isInSameFeatureOrBothInSameBase(
+ DexProgramClass a, DexProgramClass b, AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return isInSameFeatureOrBothInSameBase(
+ a, b, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+ }
+
+ public boolean isInSameFeatureOrBothInSameBase(
+ DexProgramClass a,
+ DexProgramClass b,
+ StartupOrder startupOrder,
+ SyntheticItems syntheticItems) {
+ return getFeatureSplit(a, startupOrder, syntheticItems)
+ == getFeatureSplit(b, startupOrder, syntheticItems);
}
public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) {
@@ -240,6 +321,7 @@
}
public static boolean isInFeature(DexProgramClass clazz, AppView<AppInfoWithLiveness> appView) {
- return getMap(appView).isInFeature(clazz, appView.getSyntheticItems());
+ return getMap(appView)
+ .isInFeature(clazz, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index cf2c789..428d0fd 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.experimental.startup.StartupOrder;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.OptionalBool;
@@ -20,13 +21,18 @@
ProgramDefinition context,
AppView<? extends AppInfoWithClassHierarchy> appView) {
return isClassAccessible(
- clazz, context, appView.appInfo().getClassToFeatureSplitMap(), appView.getSyntheticItems());
+ clazz,
+ context,
+ appView.appInfo().getClassToFeatureSplitMap(),
+ appView.appInfo().getStartupOrder(),
+ appView.getSyntheticItems());
}
public static OptionalBool isClassAccessible(
DexClass clazz,
Definition context,
ClassToFeatureSplitMap classToFeatureSplitMap,
+ StartupOrder startupOrder,
SyntheticItems syntheticItems) {
if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
return OptionalBool.FALSE;
@@ -34,7 +40,7 @@
if (clazz.isProgramClass()
&& context.isProgramDefinition()
&& !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
- clazz.asProgramClass(), context.asProgramDefinition(), syntheticItems)) {
+ clazz.asProgramClass(), context.asProgramDefinition(), startupOrder, syntheticItems)) {
return OptionalBool.UNKNOWN;
}
return OptionalBool.TRUE;
@@ -72,6 +78,7 @@
initialResolutionHolder,
context,
appInfo.getClassToFeatureSplitMap(),
+ appInfo.getStartupOrder(),
appInfo.getSyntheticItems());
if (classAccessibility.isFalse()) {
return OptionalBool.FALSE;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 836f1a5..1a029f8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -140,7 +140,7 @@
getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
getMainDexInfo().withoutPrunedItems(prunedItems),
getMissingClasses(),
- getStartupOrder().withoutPrunedItems(prunedItems));
+ getStartupOrder().withoutPrunedItems(prunedItems, getSyntheticItems()));
}
public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index b5b0c60..6776d2c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -110,8 +110,7 @@
assert featureImplementations.size() <= 2;
// Check if service is defined feature
DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
- if (serviceClass != null
- && classToFeatureSplitMap.isInFeature(serviceClass, appView.getSyntheticItems())) {
+ if (serviceClass != null && classToFeatureSplitMap.isInFeature(serviceClass, appView)) {
return true;
}
for (Entry<FeatureSplit, List<DexType>> entry : featureImplementations.entrySet()) {
@@ -121,8 +120,7 @@
for (DexType implementationType : implementationTypes) {
DexProgramClass implementationClass = appView.definitionForProgramType(implementationType);
if (implementationClass != null
- && classToFeatureSplitMap.isInFeature(
- implementationClass, appView.getSyntheticItems())) {
+ && classToFeatureSplitMap.isInFeature(implementationClass, appView)) {
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index f6d0d19..2a9db3a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -492,6 +492,20 @@
return true;
}
+ public DexString prepend(String prefix, DexItemFactory dexItemFactory) {
+ return prepend(dexItemFactory.createString(prefix), dexItemFactory);
+ }
+
+ public DexString prepend(DexString prefix, DexItemFactory dexItemFactory) {
+ int newSize = prefix.size + this.size;
+ // Each string ends with a 0 terminating byte, hence the +/- 1.
+ byte[] newContent = new byte[prefix.content.length + this.content.length - 1];
+ System.arraycopy(prefix.content, 0, newContent, 0, prefix.content.length - 1);
+ System.arraycopy(
+ this.content, 0, newContent, prefix.content.length - 1, this.content.length - 1);
+ return dexItemFactory.createString(newSize, newContent);
+ }
+
public DexString withNewPrefix(
DexString prefix, DexString rewrittenPrefix, DexItemFactory factory) {
// Copy bytes over to avoid decoding/encoding cost.
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index c56866e..8730c35 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -1253,25 +1253,26 @@
}
BooleanBox seenNoAccess = new BooleanBox(false);
forEachFailureDependency(
- type -> {
- appInfo
- .contextIndependentDefinitionForWithResolutionResult(type)
- .forEachClassResolutionResult(
- clazz -> {
- AccessControl.isClassAccessible(
- clazz,
- context,
- appInfo.getClassToFeatureSplitMap(),
- appInfo.getSyntheticItems());
- });
- },
+ type ->
+ appInfo
+ .contextIndependentDefinitionForWithResolutionResult(type)
+ .forEachClassResolutionResult(
+ clazz ->
+ seenNoAccess.or(
+ AccessControl.isClassAccessible(
+ clazz,
+ context,
+ appInfo.getClassToFeatureSplitMap(),
+ appInfo.getStartupOrder(),
+ appInfo.getSyntheticItems())
+ .isPossiblyFalse())),
method -> {
DexClass holder = appInfo.definitionFor(method.getHolderType());
DexClassAndMethod classAndMethod = DexClassAndMethod.create(holder, method);
seenNoAccess.or(
AccessControl.isMemberAccessible(
classAndMethod, initialResolutionHolder, context, appInfo)
- .isFalse());
+ .isPossiblyFalse());
});
return seenNoAccess.get();
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
index 58054a2..73dfa2a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
@@ -19,10 +19,7 @@
@Override
public FeatureSplit getMergeKey(DexProgramClass clazz) {
- return appView
- .appInfo()
- .getClassToFeatureSplitMap()
- .getFeatureSplit(clazz, appView.getSyntheticItems());
+ return appView.appInfo().getClassToFeatureSplitMap().getFeatureSplit(clazz, appView);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index fe41e44..80718a7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -25,7 +25,6 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticItems;
public class SingleConstClassValue extends SingleConstValue {
@@ -110,9 +109,8 @@
return false;
}
ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
- SyntheticItems syntheticItems = appView.getSyntheticItems();
if (clazz.isProgramClass()
- && classToFeatureSplitMap.isInFeature(clazz.asProgramClass(), syntheticItems)) {
+ && classToFeatureSplitMap.isInFeature(clazz.asProgramClass(), appView)) {
return false;
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index d7442d5..da02bae 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -30,7 +30,6 @@
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ObjectAllocationInfoCollectionUtils;
-import com.android.tools.r8.synthesis.SyntheticItems;
public abstract class SingleFieldValue extends SingleValue {
@@ -129,9 +128,8 @@
return false;
}
ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
- SyntheticItems syntheticItems = appView.getSyntheticItems();
if (holder.isProgramClass()
- && classToFeatureSplitMap.isInFeature(holder.asProgramClass(), syntheticItems)) {
+ && classToFeatureSplitMap.isInFeature(holder.asProgramClass(), appView)) {
return false;
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index d0eab48..e195eac 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -158,7 +158,8 @@
// Finalize the desugaring of the processed classes. This may require processing (and
// reprocessing) of some methods.
List<ProgramMethod> needsProcessing =
- instructionDesugaringEventConsumer.finalizeDesugaring(appView, resultBuilder);
+ instructionDesugaringEventConsumer.finalizeDesugaring(
+ appView, executorService, resultBuilder);
if (!needsProcessing.isEmpty()) {
// Create a new processor context to ensure unique method processing contexts.
methodProcessor.newWave();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index fcf37fb..15c214a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar;
+import com.android.tools.r8.experimental.startup.StartupInstrumentation;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClasspathClass;
@@ -40,6 +41,8 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -87,6 +90,7 @@
private final D8MethodProcessor methodProcessor;
+ private final Set<DexProgramClass> synthesizedClasses = Sets.newConcurrentHashSet();
private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
new LinkedHashMap<>();
private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
@@ -96,9 +100,18 @@
this.methodProcessor = methodProcessor;
}
+ private void acceptClass(DexProgramClass clazz) {
+ synthesizedClasses.add(clazz);
+ }
+
+ private void acceptMethod(ProgramMethod method) {
+ acceptClass(method.getHolder());
+ }
+
@Override
public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
// Intentionally empty. Methods are moved when processing the interface definition.
+ acceptMethod(method);
}
@Override
@@ -113,21 +126,25 @@
@Override
public void acceptCollectionConversion(ProgramMethod arrayConversion) {
+ acceptMethod(arrayConversion);
methodProcessor.scheduleMethodForProcessing(arrayConversion, this);
}
@Override
public void acceptCovariantRetargetMethod(ProgramMethod method) {
+ acceptMethod(method);
methodProcessor.scheduleMethodForProcessing(method, this);
}
@Override
public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
+ acceptMethod(backportedMethod);
methodProcessor.scheduleMethodForProcessing(backportedMethod, this);
}
@Override
public void acceptRecordMethod(ProgramMethod method) {
+ acceptMethod(method);
methodProcessor.scheduleDesugaredMethodForProcessing(method);
}
@@ -141,11 +158,13 @@
@Override
public void acceptRecordClass(DexProgramClass recordClass) {
+ acceptClass(recordClass);
methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods());
}
@Override
public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
+ acceptClass(lambdaClass.getLambdaProgramClass());
synchronized (synthesizedLambdaClasses) {
synthesizedLambdaClasses.add(lambdaClass);
}
@@ -154,6 +173,7 @@
@Override
public void acceptConstantDynamicClass(
ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+ acceptClass(constantDynamicClass.getConstantDynamicProgramClass());
synchronized (synthesizedConstantDynamicClasses) {
synthesizedConstantDynamicClasses.add(constantDynamicClass);
}
@@ -176,17 +196,20 @@
@Override
public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
+ acceptMethod(closeMethod);
methodProcessor.scheduleMethodForProcessing(closeMethod, this);
}
@Override
public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+ acceptMethod(method);
methodProcessor.scheduleDesugaredMethodForProcessing(method);
}
@Override
public void acceptInvokeStaticInterfaceOutliningMethod(
ProgramMethod method, ProgramMethod context) {
+ acceptMethod(method);
methodProcessor.scheduleDesugaredMethodForProcessing(method);
}
@@ -202,20 +225,31 @@
@Override
public void acceptAPIConversion(ProgramMethod method) {
+ acceptMethod(method);
methodProcessor.scheduleDesugaredMethodForProcessing(method);
}
@Override
public void acceptCompanionClassClinit(ProgramMethod method) {
+ acceptMethod(method);
methodProcessor.scheduleDesugaredMethodForProcessing(method);
}
public List<ProgramMethod> finalizeDesugaring(
- AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
+ AppView<?> appView,
+ ExecutorService executorService,
+ ClassConverterResult.Builder classConverterResultBuilder)
+ throws ExecutionException {
List<ProgramMethod> needsProcessing = new ArrayList<>();
finalizeInvokeSpecialDesugaring(appView, needsProcessing::add);
finalizeLambdaDesugaring(classConverterResultBuilder, needsProcessing::add);
finalizeConstantDynamicDesugaring(needsProcessing::add);
+ if (new StartupInstrumentation(appView)
+ .instrumentClasses(synthesizedClasses, executorService)) {
+ for (DexProgramClass synthesizedClass : synthesizedClasses) {
+ needsProcessing.add(synthesizedClass.getProgramClassInitializer());
+ }
+ }
return needsProcessing;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 8e8541b..955183a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -593,6 +593,19 @@
// 1.7 or below, this will make a VerificationError on the input a VerificationError
// on the output. If the input was 1.8 or above the runtime behaviour (potential ICCE)
// will remain the same.
+ upgradeCfVersionToSupportInterfaceMethodInvoke(method);
+ }
+
+ private void leavingSuperInvokeToInterface(ProgramMethod method) {
+ // When leaving interface method invokes possibly upgrade the class file
+ // version, but don't go above the initial class file version. If the input was
+ // 1.7 or below, this will make a VerificationError on the input a VerificationError
+ // on the output. If the input was 1.8 or above the runtime behaviour (potential ICCE)
+ // will remain the same.
+ upgradeCfVersionToSupportInterfaceMethodInvoke(method);
+ }
+
+ private void upgradeCfVersionToSupportInterfaceMethodInvoke(ProgramMethod method) {
if (method.getHolder().hasClassFileVersion()) {
method
.getDefinition()
@@ -649,7 +662,7 @@
return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
}
- if (clazz.isInterface() && !clazz.isLibraryClass()) {
+ if (clazz.isInterface() && !resolutionResult.getResolutionPair().getHolder().isLibraryClass()) {
// NOTE: we intentionally don't desugar super calls into interface methods
// coming from android.jar since it is only possible in case v24+ version
// of android.jar is provided.
@@ -714,20 +727,27 @@
DesugarDescription emulatedInterfaceDesugaring =
computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
- if (!emulatedInterfaceDesugaring.needsDesugaring() && context.isDefaultMethod()) {
- return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowNSMERewrite(
- appView,
- invoke,
- () ->
- appView
- .reporter()
- .warning(
- new StringDiagnostic(
- "Interface method desugaring has inserted NoSuchMethodError replacing a"
- + " super call in "
- + context.toSourceString(),
- context.getOrigin())));
+ if (!emulatedInterfaceDesugaring.needsDesugaring()) {
+ if (context.isDefaultMethod()) {
+ return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowNSMERewrite(
+ appView,
+ invoke,
+ () ->
+ appView
+ .reporter()
+ .warning(
+ new StringDiagnostic(
+ "Interface method desugaring has inserted NoSuchMethodError replacing a"
+ + " super call in "
+ + context.toSourceString(),
+ context.getOrigin())));
+ } else {
+ return DesugarDescription.builder()
+ .addScanEffect(() -> leavingSuperInvokeToInterface(context))
+ .build();
+ }
}
+
return emulatedInterfaceDesugaring;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 75c275f..d3d0ece 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -45,7 +45,6 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.AssumeInfoCollection;
import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.google.common.collect.Sets;
@@ -164,12 +163,10 @@
return false;
}
- SyntheticItems syntheticItems = appView.getSyntheticItems();
ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
- if (!classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(
- singleTarget, method, syntheticItems)) {
+ if (!classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(singleTarget, method, appView)) {
// Still allow inlining if we inline from the base into a feature.
- if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder(), syntheticItems)) {
+ if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder(), appView)) {
whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 59e06c4..96b79b1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -386,9 +386,7 @@
.appInfo()
.getClassToFeatureSplitMap()
.isInBaseOrSameFeatureAs(
- resolvedMember.getHolderType(),
- context.asProgramMethod(),
- appView.getSyntheticItems())) {
+ resolvedMember.getHolderType(), context.asProgramMethod(), appView)) {
// We never inline into the base from a feature (calls should never happen) and we
// never inline between features, so this check should be sufficient.
return ConstraintWithTarget.NEVER;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 879646e..be176f2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -264,9 +264,7 @@
// Make sure the (base) type is visible.
ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
- if (AccessControl.isClassAccessible(
- baseClass, context, classToFeatureSplitMap, appView.getSyntheticItems())
- .isPossiblyFalse()) {
+ if (AccessControl.isClassAccessible(baseClass, context, appView).isPossiblyFalse()) {
return;
}
@@ -274,7 +272,7 @@
// or in the base.
assert !baseClass.isProgramClass()
|| classToFeatureSplitMap.isInBaseOrSameFeatureAs(
- baseClass.asProgramClass(), context, appView.getSyntheticItems());
+ baseClass.asProgramClass(), context, appView);
consumer.accept(type, baseClass);
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
index d42da83..0d764a5 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
@@ -18,7 +18,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
-import java.util.Set;
+import java.util.function.Predicate;
public abstract class ProguardMapReaderWithFiltering implements LineReader {
@@ -188,9 +188,9 @@
private int startIndex = 0;
private int endIndex = 0;
- private final Set<String> filter;
+ private final Predicate<String> filter;
- protected ProguardMapReaderWithFiltering(Set<String> filter) {
+ protected ProguardMapReaderWithFiltering(Predicate<String> filter) {
this.filter = filter;
}
@@ -220,7 +220,7 @@
seenFirstClass = true;
String classMapping = getBufferAsString(bytes);
String obfuscatedClassName = getObfuscatedClassName(classMapping);
- isInsideClassOfInterest = filter.contains(obfuscatedClassName);
+ isInsideClassOfInterest = filter.test(obfuscatedClassName);
return classMapping;
} else if (lineParserState == IS_COMMENT_SOURCE_FILE) {
return getBufferAsString(bytes);
@@ -284,7 +284,7 @@
private int temporaryBufferPosition = 0;
public ProguardMapReaderWithFilteringMappedBuffer(
- Path mappingFile, Set<String> classNamesOfInterest) throws IOException {
+ Path mappingFile, Predicate<String> classNamesOfInterest) throws IOException {
super(classNamesOfInterest);
fileChannel = FileChannel.open(mappingFile, StandardOpenOption.READ);
channelSize = fileChannel.size();
@@ -364,7 +364,7 @@
private int endReadIndex = 0;
public ProguardMapReaderWithFilteringInputBuffer(
- InputStream inputStream, Set<String> classNamesOfInterest) {
+ InputStream inputStream, Predicate<String> classNamesOfInterest) {
super(classNamesOfInterest);
this.inputStream = inputStream;
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
index ebba380..8cf9ace 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringMappedBuffer;
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Predicate;
public class ProguardMappingProviderBuilderImpl extends ProguardMappingProvider.Builder {
@@ -64,7 +65,7 @@
@Override
public ProguardMappingProvider build() {
try {
- Set<String> buildForClass = allowLookupAllClasses ? null : allowedLookup;
+ Predicate<String> buildForClass = allowLookupAllClasses ? null : allowedLookup::contains;
LineReader reader =
proguardMapProducer.isFileBacked()
? new ProguardMapReaderWithFilteringMappedBuffer(
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 677338b..f4e27a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -319,7 +319,7 @@
previous.getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
previous.getMainDexInfo().withoutPrunedItems(prunedItems),
previous.getMissingClasses(),
- previous.getStartupOrder().withoutPrunedItems(prunedItems),
+ previous.getStartupOrder().withoutPrunedItems(prunedItems, previous.getSyntheticItems()),
previous.deadProtoTypes,
pruneClasses(previous.liveTypes, prunedItems, executorService, futures),
pruneMethods(previous.targetedMethods, prunedItems, executorService, futures),
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 3addb2d..c731f7d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -460,6 +460,8 @@
private final InterfaceProcessor interfaceProcessor;
+ private final Thread mainThreadForTesting = Thread.currentThread();
+
Enqueuer(
AppView<? extends AppInfoWithClassHierarchy> appView,
ExecutorService executorService,
@@ -686,6 +688,7 @@
ProgramDerivedContext context,
BiConsumer<DexClass, ProgramDerivedContext> foundClassConsumer,
BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
+ assert verifyIsMainThread();
return internalDefinitionFor(type, context, foundClassConsumer, missingClassConsumer)
.toSingleClassWithProgramOverLibrary();
}
@@ -1958,6 +1961,11 @@
// Actual actions performed.
//
+ private boolean verifyIsMainThread() {
+ assert Thread.currentThread() == mainThreadForTesting;
+ return true;
+ }
+
private boolean verifyMethodIsTargeted(ProgramMethod method) {
DexEncodedMethod definition = method.getDefinition();
assert !definition.isClassInitializer() : "Class initializers are never targeted";
@@ -3943,7 +3951,7 @@
lambdaCallback.andThen(
(clazz, context) -> {
for (DexType itf : clazz.getLambdaProgramClass().getInterfaces()) {
- if (definitionFor(itf, context) == null) {
+ if (appInfo().definitionFor(itf, context) == null) {
for (ProgramMethod method :
clazz.getLambdaProgramClass().virtualProgramMethods()) {
synchronized (additions) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index b5898c5..37641f0 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -389,7 +389,7 @@
if (!appInfo
.getClassToFeatureSplitMap()
- .isInSameFeatureOrBothInSameBase(sourceClass, targetClass, appView.getSyntheticItems())) {
+ .isInSameFeatureOrBothInSameBase(sourceClass, targetClass, appView)) {
return false;
}
if (appView.appServices().allServiceTypes().contains(sourceClass.type)
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index 3b3aeea..8c2a3bf 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.DexProgramClass;
@@ -69,11 +70,18 @@
DexProgramClass clazz, DexType synthesizingContextType, AppView<?> appView) {
// A context that is itself synthetic must denote a synthesizing context from which to ensure
// hygiene. This synthesizing context type is encoded on the synthetic for intermediate builds.
- FeatureSplit featureSplit =
- appView
- .appInfoForDesugaring()
- .getClassToFeatureSplitMap()
- .getFeatureSplit(clazz, appView.getSyntheticItems());
+ FeatureSplit featureSplit;
+ if (appView.hasClassHierarchy()) {
+ AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+ appView.withClassHierarchy();
+ featureSplit =
+ appViewWithClassHierarchy
+ .appInfo()
+ .getClassToFeatureSplitMap()
+ .getFeatureSplit(clazz, appViewWithClassHierarchy);
+ } else {
+ featureSplit = FeatureSplit.BASE;
+ }
return new SynthesizingContext(synthesizingContextType, clazz.type, clazz.origin, featureSplit);
}
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 86206f6..eb764c0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -642,11 +642,13 @@
boolean mustBeRepresentative = isPinned(appView, synthetic);
EquivalenceGroup<T> equivalenceGroup = null;
for (EquivalenceGroup<T> group : groups) {
+ boolean includeContext =
+ intermediate || appView.options().getStartupOptions().isStartupInstrumentationEnabled();
if (synthetic.isEquivalentTo(
group.hasRepresentative()
? group.getRepresentative()
: group.getFirstNonRepresentativeMember(),
- intermediate,
+ includeContext,
appView.graphLens(),
classToFeatureSplitMap)) {
if (mustBeRepresentative && group.hasRepresentative()) {
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 e3d52a4..2c2f503 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -9,8 +9,10 @@
import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.startup.StartupOrder;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassResolutionResult;
import com.android.tools.r8.graph.ClasspathMethod;
@@ -209,14 +211,6 @@
return globalSyntheticsStrategy;
}
- // Empty collection for use only in tests and utilities.
- public static SyntheticItems empty() {
- return new SyntheticItems(
- State.FINALIZED,
- CommittedSyntheticsCollection.empty(null),
- GlobalSyntheticsStrategy.forNonSynthesizing());
- }
-
// Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
public static CommittedItems createInitialSyntheticItems(
DexApplication application, GlobalSyntheticsStrategy globalSyntheticsStrategy) {
@@ -534,13 +528,18 @@
private SynthesizingContext getSynthesizingContext(
ProgramDefinition context, AppView<?> appView) {
+ if (appView.hasClassHierarchy()) {
+ AppInfoWithClassHierarchy appInfo = appView.appInfoWithClassHierarchy();
+ return getSynthesizingContext(
+ context, appInfo.getClassToFeatureSplitMap(), appInfo.getStartupOrder());
+ }
return getSynthesizingContext(
- context, appView.appInfoForDesugaring().getClassToFeatureSplitMap());
+ context, ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(), StartupOrder.empty());
}
/** Used to find the synthesizing context for a new synthetic that is about to be created. */
private SynthesizingContext getSynthesizingContext(
- ProgramDefinition context, ClassToFeatureSplitMap featureSplits) {
+ ProgramDefinition context, ClassToFeatureSplitMap featureSplits, StartupOrder startupOrder) {
DexType contextType = context.getContextType();
SyntheticDefinition<?, ?, ?> existingDefinition = pending.definitions.get(contextType);
if (existingDefinition != null) {
@@ -556,7 +555,7 @@
.getContext();
}
// This context is not nested in an existing synthetic context so create a new "leaf" context.
- FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, this);
+ FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, startupOrder, this);
return SynthesizingContext.fromNonSyntheticInputContext(context, featureSplit);
}
diff --git a/src/main/java/com/android/tools/r8/utils/AccessUtils.java b/src/main/java/com/android/tools/r8/utils/AccessUtils.java
index 6e54e2a..84f644a 100644
--- a/src/main/java/com/android/tools/r8/utils/AccessUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AccessUtils.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticItems;
public class AccessUtils {
@@ -43,13 +42,11 @@
// If the new class is a program class, we need to check if it is in a feature.
if (newBaseClass.isProgramClass()) {
ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
- SyntheticItems syntheticItems = appView.getSyntheticItems();
if (classToFeatureSplitMap != null) {
FeatureSplit newFeatureSplit =
- classToFeatureSplitMap.getFeatureSplit(newBaseClass.asProgramClass(), syntheticItems);
+ classToFeatureSplitMap.getFeatureSplit(newBaseClass.asProgramClass(), appView);
if (!newFeatureSplit.isBase()
- && newFeatureSplit
- != classToFeatureSplitMap.getFeatureSplit(oldBaseType, syntheticItems)) {
+ && newFeatureSplit != classToFeatureSplitMap.getFeatureSplit(oldBaseType, appView)) {
return false;
}
}
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 14d7941..40c5a67 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.experimental.startup.StartupOrder;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.DexItemFactory;
@@ -627,8 +628,10 @@
classDescriptor -> {
if (featureSplitConfiguration != null) {
DexType type = dexItemFactory.createType(classDescriptor);
+ SyntheticItems syntheticItems = null;
FeatureSplit featureSplit =
- classToFeatureSplitMap.getFeatureSplit(type, SyntheticItems.empty());
+ classToFeatureSplitMap.getFeatureSplit(
+ type, StartupOrder.empty(), syntheticItems);
if (featureSplit != null && !featureSplit.isBase()) {
return featureSplitArchiveOutputStreams.get(featureSplit);
}
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
deleted file mode 100644
index 00412d9..0000000
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ /dev/null
@@ -1,313 +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.utils;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
-import com.android.tools.r8.origin.PathOrigin;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * Provides a mappings of classes to modules. The structure of the input file is as follows:
- * packageOrClass:module
- *
- * <p>Lines with a # prefix are ignored.
- *
- * <p>We will do most specific matching, i.e.,
- * <pre>
- * com.google.foobar.*:feature2
- * com.google.*:base
- * </pre>
- * will put everything in the com.google namespace into base, except classes in com.google.foobar
- * that will go to feature2. Class based mappings takes precedence over packages (since they are
- * more specific):
- * <pre>
- * com.google.A:feature2
- * com.google.*:base
- * </pre>
- * Puts A into feature2, and all other classes from com.google into base.
- *
- * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
- * placement tool.
- */
-@Keep
-public final class FeatureClassMapping {
-
- Map<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
- Map<String, String> parseNonClassRules = new HashMap<>();
- boolean usesOnlyExactMappings = true;
-
- Set<FeaturePredicate> mappings = new HashSet<>();
-
- Path mappingFile;
- String baseName = DEFAULT_BASE_NAME;
-
- static final String DEFAULT_BASE_NAME = "base";
-
- static final String COMMENT = "#";
- static final String SEPARATOR = ":";
-
- public String getBaseName() {
- return baseName;
- }
-
- private static class SpecificationOrigin extends PathOrigin {
-
- public SpecificationOrigin(Path path) {
- super(path);
- }
-
- @Override
- public String part() {
- return "specification file '" + super.part() + "'";
- }
- }
-
- private static class JarFileOrigin extends PathOrigin {
-
- public JarFileOrigin(Path path) {
- super(path);
- }
-
- @Override
- public String part() {
- return "jar file '" + super.part() + "'";
- }
- }
-
- public static FeatureClassMapping fromSpecification(Path file) throws FeatureMappingException {
- return fromSpecification(file, new DiagnosticsHandler() {});
- }
-
- public static FeatureClassMapping fromSpecification(Path file, DiagnosticsHandler reporter)
- throws FeatureMappingException {
- FeatureClassMapping mapping = new FeatureClassMapping();
- List<String> lines = null;
- try {
- lines = FileUtils.readAllLines(file);
- } catch (IOException e) {
- ExceptionDiagnostic error = new ExceptionDiagnostic(e, new SpecificationOrigin(file));
- reporter.error(error);
- throw new AbortException(error);
- }
- for (int i = 0; i < lines.size(); i++) {
- String line = lines.get(i);
- mapping.parseAndAdd(line, i);
- }
- return mapping;
- }
-
- public static class Internal {
- private static List<String> getClassFileDescriptors(String jar, DiagnosticsHandler reporter) {
- Path jarPath = Paths.get(jar);
- try {
- return new ArchiveClassFileProvider(jarPath).getClassDescriptors()
- .stream()
- .map(DescriptorUtils::descriptorToJavaType)
- .collect(Collectors.toList());
- } catch (IOException e) {
- ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(jarPath));
- reporter.error(error);
- throw new AbortException(error);
- }
- }
-
- private static List<String> getNonClassFiles(String jar, DiagnosticsHandler reporter) {
- try (ZipFile zipfile = new ZipFile(jar, StandardCharsets.UTF_8)) {
- return zipfile.stream()
- .filter(entry -> !ZipUtils.isClassFile(entry.getName()))
- .map(ZipEntry::getName)
- .collect(Collectors.toList());
- } catch (IOException e) {
- ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar)));
- reporter.error(error);
- throw new AbortException(error);
- }
- }
-
- public static FeatureClassMapping fromJarFiles(
- List<FeatureJar> featureJars, List<String> baseJars, String baseName,
- DiagnosticsHandler reporter)
- throws FeatureMappingException {
- FeatureClassMapping mapping = new FeatureClassMapping();
- if (baseName != null) {
- mapping.baseName = baseName;
- }
- for (FeatureJar featureJar : featureJars) {
- for (String javaType : getClassFileDescriptors(featureJar.getJar(), reporter)) {
- mapping.addMapping(javaType, featureJar.getOutputName());
- }
- for (String nonClass : getNonClassFiles(featureJar.getJar(), reporter)) {
- mapping.addNonClassMapping(nonClass, featureJar.getOutputName());
- }
- }
- for (String baseJar : baseJars) {
- for (String javaType : getClassFileDescriptors(baseJar, reporter)) {
- mapping.addBaseMapping(javaType);
- }
- for (String nonClass : getNonClassFiles(baseJar, reporter)) {
- mapping.addBaseNonClassMapping(nonClass);
- }
- }
- assert mapping.usesOnlyExactMappings;
- return mapping;
- }
-
- }
-
- private FeatureClassMapping() {}
-
- public void addBaseMapping(String clazz) throws FeatureMappingException {
- addMapping(clazz, baseName);
- }
-
- public void addBaseNonClassMapping(String name) {
- addNonClassMapping(name, baseName);
- }
-
- public void addMapping(String clazz, String feature) throws FeatureMappingException {
- addRule(clazz, feature, 0);
- }
-
- public void addNonClassMapping(String name, String feature) {
- // If a non-class file is present in multiple features put the resource in the base.
- parseNonClassRules.put(name, parseNonClassRules.containsKey(name) ? baseName : feature);
- }
-
- FeatureClassMapping(List<String> lines) throws FeatureMappingException {
- for (int i = 0; i < lines.size(); i++) {
- String line = lines.get(i);
- parseAndAdd(line, i);
- }
- }
-
- public String featureForClass(String clazz) {
- if (usesOnlyExactMappings) {
- return parsedRules.getOrDefault(clazz, baseName);
- } else {
- FeaturePredicate bestMatch = null;
- for (FeaturePredicate mapping : mappings) {
- if (mapping.match(clazz)) {
- if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) {
- bestMatch = mapping;
- }
- }
- }
- if (bestMatch == null) {
- return baseName;
- }
- return bestMatch.feature;
- }
- }
-
- public String featureForNonClass(String nonClass) {
- return parseNonClassRules.getOrDefault(nonClass, baseName);
- }
-
- private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
- if (line.startsWith(COMMENT)) {
- return; // Ignore comments
- }
- if (line.isEmpty()) {
- return; // Ignore blank lines
- }
-
- if (!line.contains(SEPARATOR)) {
- error("Mapping lines must contain a " + SEPARATOR, lineNumber);
- }
- String[] values = line.split(SEPARATOR);
- if (values.length != 2) {
- error("Mapping lines can only contain one " + SEPARATOR, lineNumber);
- }
-
- String predicate = values[0];
- String feature = values[1];
- addRule(predicate, feature, lineNumber);
- }
-
- private void addRule(String predicate, String feature, int lineNumber)
- throws FeatureMappingException {
- if (parsedRules.containsKey(predicate)) {
- if (!parsedRules.get(predicate).equals(feature)) {
- error("Redefinition of predicate " + predicate + "not allowed", lineNumber);
- }
- return; // Already have this rule.
- }
- parsedRules.put(predicate, feature);
- FeaturePredicate featurePredicate = new FeaturePredicate(predicate, feature);
- mappings.add(featurePredicate);
- usesOnlyExactMappings &= featurePredicate.isExactmapping();
- }
-
- private void error(String error, int line) throws FeatureMappingException {
- throw new FeatureMappingException(
- "Invalid mappings specification: " + error + "\n in file " + mappingFile + ":" + line);
- }
-
- @Keep
- public static class FeatureMappingException extends Exception {
- FeatureMappingException(String message) {
- super(message);
- }
- }
-
- /** A feature predicate can either be a wildcard or class predicate. */
- private static class FeaturePredicate {
- private static Pattern identifier = Pattern.compile("[A-Za-z_\\-][A-Za-z0-9_$\\-]*");
- final String predicate;
- final String feature;
- final boolean isCatchAll;
- // False implies class predicate.
- final boolean isWildcard;
-
- FeaturePredicate(String predicate, String feature) throws FeatureMappingException {
- isWildcard = predicate.endsWith(".*");
- isCatchAll = predicate.equals("*");
- if (isCatchAll) {
- this.predicate = "";
- } else if (isWildcard) {
- String packageName = predicate.substring(0, predicate.length() - 2);
- if (!DescriptorUtils.isValidJavaType(packageName)) {
- throw new FeatureMappingException(packageName + " is not a valid identifier");
- }
- // Prefix of a fully-qualified class name, including a terminating dot.
- this.predicate = predicate.substring(0, predicate.length() - 1);
- } else {
- if (!DescriptorUtils.isValidJavaType(predicate)) {
- throw new FeatureMappingException(predicate + " is not a valid identifier");
- }
- this.predicate = predicate;
- }
- this.feature = feature;
- }
-
- boolean match(String className) {
- if (isCatchAll) {
- return true;
- } else if (isWildcard) {
- return className.startsWith(predicate);
- } else {
- return className.equals(predicate);
- }
- }
-
- boolean isExactmapping() {
- return !isWildcard && !isCatchAll;
- }
- }
-}
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 c836c46..ee15be2 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -21,9 +21,12 @@
import com.android.tools.r8.Version;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Backend;
import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dex.MixedSectionLayoutStrategy;
+import com.android.tools.r8.dex.VirtualFile;
import com.android.tools.r8.dump.DumpOptions;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
@@ -104,6 +107,7 @@
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -259,6 +263,16 @@
horizontalClassMergerOptions.setRestrictToSynthetics();
}
+ public void configureAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+ if (!isAndroidPlatformBuild) {
+ return;
+ }
+ // Configure options according to platform build assumptions.
+ // See go/r8platformflag and b/232073181.
+ minApiLevel = ANDROID_PLATFORM;
+ apiModelingOptions().disableMissingApiModeling();
+ }
+
public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
// To print memory one also have to enable printtimes.
public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
@@ -853,6 +867,10 @@
return startupOptions;
}
+ public TestingOptions getTestingOptions() {
+ return testing;
+ }
+
private static Set<String> getExtensiveLoggingFilter() {
String property = System.getProperty("com.android.tools.r8.extensiveLoggingFilter");
if (property != null) {
@@ -1756,6 +1774,15 @@
? NondeterministicIROrdering.getInstance()
: IdentityIROrdering.getInstance();
+ public BiFunction<MixedSectionLayoutStrategy, VirtualFile, MixedSectionLayoutStrategy>
+ mixedSectionLayoutStrategyInspector = (strategy, virtualFile) -> strategy;
+
+ public void setMixedSectionLayoutStrategyInspector(
+ BiFunction<MixedSectionLayoutStrategy, VirtualFile, MixedSectionLayoutStrategy>
+ mixedSectionLayoutStrategyInspector) {
+ this.mixedSectionLayoutStrategyInspector = mixedSectionLayoutStrategyInspector;
+ }
+
public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null;
public Consumer<String> processingContextsConsumer = null;
@@ -1778,6 +1805,8 @@
public Consumer<Deque<ProgramMethodSet>> waveModifier = waves -> {};
+ public Consumer<DebugRepresentation> debugRepresentationCallback = null;
+
/**
* If this flag is enabled, we will also compute the set of possible targets for invoke-
* interface and invoke-virtual instructions that target a library method, and add the
@@ -1819,6 +1848,10 @@
public boolean dontCreateMarkerInD8 = false;
public boolean forceJumboStringProcessing = false;
public boolean forcePcBasedEncoding = false;
+ public int pcBasedDebugEncodingOverheadThreshold =
+ System.getProperty("com.android.tools.r8.pc2pcOverheadThreshold") != null
+ ? Integer.parseInt(System.getProperty("com.android.tools.r8.pc2pcOverheadThreshold"))
+ : 200000;
public Set<Inliner.Reason> validInliningReasons = null;
public boolean noLocalsTableOnInput = false;
public boolean forceNameReflectionOptimization = false;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 389fd36..660a5fd 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -76,9 +76,8 @@
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -354,10 +353,10 @@
private interface PcBasedDebugInfoRecorder {
/** Callback to record a code object with a given max instruction PC and parameter count. */
- void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc);
+ void recordPcMappingFor(ProgramMethod method, int maxEncodingPc);
/** Callback to record a code object with only a single "line". */
- void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc);
+ void recordSingleLineFor(ProgramMethod method, int maxEncodingPc);
/**
* Install the correct debug info objects.
@@ -406,32 +405,44 @@
singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
}
+ private int getLastInstructionOffset(DexCode code) {
+ return DebugRepresentation.getLastExecutableInstruction(code).getOffset();
+ }
+
+ private boolean cantAddToClearSet(ProgramMethod method) {
+ assert method.getDefinition().getCode().isDexCode();
+ if (singleLineCodesToClear == null) {
+ return true;
+ }
+ singleLineCodesToClear.add(method.getDefinition().getCode().asDexCode());
+ return false;
+ }
+
@Override
- public void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc) {
+ public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
+ assert method.getDefinition().getCode().isDexCode();
+ int parameterCount = method.getParameters().size();
+ DexCode code = method.getDefinition().getCode().asDexCode();
assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(code, maxEncodingPc);
codesToUpdate.add(new UpdateInfo(code, parameterCount, maxEncodingPc));
}
@Override
- public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
- if (singleLineCodesToClear != null) {
- singleLineCodesToClear.add(code);
- return;
+ public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
+ if (cantAddToClearSet(method)) {
+ recordPcMappingFor(method, maxEncodingPc);
}
- recordPcMappingFor(code, parameterCount, maxEncodingPc);
}
@Override
public void updateDebugInfoInCodeObjects() {
- Object2ReferenceMap<UpdateInfo, DexDebugInfo> debugInfos =
- new Object2ReferenceOpenHashMap<>();
+ Map<UpdateInfo, DexDebugInfo> debugInfos = new HashMap<>();
codesToUpdate.forEach(
entry -> {
assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
entry.code, entry.maxEncodingPc);
DexDebugInfo debugInfo =
- debugInfos.computeIfAbsent(
- entry, key -> buildPc2PcDebugInfo(key.maxEncodingPc, key.paramCount));
+ debugInfos.computeIfAbsent(entry, Pc2PcMappingSupport::buildPc2PcDebugInfo);
assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
entry.code.setDebugInfo(debugInfo);
});
@@ -440,23 +451,26 @@
}
}
- private static DexDebugInfo buildPc2PcDebugInfo(int lastInstructionPc, int parameterCount) {
- return new DexDebugInfo.PcBasedDebugInfo(parameterCount, lastInstructionPc);
+ private static DexDebugInfo buildPc2PcDebugInfo(UpdateInfo info) {
+ return new DexDebugInfo.PcBasedDebugInfo(info.paramCount, info.maxEncodingPc);
}
}
private static class NativePcSupport implements PcBasedDebugInfoRecorder {
- @Override
- public void recordPcMappingFor(DexCode code, int length, int maxEncodingPc) {
- // Strip the info in full as the runtime will emit the PC directly.
- code.setDebugInfo(null);
+ private void clearDebugInfo(ProgramMethod method) {
+ // Always strip the info in full as the runtime will emit the PC directly.
+ method.getDefinition().getCode().asDexCode().setDebugInfo(null);
}
@Override
- public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
- // Strip the info at once as it does not conflict with any PC mapping update.
- code.setDebugInfo(null);
+ public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
+ clearDebugInfo(method);
+ }
+
+ @Override
+ public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
+ clearDebugInfo(method);
}
@Override
@@ -567,7 +581,7 @@
List<MappedPosition> mappedPositions;
Code code = definition.getCode();
int pcEncodingCutoff =
- methods.size() == 1 ? representation.getDexPcEncodingCutoff(clazz, definition) : -1;
+ methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
boolean canUseDexPc = pcEncodingCutoff > 0;
if (code != null) {
if (code.isDexCode()
@@ -575,7 +589,7 @@
if (canUseDexPc) {
mappedPositions =
optimizeDexCodePositionsForPc(
- definition, appView, kotlinRemapper, pcBasedDebugInfo, pcEncodingCutoff);
+ method, pcEncodingCutoff, appView, kotlinRemapper, pcBasedDebugInfo);
} else {
mappedPositions =
optimizeDexCodePositions(
@@ -749,8 +763,7 @@
if (definition.getCode().isDexCode()
&& definition.getCode().asDexCode().getDebugInfo()
== DexDebugInfoForSingleLineMethod.getInstance()) {
- pcBasedDebugInfo.recordSingleLineFor(
- definition.getCode().asDexCode(), method.getParameters().size(), pcEncodingCutoff);
+ pcBasedDebugInfo.recordSingleLineFor(method, pcEncodingCutoff);
}
} // for each method of the group
} // for each method group, grouped by name
@@ -1194,15 +1207,16 @@
}
private static List<MappedPosition> optimizeDexCodePositionsForPc(
- DexEncodedMethod method,
+ ProgramMethod method,
+ int pcEncodingCutoff,
AppView<?> appView,
PositionRemapper positionRemapper,
- PcBasedDebugInfoRecorder debugInfoProvider,
- int pcEncodingCutoff) {
+ PcBasedDebugInfoRecorder debugInfoProvider) {
List<MappedPosition> mappedPositions = new ArrayList<>();
// Do the actual processing for each method.
- DexCode dexCode = method.getCode().asDexCode();
- EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method, dexCode, appView);
+ DexCode dexCode = method.getDefinition().getCode().asDexCode();
+ EventBasedDebugInfo debugInfo =
+ getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
IntBox firstDefaultEventPc = new IntBox(-1);
BooleanBox singleOriginalLine = new BooleanBox(true);
Pair<Integer, Position> lastPosition = new Pair<>();
@@ -1269,10 +1283,9 @@
&& lastPosition.getSecond() != null
&& (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
- debugInfoProvider.recordSingleLineFor(
- dexCode, method.getParameters().size(), pcEncodingCutoff);
+ debugInfoProvider.recordSingleLineFor(method, pcEncodingCutoff);
} else {
- debugInfoProvider.recordPcMappingFor(dexCode, debugInfo.parameters.length, pcEncodingCutoff);
+ debugInfoProvider.recordPcMappingFor(method, pcEncodingCutoff);
}
return mappedPositions;
}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 59011c5..7275934 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -184,7 +184,12 @@
return join(LINE_SEPARATOR, collection, BraceType.NONE);
}
+
public static List<String> splitLines(String content) {
+ return splitLines(content, false);
+ }
+
+ public static List<String> splitLines(String content, boolean includeTrailingEmptyLine) {
int length = content.length();
List<String> lines = new ArrayList<>();
int start = 0;
@@ -201,7 +206,7 @@
}
if (start < length) {
String line = content.substring(start);
- if (!line.isEmpty()) {
+ if (includeTrailingEmptyLine || !line.isEmpty()) {
lines.add(line);
}
}
diff --git a/src/main/keep.txt b/src/main/keep.txt
index e376924..deb8e6d 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -12,7 +12,6 @@
-keep public class com.android.tools.r8.D8 { public static void main(java.lang.String[]); }
-keep public class com.android.tools.r8.R8 { public static void main(java.lang.String[]); }
-keep public class com.android.tools.r8.ExtractMarker { public static void main(java.lang.String[]); }
--keep public class com.android.tools.r8.dexsplitter.DexSplitter { public static void main(java.lang.String[]); }
-keep public class com.android.tools.r8.Version { public static final java.lang.String LABEL; }
-keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 302b84a..8b951c9 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.ApiModelTestingOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
@@ -901,6 +902,14 @@
numThreadsOptionInvalid("two");
}
+ @Test
+ public void defaultApiModelingState() throws Exception {
+ ApiModelTestingOptions options = parse("").getInternalOptions().apiModelingOptions();
+ assertTrue(options.enableApiCallerIdentification);
+ assertTrue(options.enableOutliningOfMethods);
+ assertTrue(options.enableStubbingOfClasses);
+ }
+
@Override
String[] requiredArgsForTest() {
return new String[0];
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index fe500f8..f343c0f 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -118,8 +118,10 @@
getApp().writeToDirectory(out, OutputMode.DexIndexed);
consumers[0].accept(new CodeInspector(out.resolve("classes.dex"), getProguardMap()));
for (int i = 1; i < consumers.length; i++) {
- consumers[i].accept(
- new CodeInspector(out.resolve("classes" + (i + 1) + ".dex"), getProguardMap()));
+ Path dex = out.resolve("classes" + (i + 1) + ".dex");
+ CodeInspector inspector =
+ dex.toFile().exists() ? new CodeInspector(dex, getProguardMap()) : CodeInspector.empty();
+ consumers[i].accept(inspector);
}
return self();
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index eb7df8c..b22d6d3 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -2117,7 +2117,7 @@
public static class ProcessResult {
public final int exitCode;
- public final String stdout;
+ public String stdout;
public final String stderr;
public final String command;
@@ -2132,6 +2132,10 @@
this(exitCode, stdout, stderr, null);
}
+ public void setStdout(String stdout) {
+ this.stdout = stdout;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
new file mode 100644
index 0000000..0726238
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
@@ -0,0 +1,66 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentHashMap.KeySetView;
+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 ApiModelCovariantReturnTypeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelCovariantReturnTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method main = Main.class.getDeclaredMethod("main", String[].class);
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .applyIf(
+ parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+ b -> b.addDontWarn(KeySetView.class))
+ .addKeepMainRule(Main.class)
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(
+ addTracedApiReferenceLevelCallBack(
+ (method, apiLevel) -> {
+ if (Reference.methodFromMethod(main).equals(method)) {
+ // TODO(b/232891189): Should be api level 28.
+ assertNull(apiLevel);
+ }
+ }))
+ .compile();
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
+ KeySetView<String, String> strings = map.keySet();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
index ae8d0b6..e77e3d9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
@@ -7,12 +7,13 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
import com.android.tools.r8.experimental.startup.StartupConfiguration;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -47,11 +48,17 @@
options
.getStartupOptions()
.setStartupConfiguration(
- new StartupConfiguration(
- startupClasses.stream()
- .map(clazz -> toDexType(clazz, dexItemFactory))
- .collect(Collectors.toList()),
- Collections.emptyList()));
+ StartupConfiguration.builder()
+ .apply(
+ builder ->
+ startupClasses.forEach(
+ startupClass ->
+ builder.addStartupClass(
+ StartupClass.<DexType>builder()
+ .setReference(
+ toDexType(startupClass, dexItemFactory))
+ .build())))
+ .build());
})
.addHorizontallyMergedClassesInspector(
inspector ->
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 0dcd986..017c652 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.ToolHelper.isTestingR8Lib;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.androidplatformbuild.AndroidPlatformBuildApiTest;
import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
@@ -45,7 +46,8 @@
ImmutableList.of(
GlobalSyntheticsTest.ApiTest.class,
CommandLineParserTest.ApiTest.class,
- EnableMissingLibraryApiModelingTest.ApiTest.class);
+ EnableMissingLibraryApiModelingTest.ApiTest.class,
+ AndroidPlatformBuildApiTest.ApiTest.class);
private final TemporaryFolder temp;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
new file mode 100644
index 0000000..07d01cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
@@ -0,0 +1,94 @@
+// 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.androidplatformbuild;
+
+import static com.android.tools.r8.MarkerMatcher.markerMinApi;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+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 com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+public class AndroidPlatformBuildApiTest extends CompilerApiTestRunner {
+
+ public AndroidPlatformBuildApiTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ runTest(test::runD8);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ runTest(test::runR8);
+ }
+
+ private void runTest(ThrowingConsumer<ProgramConsumer, Exception> test) throws Exception {
+ Path output = temp.newFolder().toPath().resolve("out.jar");
+ test.accept(new DexIndexedConsumer.ArchiveConsumer(output));
+ assertThat(
+ new CodeInspector(output).getMarkers(),
+ CoreMatchers.everyItem(markerMinApi(AndroidApiLevel.ANDROID_PLATFORM)));
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runD8(ProgramConsumer programConsumer) throws Exception {
+ D8.run(
+ D8Command.builder()
+ .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(programConsumer)
+ .setAndroidPlatformBuild(true)
+ .build());
+ }
+
+ public void runR8(ProgramConsumer programConsumer) throws Exception {
+ R8.run(
+ R8Command.builder()
+ .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+ .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(programConsumer)
+ .setAndroidPlatformBuild(true)
+ .build());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ runD8(DexIndexedConsumer.emptyConsumer());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runR8(DexIndexedConsumer.emptyConsumer());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
index 6891000..08b55f8 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
@@ -4,15 +4,20 @@
package com.android.tools.r8.debuginfo;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeFalse;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.naming.retrace.StackTrace;
import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
import com.android.tools.r8.utils.BooleanUtils;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -91,6 +96,25 @@
b -> b.getBuilder().setSourceFileProvider(environment -> CUSTOM_SOURCE_FILE))
.run(parameters.getRuntime(), TestClass.class)
.assertFailureWithErrorThatThrows(NullPointerException.class)
+ .inspectFailure(
+ i -> {
+ if (parameters.isDexRuntime()) {
+ Set<DexDebugInfo> debugInfos =
+ i.allClasses().stream()
+ .flatMap(c -> c.allMethods().stream())
+ .map(m -> m.getMethod().getCode().asDexCode().getDebugInfo())
+ .collect(Collectors.toSet());
+ if (isCompileWithPcAsLineNumberSupport() && !customSourceFile) {
+ // If debug info is stripped all items are null pointers.
+ assertEquals(Collections.singleton(null), debugInfos);
+ } else {
+ // If debug info remains it is two canonical items and one null pointer.
+ // The presence of 'null' debug info items is for methods with no actual lines at
+ // all.
+ assertEquals(3, debugInfos.size());
+ }
+ }
+ })
.inspectOriginalStackTrace(
stackTrace ->
assertThat(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index a9aae3f..af9ac89 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -217,6 +217,8 @@
// Manually construct the R8 command as the test builder will change defaults compared
// to the CLI invocation (eg, compressed and pg-map output).
Builder builder = R8Command.builder().setOutput(outputThroughCf, OutputMode.DexIndexed);
+ // Set API model until default changes.
+ builder.setEnableExperimentalMissingLibraryApiModeling(true);
getSharedBuilder().accept(builder);
ToolHelper.runR8WithOptionsModificationOnly(
builder.build(),
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
similarity index 83%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java
rename to src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
index 8c5cdd0..b5ad377 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.nestaccesscontrol;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -11,17 +12,19 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDex.Host.Member1;
-import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDex.Host.Member2;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDexTest.Host.Member1;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDexTest.Host.Member2;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.TypeSubject;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -33,7 +36,7 @@
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
-public class NestAttributesInDex extends TestBase {
+public class NestAttributesInDexTest extends TestBase {
@Parameter() public TestParameters parameters;
@@ -62,15 +65,18 @@
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
- private void inspect(CodeInspector inspector) {
+ private void inspect(CodeInspector inspector, boolean emitNestAnnotationsInDex) {
ClassSubject host = inspector.clazz(Host.class);
ClassSubject member1 = inspector.clazz(Member1.class);
ClassSubject member2 = inspector.clazz(Member2.class);
assertEquals(
- ImmutableList.of(member1.asTypeSubject(), member2.asTypeSubject()),
+ emitNestAnnotationsInDex
+ ? ImmutableList.of(member1.asTypeSubject(), member2.asTypeSubject())
+ : Collections.emptyList(),
host.getFinalNestMembersAttribute());
- assertEquals(host.asTypeSubject(), member1.getFinalNestHostAttribute());
- assertEquals(host.asTypeSubject(), member2.getFinalNestHostAttribute());
+ TypeSubject expectedNestHostAttribute = emitNestAnnotationsInDex ? host.asTypeSubject() : null;
+ assertEquals(expectedNestHostAttribute, member1.getFinalNestHostAttribute());
+ assertEquals(expectedNestHostAttribute, member2.getFinalNestHostAttribute());
ClassSubject otherHost = inspector.clazz(OtherHost.class);
assertNull(otherHost.getFinalNestHostAttribute());
assertEquals(0, otherHost.getFinalNestMembersAttribute().size());
@@ -83,17 +89,29 @@
.addProgramClassFileData(getTransformedClasses())
.addProgramClasses(OtherHost.class)
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(
- options -> {
- options.emitNestAnnotationsInDex = true;
- })
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
.compile()
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, true))
.run(parameters.getRuntime(), TestClass.class)
// No Art versions have support for nest attributes yet.
.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
}
+ @Test
+ public void testD8NoDesugar() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .addProgramClasses(OtherHost.class)
+ .disableDesugaring()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> assertFalse(options.emitNestAnnotationsInDex))
+ .compile()
+ .inspect(inspector -> inspect(inspector, false))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+
public Collection<byte[]> getTransformedClasses() throws Exception {
ClassFileTransformer transformer =
transformer(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java
new file mode 100644
index 0000000..6e19e01
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java
@@ -0,0 +1,183 @@
+// 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.desugaring.interfacemethods;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+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.ToolHelper;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.util.function.Function;
+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 InvokeSuperInDefaultMethodResolvingToLibraryTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("8");
+
+ private void inspect(CodeInspector inspector) {
+ assertTrue(
+ inspector
+ .clazz(B.class)
+ .uniqueMethodWithName("compose")
+ .streamInstructions()
+ .filter(InstructionSubject::isInvoke)
+ .map(invoke -> invoke.getMethod().getHolderType().toString())
+ .noneMatch(name -> name.endsWith("$-CC")));
+ }
+
+ @Test
+ public void testDesugaring() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .maxSupportedApiLevel()
+ .isLessThan(AndroidApiLevel.N),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ @Test
+ public void testDesugaringWithApiLevelCheck() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addAndroidBuildVersion(parameters.getRuntime().asDex().maxSupportedApiLevel())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClassWithApiLevelCheck.class)
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .maxSupportedApiLevel()
+ .isLessThan(AndroidApiLevel.N),
+ r -> r.assertSuccessWithOutputLines("No call"),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ try {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ // .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .maxSupportedApiLevel()
+ .isLessThan(AndroidApiLevel.N),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ } catch (CompilationFailedException e) {
+ // TODO(b/235184674): Fix this.
+ assertTrue(parameters.isCfRuntime());
+ }
+ }
+
+ // TODO(b/235184674): Fix this.
+ @Test(expected = CompilationFailedException.class)
+ public void testR8WithApiLevelCheck() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClassWithApiLevelCheck.class)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClassWithApiLevelCheck.class)
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .maxSupportedApiLevel()
+ .isLessThan(AndroidApiLevel.N),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ static class TestClass {
+
+ private static void m(C c) {
+ System.out.println(c.compose(c).apply(2));
+ }
+
+ public static void main(String[] args) {
+ m(new C());
+ }
+ }
+
+ static class TestClassWithApiLevelCheck {
+
+ private static void m(C c) {
+ System.out.println(c.compose(c).apply(2));
+ }
+
+ public static void main(String[] args) {
+ if (AndroidBuildVersion.VERSION >= 24) {
+ m(new C());
+ } else {
+ System.out.println("No call");
+ }
+ }
+ }
+
+ interface MyFunction<V, R> extends Function<V, R> {}
+
+ abstract static class B<V, R> implements MyFunction<V, R> {
+
+ @Override
+ public <V1> Function<V1, R> compose(Function<? super V1, ? extends V> before) {
+ return MyFunction.super.compose(before);
+ }
+ }
+
+ static class C extends B<Integer, Integer> {
+
+ @Override
+ public Integer apply(Integer integer) {
+ return integer * 2;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
index 45a3be4..71e7df5 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
@@ -47,28 +47,6 @@
}
@Test
- public void testPropagationFromFeature() throws Exception {
- ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
- r8TestCompileResult -> {
- // Ensure that getFromFeature from FeatureClass is inlined into the run method.
- ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
- assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
- };
- ProcessResult processResult =
- testDexSplitter(
- parameters,
- ImmutableSet.of(BaseSuperClass.class),
- ImmutableSet.of(FeatureClass.class),
- FeatureClass.class,
- EXPECTED,
- ensureGetFromFeatureGone,
- TestShrinkerBuilder::noMinification);
- // We expect art to fail on this with the dex splitter, see b/122902374
- assertNotEquals(processResult.exitCode, 0);
- assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
- }
-
- @Test
public void testOnR8Splitter() throws IOException, CompilationFailedException {
assumeTrue(parameters.isDexRuntime());
ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 96220f8..53af2b6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -48,31 +48,6 @@
}
@Test
- public void testInliningFromFeature() throws Exception {
- ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
- r8TestCompileResult -> {
- // Ensure that getFromFeature from FeatureClass is inlined into the run method.
- ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
- assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
- };
- Consumer<R8FullTestBuilder> configurator =
- r8FullTestBuilder ->
- r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
- ProcessResult processResult =
- testDexSplitter(
- parameters,
- ImmutableSet.of(BaseSuperClass.class),
- ImmutableSet.of(FeatureClass.class),
- FeatureClass.class,
- EXPECTED,
- ensureGetFromFeatureGone,
- configurator);
- // We expect art to fail on this with the dex splitter, see b/122902374
- assertNotEquals(processResult.exitCode, 0);
- assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
- }
-
- @Test
public void testOnR8Splitter() throws IOException, CompilationFailedException {
assumeTrue(parameters.isDexRuntime());
ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index 38d19da..a2e1b1d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -46,28 +46,6 @@
}
@Test
- public void testPropagationFromFeature() throws Exception {
- ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
- r8TestCompileResult -> {
- // Ensure that getFromFeature from FeatureClass is inlined into the run method.
- ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
- assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
- };
- ProcessResult processResult =
- testDexSplitter(
- parameters,
- ImmutableSet.of(BaseSuperClass.class),
- ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
- FeatureClass.class,
- EXPECTED,
- ensureGetFromFeatureGone,
- builder -> builder.enableInliningAnnotations().noMinification());
- // We expect art to fail on this with the dex splitter, see b/122902374
- assertNotEquals(processResult.exitCode, 0);
- assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
- }
-
- @Test
public void testOnR8Splitter() throws IOException, CompilationFailedException {
assumeTrue(parameters.isDexRuntime());
ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 71ddab8..a7d137f 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -48,43 +48,6 @@
}
@Test
- public void testInliningFromFeature() throws Exception {
- // Static merging is based on sorting order, we assert that we merged to the feature.
- ThrowingConsumer<R8TestCompileResult, Exception> ensureMergingToFeature =
- r8TestCompileResult -> {
- ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class);
- assertEquals(2, clazz.allMethods().size());
- assertThat(clazz.uniqueMethodWithName("getBase42"), isPresent());
- assertThat(clazz.uniqueMethodWithName("getFoobar"), isPresent());
- };
- Consumer<R8FullTestBuilder> configurator =
- r8FullTestBuilder ->
- r8FullTestBuilder
- .addOptionsModification(
- options ->
- options.testing.horizontalClassMergingTarget =
- (appView, candidates, target) -> candidates.iterator().next())
- .addHorizontallyMergedClassesInspector(
- inspector ->
- inspector.assertMergedInto(BaseWithStatic.class, AFeatureWithStatic.class))
- .enableNoVerticalClassMergingAnnotations()
- .enableInliningAnnotations()
- .noMinification();
- ProcessResult processResult =
- testDexSplitter(
- parameters,
- ImmutableSet.of(BaseClass.class, BaseWithStatic.class),
- ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class),
- FeatureClass.class,
- EXPECTED,
- ensureMergingToFeature,
- configurator);
- // We expect art to fail on this with the dex splitter.
- assertNotEquals(processResult.exitCode, 0);
- assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
- }
-
- @Test
public void testOnR8Splitter() throws IOException, CompilationFailedException {
assumeTrue(parameters.isDexRuntime());
ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
deleted file mode 100644
index e90e0ab..0000000
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ /dev/null
@@ -1,484 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.dexsplitter;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DexSplitterHelper;
-import com.android.tools.r8.ExtractMarker;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
-import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-public class DexSplitterTests {
-
- private static final String CLASS_DIR =
- ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR + "classes/dexsplitsample";
- private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
- private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
- private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class";
- private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
- private static final String CLASS3_SYNTHETIC_CLASS = CLASS_DIR + "/Class3$1.class";
- private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class";
- private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class";
- private static final String TEXT_FILE =
- ToolHelper.EXAMPLES_ANDROID_N_DIR + "dexsplitsample/TextFile.txt";
-
-
- @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
- private Path createInput(boolean dontCreateMarkerInD8)
- throws IOException, CompilationFailedException {
- // Initial normal compile to create dex files.
- Path inputZip = temp.newFolder().toPath().resolve("input.zip");
- D8Command command =
- D8Command.builder()
- .setOutput(inputZip, OutputMode.DexIndexed)
- .addProgramFiles(Paths.get(CLASS1_CLASS))
- .addProgramFiles(Paths.get(CLASS2_CLASS))
- .addProgramFiles(Paths.get(CLASS3_CLASS))
- .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
- .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS))
- .addProgramFiles(Paths.get(CLASS4_CLASS))
- .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
- .build();
-
- DexSplitterHelper.runD8ForTesting(command, dontCreateMarkerInD8);
-
- return inputZip;
- }
-
- private void testMarker(boolean addMarkerToInput)
- throws CompilationFailedException, IOException, ResourceException, ExecutionException {
- Path inputZip = createInput(!addMarkerToInput);
-
- Path output = temp.newFolder().toPath().resolve("output");
- Files.createDirectory(output);
- Path splitSpec = createSplitSpec();
-
- DexSplitter.main(
- new String[] {
- "--input", inputZip.toString(),
- "--output", output.toString(),
- "--feature-splits", splitSpec.toString()
- });
-
- Path base = output.resolve("base").resolve("classes.dex");
- Path feature = output.resolve("feature1").resolve("classes.dex");
-
- for (Path path : new Path[] {inputZip, base, feature}) {
- Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(path);
- assertEquals(addMarkerToInput ? 1 : 0, markers.size());
- }
- }
-
- @Test
- public void testMarkerPreserved()
- throws CompilationFailedException, IOException, ResourceException, ExecutionException {
- testMarker(true);
- }
-
- @Test
- public void testMarkerNotAdded()
- throws CompilationFailedException, IOException, ResourceException, ExecutionException {
- testMarker(false);
- }
-
- /**
- * To test the file splitting we have 3 classes that we distribute like this: Class1 -> base
- * Class2 -> feature1 Class3 -> feature1
- *
- * <p>Class1 and Class2 works independently of each other, but Class3 extends Class1, and
- * therefore can't run without the base being loaded.
- */
- @Test
- public void splitFilesNoObfuscation()
- throws CompilationFailedException, IOException, FeatureMappingException {
- noObfuscation(false);
- noObfuscation(true);
- }
-
- private void noObfuscation(boolean useOptions)
- throws IOException, CompilationFailedException, FeatureMappingException {
- Path inputZip = createInput(false);
- Path output = temp.newFolder().toPath().resolve("output");
- Files.createDirectory(output);
- Path splitSpec = createSplitSpec();
-
- if (useOptions) {
- Options options = new Options();
- options.addInputArchive(inputZip.toString());
- options.setFeatureSplitMapping(splitSpec.toString());
- options.setOutput(output.toString());
- DexSplitter.run(options);
- } else {
- DexSplitter.main(
- new String[] {
- "--input", inputZip.toString(),
- "--output", output.toString(),
- "--feature-splits", splitSpec.toString()
- });
- }
-
- Path base = output.resolve("base").resolve("classes.dex");
- Path feature = output.resolve("feature1").resolve("classes.dex");
- validateUnobfuscatedOutput(base, feature);
- }
-
- private void validateUnobfuscatedOutput(Path base, Path feature) throws IOException {
- // Both classes should still work if we give all dex files to the system.
- for (String className : new String[] {"Class1", "Class2", "Class3"}) {
- ArtCommandBuilder builder = new ArtCommandBuilder();
- builder.appendClasspath(base.toString());
- builder.appendClasspath(feature.toString());
- builder.setMainClass("dexsplitsample." + className);
- String out = ToolHelper.runArt(builder);
- assertEquals(out, className + "\n");
- }
- // Individual classes should also work from the individual files.
- String className = "Class1";
- ArtCommandBuilder builder = new ArtCommandBuilder();
- builder.appendClasspath(base.toString());
- builder.setMainClass("dexsplitsample." + className);
- String out = ToolHelper.runArt(builder);
- assertEquals(out, className + "\n");
-
- className = "Class2";
- builder = new ArtCommandBuilder();
- builder.appendClasspath(feature.toString());
- builder.setMainClass("dexsplitsample." + className);
- out = ToolHelper.runArt(builder);
- assertEquals(out, className + "\n");
-
- className = "Class3";
- builder = new ArtCommandBuilder();
- builder.appendClasspath(feature.toString());
- builder.setMainClass("dexsplitsample." + className);
- try {
- ToolHelper.runArt(builder);
- assertFalse(true);
- } catch (AssertionError assertionError) {
- // We expect this to throw since base is not in the path and Class3 depends on Class1
- }
-
- className = "Class4";
- builder = new ArtCommandBuilder();
- builder.appendClasspath(feature.toString());
- builder.setMainClass("dexsplitsample." + className);
- try {
- ToolHelper.runArt(builder);
- assertFalse(true);
- } catch (AssertionError assertionError) {
- // We expect this to throw since base is not in the path and Class4 includes a lambda that
- // would have been pushed to base.
- }
- }
-
- private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
- Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
- try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
- out.write(
- "dexsplitsample.Class1:base\n"
- + "dexsplitsample.Class2:feature1\n"
- + "dexsplitsample.Class3:feature1\n"
- + "dexsplitsample.Class4:feature1");
- }
- return splitSpec;
- }
-
- private List<String> getProguardConf() {
- return ImmutableList.of(
- "-keep class dexsplitsample.Class3 {",
- " public static void main(java.lang.String[]);",
- "}");
- }
-
- @Test
- public void splitFilesFromJar()
- throws IOException, CompilationFailedException, FeatureMappingException {
- for (boolean useOptions : new boolean[]{false, true}) {
- for (boolean explicitBase: new boolean[]{false, true}) {
- for (boolean renameBase: new boolean[]{false, true}) {
- splitFromJars(useOptions, explicitBase, renameBase);
- }
- }
- }
- }
-
- private void splitFromJars(boolean useOptions, boolean explicitBase, boolean renameBase)
- throws IOException, CompilationFailedException, FeatureMappingException {
- Path inputZip = createInput(false);
- Path output = temp.newFolder().toPath().resolve("output");
- Files.createDirectory(output);
- Path baseJar = temp.getRoot().toPath().resolve("base.jar");
- Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
- ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
- String name = "dexsplitsample/Class1.class";
- baseStream.putNextEntry(new ZipEntry(name));
- baseStream.write(Files.readAllBytes(Paths.get(CLASS1_CLASS)));
- baseStream.closeEntry();
- baseStream.close();
-
- ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
- name = "dexsplitsample/Class2.class";
- featureStream.putNextEntry(new ZipEntry(name));
- featureStream.write(Files.readAllBytes(Paths.get(CLASS2_CLASS)));
- featureStream.closeEntry();
- name = "dexsplitsample/Class3.class";
- featureStream.putNextEntry(new ZipEntry(name));
- featureStream.write(Files.readAllBytes(Paths.get(CLASS3_CLASS)));
- featureStream.closeEntry();
- name = "dexsplitsample/Class3$InnerClass.class";
- featureStream.putNextEntry(new ZipEntry(name));
- featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS)));
- featureStream.closeEntry();
- name = "dexsplitsample/Class3$1.class";
- featureStream.putNextEntry(new ZipEntry(name));
- featureStream.write(Files.readAllBytes(Paths.get(CLASS3_SYNTHETIC_CLASS)));
- featureStream.closeEntry();
- name = "dexsplitsample/Class4";
- featureStream.putNextEntry(new ZipEntry(name));
- featureStream.write(Files.readAllBytes(Paths.get(CLASS4_CLASS)));
- featureStream.closeEntry();
- name = "dexsplitsample/Class4$LambdaInterface";
- featureStream.putNextEntry(new ZipEntry(name));
- featureStream.write(Files.readAllBytes(Paths.get(CLASS4_LAMBDA_INTERFACE)));
- featureStream.closeEntry();
- featureStream.close();
- // Make sure that we can pass in a name for the output.
- String specificOutputName = "renamed";
- if (useOptions) {
- Options options = new Options();
- options.addInputArchive(inputZip.toString());
- options.setOutput(output.toString());
- if (explicitBase) {
- options.addBaseJar(baseJar.toString());
- } else if (renameBase){
- // Ensure that we can rename base (if people called a feature base)
- options.setBaseOutputName("base_renamed");
- }
- options.addFeatureJar(featureJar.toString(), specificOutputName);
- DexSplitter.run(options);
- } else {
- List<String> args = Lists.newArrayList(
- "--input",
- inputZip.toString(),
- "--output",
- output.toString(),
- "--feature-jar",
- featureJar.toString().concat(":").concat(specificOutputName));
- if (explicitBase) {
- args.add("--base-jar");
- args.add(baseJar.toString());
- } else if (renameBase) {
- args.add("--base-output-name");
- args.add("base_renamed");
- }
-
- DexSplitter.main(args.toArray(StringUtils.EMPTY_ARRAY));
- }
- String baseOutputName = explicitBase || !renameBase ? "base" : "base_renamed";
- Path base = output.resolve(baseOutputName).resolve("classes.dex");
- Path feature = output.resolve(specificOutputName).resolve("classes.dex");;
- validateUnobfuscatedOutput(base, feature);
- }
-
- @Test
- public void splitFilesObfuscation()
- throws CompilationFailedException, IOException, ExecutionException {
- // Initial normal compile to create dex files.
- Path inputDex = temp.newFolder().toPath().resolve("input.zip");
- Path proguardMap = temp.getRoot().toPath().resolve("proguard.map");
-
- R8.run(
- R8Command.builder()
- .setOutput(inputDex, OutputMode.DexIndexed)
- .addProgramFiles(Paths.get(CLASS1_CLASS))
- .addProgramFiles(Paths.get(CLASS2_CLASS))
- .addProgramFiles(Paths.get(CLASS3_CLASS))
- .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
- .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS))
- .addProgramFiles(Paths.get(CLASS4_CLASS))
- .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
- .setProguardMapOutputPath(proguardMap)
- .addProguardConfiguration(getProguardConf(), Origin.unknown())
- .build());
-
- Path outputDex = temp.newFolder().toPath().resolve("output");
- Files.createDirectory(outputDex);
- Path splitSpec = createSplitSpec();
-
- DexSplitter.main(
- new String[] {
- "--input", inputDex.toString(),
- "--output", outputDex.toString(),
- "--feature-splits", splitSpec.toString(),
- "--proguard-map", proguardMap.toString()
- });
-
- Path base = outputDex.resolve("base").resolve("classes.dex");
- Path feature = outputDex.resolve("feature1").resolve("classes.dex");
- String class3 = "dexsplitsample.Class3";
- // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
- // class1 which is in base.
- ArtCommandBuilder builder = new ArtCommandBuilder();
- builder.appendClasspath(base.toString());
- builder.appendClasspath(feature.toString());
- builder.setMainClass(class3);
- String out = ToolHelper.runArt(builder);
- assertEquals(out, "Class3\n");
-
- // Class1 should not be in the feature, it should still be in base.
- builder = new ArtCommandBuilder();
- builder.appendClasspath(feature.toString());
- builder.setMainClass(class3);
- try {
- ToolHelper.runArt(builder);
- assertFalse(true);
- } catch (AssertionError assertionError) {
- // We expect this to throw since base is not in the path and Class3 depends on Class1.
- }
-
- // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
- // shaken away.
- CodeInspector inspector = new CodeInspector(base, proguardMap);
- ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
- assertTrue(subject.isPresent());
- assertTrue(subject.isRenamed());
- }
-
- @Test
- public void splitNonClassFiles()
- throws CompilationFailedException, IOException, FeatureMappingException {
- Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
- ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
- String name = "dexsplitsample/TextFile.txt";
- inputZipStream.putNextEntry(new ZipEntry(name));
- byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
- inputZipStream.write(fileBytes);
- inputZipStream.closeEntry();
- name = "dexsplitsample/TextFile2.txt";
- inputZipStream.putNextEntry(new ZipEntry(name));
- inputZipStream.write(fileBytes);
- inputZipStream.write(fileBytes);
- inputZipStream.closeEntry();
- inputZipStream.close();
- Path output = temp.newFolder().toPath().resolve("output");
- Files.createDirectory(output);
- Path baseJar = temp.getRoot().toPath().resolve("base.jar");
- Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
- ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
- name = "dexsplitsample/TextFile.txt";
- baseStream.putNextEntry(new ZipEntry(name));
- baseStream.write(fileBytes);
- baseStream.closeEntry();
- baseStream.close();
- ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
- name = "dexsplitsample/TextFile2.txt";
- featureStream.putNextEntry(new ZipEntry(name));
- featureStream.write(fileBytes);
- featureStream.write(fileBytes);
- featureStream.closeEntry();
- featureStream.close();
- Options options = new Options();
- options.addInputArchive(inputZip.toString());
- options.setOutput(output.toString());
- options.addFeatureJar(baseJar.toString());
- options.addFeatureJar(featureJar.toString());
- options.setSplitNonClassResources(true);
- DexSplitter.run(options);
- Path baseDir = output.resolve("base");
- Path featureDir = output.resolve("feature1");
- byte[] contents = fileBytes;
- byte[] contents2 = new byte[contents.length * 2];
- System.arraycopy(contents, 0, contents2, 0, contents.length);
- System.arraycopy(contents, 0, contents2, contents.length, contents.length);
- Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
- Path featureTextFile = featureDir.resolve("dexsplitsample/TextFile2.txt");
- assert Files.exists(baseTextFile);
- assert Files.exists(featureTextFile);
- assert Arrays.equals(Files.readAllBytes(baseTextFile), contents);
- assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2);
- }
-
- @Test
- public void splitDuplicateNonClassFiles()
- throws IOException, CompilationFailedException, FeatureMappingException {
- Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
- ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
- String name = "dexsplitsample/TextFile.txt";
- inputZipStream.putNextEntry(new ZipEntry(name));
- byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
- inputZipStream.write(fileBytes);
- inputZipStream.closeEntry();
- inputZipStream.close();
- Path output = temp.newFolder().toPath().resolve("output");
- Files.createDirectory(output);
- Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
- Path feature2Jar = temp.getRoot().toPath().resolve("feature2.jar");
- ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
- name = "dexsplitsample/TextFile.txt";
- featureStream.putNextEntry(new ZipEntry(name));
- featureStream.write(fileBytes);
- featureStream.closeEntry();
- featureStream.close();
- ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(feature2Jar));
- name = "dexsplitsample/TextFile.txt";
- feature2Stream.putNextEntry(new ZipEntry(name));
- feature2Stream.write(fileBytes);
- feature2Stream.closeEntry();
- feature2Stream.close();
- Options options = new Options();
- options.addInputArchive(inputZip.toString());
- options.setOutput(output.toString());
- options.addFeatureJar(feature2Jar.toString());
- options.addFeatureJar(featureJar.toString());
- options.setSplitNonClassResources(true);
- DexSplitter.run(options);
- Path baseDir = output.resolve("base");
- Path feature1Dir = output.resolve("feature1");
- Path feature2Dir = output.resolve("feature2");
- Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
- Path feature1TextFile = feature1Dir.resolve("dexsplitsample/TextFile2.txt");
- Path feature2TextFile = feature2Dir.resolve("dexsplitsample/TextFile2.txt");
- assert !Files.exists(feature1TextFile);
- assert !Files.exists(feature2TextFile);
- assert Files.exists(baseTextFile);
- assert Arrays.equals(Files.readAllBytes(baseTextFile), fileBytes);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 05d6d3d..bd7929d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -17,12 +17,9 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
import com.android.tools.r8.utils.ArchiveResourceProvider;
import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import dalvik.system.PathClassLoader;
import java.io.IOException;
@@ -31,9 +28,7 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
-import java.util.List;
import java.util.Set;
-import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@@ -155,96 +150,6 @@
toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
}
- // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
- // based on the base/feature sets. toRun must implement the BaseRunInterface
- <E extends Throwable> ProcessResult testDexSplitter(
- TestParameters parameters,
- Set<Class<?>> baseClasses,
- Set<Class<?>> featureClasses,
- Class<?> toRun,
- String expectedOutput,
- ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
- Consumer<R8FullTestBuilder> r8TestConfigurator)
- throws Exception, E {
- List<Class<?>> baseClassesWithRunner =
- ImmutableList.<Class<?>>builder()
- .add(RunInterface.class, SplitRunner.class)
- .addAll(baseClasses)
- .build();
-
- Path baseJar = jarTestClasses(baseClassesWithRunner);
- Path featureJar = jarTestClasses(featureClasses);
-
- Path featureOnly =
- testForR8(parameters.getBackend())
- .addProgramClasses(featureClasses)
- .addClasspathClasses(baseClasses)
- .addClasspathClasses(RunInterface.class)
- .addKeepAllClassesRule()
- .addInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .compile()
- .writeToZip();
- if (parameters.isDexRuntime()) {
- // With D8 this should just work. We compile all of the base classes, then run with the
- // feature loaded at runtime. Since there is no inlining/class merging we don't
- // have any issues.
- testForD8()
- .addProgramClasses(SplitRunner.class, RunInterface.class)
- .addProgramClasses(baseClasses)
- .setMinApi(parameters.getApiLevel())
- .compile()
- .run(
- parameters.getRuntime(),
- SplitRunner.class,
- toRun.getName(),
- featureOnly.toAbsolutePath().toString())
- .assertSuccessWithOutput(expectedOutput);
- }
-
- R8FullTestBuilder builder = testForR8(parameters.getBackend());
- if (parameters.isCfRuntime()) {
- // Compiling to jar we need to support the same way of loading code at runtime as
- // android supports.
- builder
- .addProgramClasses(PathClassLoader.class)
- .addKeepClassAndMembersRules(PathClassLoader.class);
- }
-
- R8FullTestBuilder r8FullTestBuilder =
- builder
- .setMinApi(parameters.getApiLevel())
- .addProgramClasses(SplitRunner.class, RunInterface.class)
- .addProgramClasses(baseClasses)
- .addProgramClasses(featureClasses)
- .addKeepMainRule(SplitRunner.class)
- .addKeepClassRules(toRun);
- r8TestConfigurator.accept(r8FullTestBuilder);
- R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
- compileResultConsumer.accept(r8TestCompileResult);
- Path fullFiles = r8TestCompileResult.writeToZip();
-
- // Ensure that we can run the program as a unit (i.e., without splitting)
- r8TestCompileResult
- .run(parameters.getRuntime(), SplitRunner.class, toRun.getName())
- .assertSuccessWithOutput(expectedOutput);
-
- Path splitterOutput = temp.newFolder().toPath();
- Path splitterBaseDexFile = splitterOutput.resolve("base").resolve("classes.dex");
- Path splitterFeatureDexFile = splitterOutput.resolve("feature").resolve("classes.dex");
-
- Options options = new Options();
- options.setOutput(splitterOutput.toString());
- options.addBaseJar(baseJar.toString());
- options.addFeatureJar(featureJar.toString(), "feature");
-
- options.addInputArchive(fullFiles.toString());
- DexSplitter.run(options);
-
- return runFeatureOnArt(
- toRun, splitterBaseDexFile, splitterFeatureDexFile, parameters.getRuntime());
- }
-
ProcessResult runFeatureOnArt(
Class toRun, Path splitterBaseDexFile, Path splitterFeatureDexFile, TestRuntime runtime)
throws IOException {
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 5437118..05824cc 100644
--- a/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
@@ -105,6 +105,7 @@
Path unzipped = temp.newFolder().toPath();
ZipUtils.unzip(dumpFile.toString(), unzipped.toFile());
assertTrue(Files.exists(unzipped.resolve("r8-version")));
+ assertTrue(Files.exists(unzipped.resolve("build.properties")));
assertTrue(Files.exists(unzipped.resolve("program.jar")));
assertTrue(Files.exists(unzipped.resolve("library.jar")));
if (hasClasspath) {
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
deleted file mode 100644
index 9ae3939..0000000
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
+++ /dev/null
@@ -1,80 +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.internal;
-
-import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
-import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Paths;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class YouTubeV1533TreeShakeJarVerificationTest extends YouTubeCompilationTestBase {
-
- private static final boolean DUMP = false;
- private static final int MAX_SIZE = 27500000;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
- }
-
- public YouTubeV1533TreeShakeJarVerificationTest(TestParameters parameters) {
- super(15, 33, AndroidApiLevel.H_MR2);
- parameters.assertNoneRuntime();
- }
-
- @Test
- public void testR8() throws Exception {
- // TODO(b/141603168): Enable this on the bots.
- assumeTrue(isLocalDevelopment());
- assumeTrue(shouldRunSlowTests());
-
- LibrarySanitizer librarySanitizer =
- new LibrarySanitizer(temp)
- .addProgramFiles(getProgramFiles())
- .addLibraryFiles(ImmutableList.of(getLibraryFile()))
- .sanitize()
- .assertSanitizedProguardConfigurationIsEmpty();
-
- R8TestCompileResult compileResult =
- testForR8(Backend.DEX)
- .addProgramFiles(getProgramFiles())
- .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
- .addKeepRuleFiles(getKeepRuleFiles())
- .addMainDexRuleFiles(getMainDexRuleFiles())
- .addIgnoreWarnings()
- .allowDiagnosticMessages()
- .allowUnusedDontWarnPatterns()
- .allowUnusedProguardConfigurationRules()
- .setMinApi(getApiLevel())
- .compile()
- .apply(this::printProtoStats);
-
- if (isLocalDevelopment()) {
- if (DUMP) {
- long time = System.currentTimeMillis();
- compileResult.writeToZip(Paths.get("YouTubeV1533-" + time + ".zip"));
- compileResult.writeProguardMap(Paths.get("YouTubeV1533-" + time + ".map"));
- }
- }
-
- int applicationSize = compileResult.app.applicationSize();
- System.out.println(applicationSize);
-
- assertTrue(
- "Expected max size of " + MAX_SIZE + ", got " + applicationSize,
- applicationSize < MAX_SIZE);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 5d54843..2eb6a7f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -25,8 +25,6 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.dexsplitter.DexSplitter;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.errors.Unreachable;
@@ -255,49 +253,6 @@
}
@Test
- public void everyThirdClassInMainWithDexSplitter() throws Throwable {
- List<String> featureMappings = new ArrayList<>();
- List<String> inFeatureMapping = new ArrayList<>();
-
- ImmutableList.Builder<String> mainDexBuilder = ImmutableList.builder();
- for (int i = 0; i < MANY_CLASSES.size(); i++) {
- String clazz = MANY_CLASSES.get(i);
- // Write the first 2 classes into the split.
- if (i < 10) {
- featureMappings.add(clazz + ":feature1");
- inFeatureMapping.add(clazz);
- }
- if (i % 3 == 0) {
- mainDexBuilder.add(clazz);
- }
- }
- Path featureSplitMapping = temp.getRoot().toPath().resolve("splitmapping");
- Path mainDexFile = temp.getRoot().toPath().resolve("maindex");
- FileUtils.writeTextFile(featureSplitMapping, featureMappings);
- List<String> mainDexList = mainDexBuilder.build();
- FileUtils.writeTextFile(mainDexFile, ListUtils.map(mainDexList, MainDexListTests::typeToEntry));
- Path output = temp.getRoot().toPath().resolve("split_output");
- Files.createDirectories(output);
- TestDiagnosticsHandler diagnosticsHandler = new TestDiagnosticsHandler();
- Options options = new Options(diagnosticsHandler);
- options.addInputArchive(getManyClassesMultiDexAppPath().toString());
- options.setFeatureSplitMapping(featureSplitMapping.toString());
- options.setOutput(output.toString());
- options.setMainDexList(mainDexFile.toString());
- DexSplitter.run(options);
- assertEquals(0, diagnosticsHandler.numberOfErrorsAndWarnings());
- Path baseDir = output.resolve("base");
- CodeInspector inspector =
- new CodeInspector(
- AndroidApp.builder().addProgramFiles(baseDir.resolve("classes.dex")).build());
- for (String clazz : mainDexList) {
- if (!inspector.clazz(clazz).isPresent() && !inFeatureMapping.contains(clazz)) {
- failedToFindClassInExpectedFile(baseDir, clazz);
- }
- }
- }
-
- @Test
public void singleClassInMainDex() throws Throwable {
ImmutableList<String> mainDex = ImmutableList.of(MANY_CLASSES.get(0));
verifyMainDexContains(mainDex, getManyClassesSingleDexAppPath(), true);
diff --git a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
index a6dbbb7..4419055 100644
--- a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
+++ b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
@@ -12,13 +12,13 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.experimental.startup.StartupClass;
import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Lists;
-import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -48,21 +48,27 @@
options ->
options
.getStartupOptions()
- .setEnableMinimalStartupDex()
+ .setEnableMinimalStartupDex(true)
.setEnableStartupCompletenessCheckForTesting()
.setStartupConfiguration(
- new StartupConfiguration(
- Lists.newArrayList(
- toDexType(Main.class, options.dexItemFactory()),
- toDexType(StartupClass.class, options.dexItemFactory())),
- Collections.emptyList())))
+ StartupConfiguration.builder()
+ .addStartupClass(
+ StartupClass.<DexType>builder()
+ .setReference(toDexType(Main.class, options.dexItemFactory()))
+ .build())
+ .addStartupClass(
+ StartupClass.<DexType>builder()
+ .setReference(
+ toDexType(AStartupClass.class, options.dexItemFactory()))
+ .build())
+ .build()))
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.inspectMultiDex(
primaryDexInspector -> {
// StartupClass should be in the primary dex.
- ClassSubject startupClassSubject = primaryDexInspector.clazz(StartupClass.class);
+ ClassSubject startupClassSubject = primaryDexInspector.clazz(AStartupClass.class);
assertThat(startupClassSubject, isPresent());
MethodSubject startupMethodSubject = startupClassSubject.uniqueMethodWithName("foo");
@@ -99,7 +105,7 @@
static class Main {
public static void main(String[] args) {
- StartupClass.foo();
+ AStartupClass.foo();
}
// @Keep
@@ -108,7 +114,7 @@
}
}
- static class StartupClass {
+ static class AStartupClass {
@NeverInline
static void foo() {
diff --git a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
index 8a73ac4..6364cb0 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
@@ -57,16 +57,13 @@
}
private static List<String> getExpectedOutput() {
- return ImmutableList.of(
- "Lcom/android/tools/r8/startup/StartupInstrumentationTest$Main;",
- "Lcom/android/tools/r8/startup/StartupInstrumentationTest$StartupClass;",
- "foo");
+ return ImmutableList.of(descriptor(Main.class), descriptor(AStartupClass.class), "foo");
}
static class Main {
public static void main(String[] args) {
- StartupClass.foo();
+ AStartupClass.foo();
}
// @Keep
@@ -75,7 +72,7 @@
}
}
- static class StartupClass {
+ static class AStartupClass {
@NeverInline
static void foo() {
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
new file mode 100644
index 0000000..884c1bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
@@ -0,0 +1,212 @@
+// 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.startup;
+
+import static com.android.tools.r8.startup.utils.StartupTestingMatchers.isEqualToClassDataLayout;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+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;
+import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+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 StartupSyntheticPlacementTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean enableMinimalStartupDex;
+
+ @Parameter(2)
+ public boolean useLambda;
+
+ @Parameters(name = "{0}, minimal startup dex: {1}, use lambda: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ // N so that java.util.function.Consumer is present.
+ getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.N).build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ @Test
+ public void test() throws Exception {
+ List<StartupClass<ClassReference>> startupList = new ArrayList<>();
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .apply(StartupTestingUtils.enableStartupInstrumentation(parameters))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
+ .run(parameters.getRuntime(), Main.class, Boolean.toString(useLambda))
+ .apply(StartupTestingUtils.removeStartupClassesFromStdout(startupList::add))
+ .assertSuccessWithOutputLines(getExpectedOutput());
+ assertEquals(getExpectedStartupList(), startupList);
+
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(A.class, B.class, C.class)
+ .addOptionsModification(
+ options -> {
+ options.getStartupOptions().setEnableMinimalStartupDex(enableMinimalStartupDex);
+ options
+ .getTestingOptions()
+ .setMixedSectionLayoutStrategyInspector(getMixedSectionLayoutInspector());
+ })
+ .apply(testBuilder -> StartupTestingUtils.setStartupConfiguration(testBuilder, startupList))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectMultiDex(this::inspectPrimaryDex, this::inspectSecondaryDex)
+ .run(parameters.getRuntime(), Main.class, Boolean.toString(useLambda))
+ .assertSuccessWithOutputLines(getExpectedOutput());
+ }
+
+ private List<String> getExpectedOutput() {
+ return ImmutableList.of("A", "B", "C");
+ }
+
+ private List<StartupClass<ClassReference>> getExpectedStartupList() {
+ ImmutableList.Builder<StartupClass<ClassReference>> builder = ImmutableList.builder();
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(Main.class))
+ .build());
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(A.class))
+ .build());
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(B.class))
+ .build());
+ if (useLambda) {
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(B.class))
+ .setSynthetic()
+ .build());
+ }
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(C.class))
+ .build());
+ return builder.build();
+ }
+
+ private List<ClassReference> getExpectedClassDataLayout(int virtualFile) {
+ // The synthetic lambda should only be placed alongside its synthetic context (B) if it is used.
+ // Otherwise, it should be last, or in the second dex file if compiling with minimal startup.
+ ImmutableList.Builder<ClassReference> layoutBuilder = ImmutableList.builder();
+ if (virtualFile == 0) {
+ layoutBuilder.add(
+ Reference.classFromClass(Main.class),
+ Reference.classFromClass(A.class),
+ Reference.classFromClass(B.class));
+ if (useLambda) {
+ layoutBuilder.add(getSyntheticLambdaClassReference());
+ }
+ layoutBuilder.add(Reference.classFromClass(C.class));
+ }
+ if (!useLambda) {
+ if (!enableMinimalStartupDex || virtualFile == 1) {
+ layoutBuilder.add(getSyntheticLambdaClassReference());
+ }
+ }
+ return layoutBuilder.build();
+ }
+
+ private MixedSectionLayoutInspector getMixedSectionLayoutInspector() {
+ return new MixedSectionLayoutInspector() {
+ @Override
+ public void inspectClassDataLayout(int virtualFile, Collection<DexProgramClass> layout) {
+ assertThat(layout, isEqualToClassDataLayout(getExpectedClassDataLayout(virtualFile)));
+ }
+ };
+ }
+
+ private void inspectPrimaryDex(CodeInspector inspector) {
+ assertThat(inspector.clazz(Main.class), isPresent());
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ assertThat(inspector.clazz(C.class), isPresent());
+ assertThat(
+ inspector.clazz(getSyntheticLambdaClassReference()),
+ notIf(isPresent(), enableMinimalStartupDex && !useLambda));
+ }
+
+ private void inspectSecondaryDex(CodeInspector inspector) {
+ if (enableMinimalStartupDex && !useLambda) {
+ assertEquals(1, inspector.allClasses().size());
+ assertThat(inspector.clazz(getSyntheticLambdaClassReference()), isPresent());
+ } else {
+ assertTrue(inspector.allClasses().isEmpty());
+ }
+ }
+
+ private static ClassReference getSyntheticLambdaClassReference() {
+ return SyntheticItemsTestUtils.syntheticLambdaClass(B.class, 0);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ boolean useLambda = args.length > 0 && args[0].equals("true");
+ A.a();
+ B.b(useLambda);
+ C.c();
+ }
+ }
+
+ static class A {
+
+ static void a() {
+ System.out.println("A");
+ }
+ }
+
+ static class B {
+
+ static void b(boolean useLambda) {
+ String message = System.currentTimeMillis() > 0 ? "B" : null;
+ if (useLambda) {
+ Consumer<Object> consumer = obj -> {};
+ consumer.accept(consumer);
+ }
+ System.out.println(message);
+ }
+ }
+
+ static class C {
+
+ static void c() {
+ System.out.println("C");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
new file mode 100644
index 0000000..18ed52b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
@@ -0,0 +1,199 @@
+// 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.startup;
+
+import static com.android.tools.r8.startup.utils.StartupTestingMatchers.isEqualToClassDataLayout;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+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;
+import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+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 StartupSyntheticWithoutContextTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean enableMinimalStartupDex;
+
+ @Parameters(name = "{0}, minimal startup dex: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ // N so that java.util.function.Consumer is present.
+ getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.N).build(),
+ BooleanUtils.values());
+ }
+
+ @Test
+ public void test() throws Exception {
+ List<StartupClass<ClassReference>> startupList = new ArrayList<>();
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .apply(StartupTestingUtils.enableStartupInstrumentation(parameters))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(StartupTestingUtils.removeStartupClassesFromStdout(startupList::add))
+ .assertSuccessWithOutputLines(getExpectedOutput());
+ assertEquals(getExpectedStartupList(), startupList);
+
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(A.class, C.class)
+ .addOptionsModification(
+ options -> {
+ options.getStartupOptions().setEnableMinimalStartupDex(enableMinimalStartupDex);
+ options
+ .getTestingOptions()
+ .setMixedSectionLayoutStrategyInspector(getMixedSectionLayoutInspector());
+ })
+ .apply(testBuilder -> StartupTestingUtils.setStartupConfiguration(testBuilder, startupList))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectMultiDex(this::inspectPrimaryDex, this::inspectSecondaryDex)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(getExpectedOutput());
+ }
+
+ private List<String> getExpectedOutput() {
+ return ImmutableList.of("A", "B", "C");
+ }
+
+ private List<StartupClass<ClassReference>> getExpectedStartupList() {
+ ImmutableList.Builder<StartupClass<ClassReference>> builder = ImmutableList.builder();
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(Main.class))
+ .build());
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(A.class))
+ .build());
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(B.class))
+ .build());
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(B.class))
+ .setSynthetic()
+ .build());
+ builder.add(
+ StartupClass.<ClassReference>builder()
+ .setReference(Reference.classFromClass(C.class))
+ .build());
+ return builder.build();
+ }
+
+ private List<ClassReference> getExpectedClassDataLayout(int virtualFile) {
+ ImmutableList.Builder<ClassReference> builder = ImmutableList.builder();
+ if (virtualFile == 0) {
+ builder.add(
+ Reference.classFromClass(Main.class),
+ Reference.classFromClass(A.class),
+ getSyntheticLambdaClassReference(B.class),
+ Reference.classFromClass(C.class));
+ }
+ if (!enableMinimalStartupDex || virtualFile == 1) {
+ builder.add(getSyntheticLambdaClassReference(Main.class));
+ }
+ return builder.build();
+ }
+
+ private MixedSectionLayoutInspector getMixedSectionLayoutInspector() {
+ return new MixedSectionLayoutInspector() {
+ @Override
+ public void inspectClassDataLayout(int virtualFile, Collection<DexProgramClass> layout) {
+ assertThat(layout, isEqualToClassDataLayout(getExpectedClassDataLayout(virtualFile)));
+ }
+ };
+ }
+
+ private void inspectPrimaryDex(CodeInspector inspector) {
+ assertThat(inspector.clazz(Main.class), isPresent());
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isAbsent());
+ assertThat(inspector.clazz(C.class), isPresent());
+ assertThat(inspector.clazz(getSyntheticLambdaClassReference(B.class)), isPresent());
+ assertThat(
+ inspector.clazz(getSyntheticLambdaClassReference(Main.class)),
+ notIf(isPresent(), enableMinimalStartupDex));
+ }
+
+ private void inspectSecondaryDex(CodeInspector inspector) {
+ if (enableMinimalStartupDex) {
+ assertEquals(1, inspector.allClasses().size());
+ assertThat(inspector.clazz(getSyntheticLambdaClassReference(Main.class)), isPresent());
+ } else {
+ assertTrue(inspector.allClasses().isEmpty());
+ }
+ }
+
+ private static ClassReference getSyntheticLambdaClassReference(Class<?> synthesizingContext) {
+ return SyntheticItemsTestUtils.syntheticLambdaClass(synthesizingContext, 0);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A.a();
+ Runnable r = System.currentTimeMillis() > 0 ? B.b() : Main::error;
+ r.run();
+ C.c();
+ }
+
+ static void error() {
+ throw new RuntimeException();
+ }
+ }
+
+ static class A {
+
+ static void a() {
+ System.out.println("A");
+ }
+ }
+
+ static class B {
+
+ static Runnable b() {
+ return () -> System.out.println("B");
+ }
+ }
+
+ static class C {
+
+ static void c() {
+ System.out.println("C");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/utils/MixedSectionLayoutInspector.java b/src/test/java/com/android/tools/r8/startup/utils/MixedSectionLayoutInspector.java
new file mode 100644
index 0000000..c2d5285
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/utils/MixedSectionLayoutInspector.java
@@ -0,0 +1,133 @@
+// 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.startup.utils;
+
+import com.android.tools.r8.dex.MixedSectionLayoutStrategy;
+import com.android.tools.r8.dex.VirtualFile;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationDirectory;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedArray;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.Collection;
+import java.util.function.BiFunction;
+
+public abstract class MixedSectionLayoutInspector
+ implements BiFunction<MixedSectionLayoutStrategy, VirtualFile, MixedSectionLayoutStrategy> {
+
+ public void inspectAnnotationLayout(int virtualFile, Collection<DexAnnotation> layout) {
+ // Intentionally empty.
+ }
+
+ public void inspectAnnotationDirectoryLayout(
+ int virtualFile, Collection<DexAnnotationDirectory> layout) {
+ // Intentionally empty.
+ }
+
+ public void inspectAnnotationSetLayout(int virtualFile, Collection<DexAnnotationSet> layout) {
+ // Intentionally empty.
+ }
+
+ public void inspectAnnotationSetRefListLayout(
+ int virtualFile, Collection<ParameterAnnotationsList> layout) {
+ // Intentionally empty.
+ }
+
+ public void inspectClassDataLayout(int virtualFile, Collection<DexProgramClass> layout) {
+ // Intentionally empty.
+ }
+
+ public void inspectCodeLayout(int virtualFile, Collection<ProgramMethod> layout) {
+ // Intentionally empty.
+ }
+
+ public void inspectEncodedArrayLayout(int virtualFile, Collection<DexEncodedArray> layout) {
+ // Intentionally empty.
+ }
+
+ public void inspectStringDataLayout(int virtualFile, Collection<DexString> layout) {
+ // Intentionally empty.
+ }
+
+ public void inspectTypeListLayout(int virtualFile, Collection<DexTypeList> layout) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public MixedSectionLayoutStrategy apply(
+ MixedSectionLayoutStrategy mixedSectionLayoutStrategy, VirtualFile virtualFile) {
+ return new MixedSectionLayoutStrategy() {
+
+ @Override
+ public Collection<DexAnnotation> getAnnotationLayout() {
+ Collection<DexAnnotation> layout = mixedSectionLayoutStrategy.getAnnotationLayout();
+ inspectAnnotationLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+
+ @Override
+ public Collection<DexAnnotationDirectory> getAnnotationDirectoryLayout() {
+ Collection<DexAnnotationDirectory> layout =
+ mixedSectionLayoutStrategy.getAnnotationDirectoryLayout();
+ inspectAnnotationDirectoryLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+
+ @Override
+ public Collection<DexAnnotationSet> getAnnotationSetLayout() {
+ Collection<DexAnnotationSet> layout = mixedSectionLayoutStrategy.getAnnotationSetLayout();
+ inspectAnnotationSetLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+
+ @Override
+ public Collection<ParameterAnnotationsList> getAnnotationSetRefListLayout() {
+ Collection<ParameterAnnotationsList> layout =
+ mixedSectionLayoutStrategy.getAnnotationSetRefListLayout();
+ inspectAnnotationSetRefListLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+
+ @Override
+ public Collection<DexProgramClass> getClassDataLayout() {
+ Collection<DexProgramClass> layout = mixedSectionLayoutStrategy.getClassDataLayout();
+ inspectClassDataLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+
+ @Override
+ public Collection<ProgramMethod> getCodeLayout() {
+ Collection<ProgramMethod> layout = mixedSectionLayoutStrategy.getCodeLayout();
+ inspectCodeLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+
+ @Override
+ public Collection<DexEncodedArray> getEncodedArrayLayout() {
+ Collection<DexEncodedArray> layout = mixedSectionLayoutStrategy.getEncodedArrayLayout();
+ inspectEncodedArrayLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+
+ @Override
+ public Collection<DexString> getStringDataLayout() {
+ Collection<DexString> layout = mixedSectionLayoutStrategy.getStringDataLayout();
+ inspectStringDataLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+
+ @Override
+ public Collection<DexTypeList> getTypeListLayout() {
+ Collection<DexTypeList> layout = mixedSectionLayoutStrategy.getTypeListLayout();
+ inspectTypeListLayout(virtualFile.getId(), layout);
+ return layout;
+ }
+ };
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingMatchers.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingMatchers.java
new file mode 100644
index 0000000..618e644
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingMatchers.java
@@ -0,0 +1,56 @@
+// 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.startup.utils;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.Iterator;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class StartupTestingMatchers {
+
+ public static Matcher<Collection<DexProgramClass>> isEqualToClassDataLayout(
+ Collection<ClassReference> expectedLayout) {
+ return new TypeSafeMatcher<Collection<DexProgramClass>>() {
+ @Override
+ protected boolean matchesSafely(Collection<DexProgramClass> actualLayout) {
+ if (actualLayout.size() != expectedLayout.size()) {
+ return false;
+ }
+ Iterator<ClassReference> expectedLayoutIterator = expectedLayout.iterator();
+ for (DexProgramClass clazz : actualLayout) {
+ if (!clazz.getClassReference().equals(expectedLayoutIterator.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(
+ "is equal to ["
+ + StringUtils.join(", ", expectedLayout, TypeReference::getTypeName)
+ + "]");
+ }
+
+ @Override
+ public void describeMismatchSafely(
+ Collection<DexProgramClass> actualLayout, Description description) {
+ description
+ .appendText("class data layout was not: ")
+ .appendText("[")
+ .appendText(StringUtils.join(", ", actualLayout, DexClass::getTypeName))
+ .appendText("]");
+ }
+ };
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
new file mode 100644
index 0000000..dad4d44
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -0,0 +1,120 @@
+// 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.startup.utils;
+
+import static com.android.tools.r8.TestBase.transformer;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.rules.TemporaryFolder;
+
+public class StartupTestingUtils {
+
+ private static String startupInstrumentationTag = "startup";
+
+ public static ThrowableConsumer<D8TestBuilder> enableStartupInstrumentation(
+ TestParameters parameters) {
+ return testBuilder -> enableStartupInstrumentation(testBuilder, parameters);
+ }
+
+ public static void enableStartupInstrumentation(
+ D8TestBuilder testBuilder, TestParameters parameters) throws IOException {
+ testBuilder
+ .addOptionsModification(
+ options ->
+ options
+ .getStartupOptions()
+ .setEnableStartupInstrumentation()
+ .setStartupInstrumentationTag(startupInstrumentationTag))
+ .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+ .addLibraryClassFileData(getTransformedAndroidUtilLog());
+ }
+
+ public static Path getAndroidUtilLog(TemporaryFolder temporaryFolder)
+ throws CompilationFailedException, IOException {
+ return TestBase.testForD8(temporaryFolder)
+ .addProgramClassFileData(getTransformedAndroidUtilLog())
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip();
+ }
+
+ public static ThrowingConsumer<D8TestRunResult, RuntimeException> removeStartupClassesFromStdout(
+ Consumer<StartupClass<ClassReference>> startupClassConsumer) {
+ return runResult -> removeStartupClassesFromStdout(runResult, startupClassConsumer);
+ }
+
+ public static void removeStartupClassesFromStdout(
+ D8TestRunResult runResult, Consumer<StartupClass<ClassReference>> startupClassConsumer) {
+ StringBuilder stdoutBuilder = new StringBuilder();
+ String startupDescriptorPrefix = "[" + startupInstrumentationTag + "] ";
+ for (String line : StringUtils.splitLines(runResult.getStdOut(), true)) {
+ if (line.startsWith(startupDescriptorPrefix)) {
+ StartupClass.Builder<ClassReference> startupClassBuilder = StartupClass.builder();
+ String message = line.substring(startupDescriptorPrefix.length());
+ message = StartupConfiguration.parseSyntheticFlag(message, startupClassBuilder);
+ startupClassBuilder.setReference(Reference.classFromDescriptor(message));
+ startupClassConsumer.accept(startupClassBuilder.build());
+ } else {
+ stdoutBuilder.append(line).append(System.lineSeparator());
+ }
+ }
+ runResult.getResult().setStdout(stdoutBuilder.toString());
+ }
+
+ public static void setStartupConfiguration(
+ R8TestBuilder<?> testBuilder, List<StartupClass<ClassReference>> startupClasses) {
+ testBuilder.addOptionsModification(
+ options -> {
+ DexItemFactory dexItemFactory = options.dexItemFactory();
+ options
+ .getStartupOptions()
+ .setStartupConfiguration(
+ StartupConfiguration.builder()
+ .apply(
+ builder ->
+ startupClasses.forEach(
+ startupClass ->
+ builder.addStartupClass(
+ StartupClass.<DexType>builder()
+ .setFlags(startupClass.getFlags())
+ .setReference(
+ dexItemFactory.createType(
+ startupClass.getReference().getDescriptor()))
+ .build())))
+ .build());
+ });
+ }
+
+ private static byte[] getTransformedAndroidUtilLog() throws IOException {
+ return transformer(Log.class).setClassDescriptor("Landroid/util/Log;").transform();
+ }
+
+ public static class Log {
+
+ public static int i(String tag, String msg) {
+ System.out.println("[" + tag + "] " + msg);
+ return 42;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
deleted file mode 100644
index f41be5c..0000000
--- a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2016, 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 junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import org.junit.Test;
-
-public class FeatureClassMappingTest {
-
- @Test
- public void testSimpleParse() throws Exception {
-
- List<String> lines =
- ImmutableList.of(
- "# Comment, don't care about contents: even more ::::",
- "com.google.base:base",
- "", // Empty lines allowed
- "com.google.feature1:feature1",
- "com.google.feature1:feature1", // Multiple definitions of the same predicate allowed.
- "com.google$:feature1",
- "_com.google:feature21",
- "com.google.*:feature32");
- FeatureClassMapping mapping = new FeatureClassMapping(lines);
- }
-
- private void ensureThrowsMappingException(List<String> lines) {
- try {
- new FeatureClassMapping(lines);
- assertFalse(true);
- } catch (FeatureMappingException e) {
- // Expected
- }
- }
-
- private void ensureThrowsMappingException(String string) {
- ensureThrowsMappingException(ImmutableList.of(string));
- }
-
- @Test
- public void testLookup() throws Exception {
- List<String> lines =
- ImmutableList.of(
- "com.google.Base:base",
- "",
- "com.google.Feature1:feature1",
- "com.google.Feature1:feature1", // Multiple definitions of the same predicate allowed.
- "com.google.different.*:feature1",
- "_com.Google:feature21",
- "com.google.bas42.*:feature42");
- FeatureClassMapping mapping = new FeatureClassMapping(lines);
- assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
- assertEquals(mapping.featureForClass("com.google.different.Feature1"), "feature1");
- assertEquals(mapping.featureForClass("com.google.different.Foobar"), "feature1");
- assertEquals(mapping.featureForClass("com.google.Base"), "base");
- assertEquals(mapping.featureForClass("com.google.bas42.foo.bar.bar.Foo"), "feature42");
- assertEquals(mapping.featureForClass("com.google.bas42.f$o$o$.bar43.bar.Foo"), "feature42");
- assertEquals(mapping.featureForClass("_com.Google"), "feature21");
- }
-
- @Test
- public void testCatchAllWildcards() throws Exception {
- testBaseWildcard(true);
- testBaseWildcard(false);
- testNonBaseCatchAll();
- }
-
- private void testNonBaseCatchAll() throws FeatureMappingException {
- List<String> lines =
- ImmutableList.of(
- "com.google.Feature1:feature1",
- "*:nonbase",
- "com.strange.*:feature2");
- FeatureClassMapping mapping = new FeatureClassMapping(lines);
- assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
- assertEquals(mapping.featureForClass("com.google.different.Feature1"), "nonbase");
- assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
- assertEquals(mapping.featureForClass("Feature1"), "nonbase");
- assertEquals(mapping.featureForClass("a.b.z.A"), "nonbase");
- }
-
- private void testBaseWildcard(boolean explicitBase) throws FeatureMappingException {
- List<String> lines =
- ImmutableList.of(
- "com.google.Feature1:feature1",
- explicitBase ? "*:base" : "",
- "com.strange.*:feature2");
- FeatureClassMapping mapping = new FeatureClassMapping(lines);
- assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
- assertEquals(mapping.featureForClass("com.google.different.Feature1"), "base");
- assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
- assertEquals(mapping.featureForClass("com.stranger.Clazz"), "base");
- assertEquals(mapping.featureForClass("Feature1"), "base");
- assertEquals(mapping.featureForClass("a.b.z.A"), "base");
- }
-
- @Test
- public void testWrongLines() throws Exception {
- // No colon.
- ensureThrowsMappingException("foo");
- ensureThrowsMappingException("com.google.base");
- // Two colons.
- ensureThrowsMappingException(ImmutableList.of("a:b:c"));
-
- // Empty identifier.
- ensureThrowsMappingException("com..google:feature1");
-
- // Ambiguous redefinition
- ensureThrowsMappingException(
- ImmutableList.of("com.google.foo:feature1", "com.google.foo:feature2"));
- ensureThrowsMappingException(
- ImmutableList.of("com.google.foo.*:feature1", "com.google.foo.*:feature2"));
- }
-
- @Test
- public void testUsesOnlyExactMappings() throws Exception {
- List<String> lines =
- ImmutableList.of(
- "com.pkg1.Clazz:feature1",
- "com.pkg2.Clazz:feature2");
- FeatureClassMapping mapping = new FeatureClassMapping(lines);
-
- assertEquals(mapping.featureForClass("com.pkg1.Clazz"), "feature1");
- assertEquals(mapping.featureForClass("com.pkg2.Clazz"), "feature2");
- assertEquals(mapping.featureForClass("com.pkg1.Other"), mapping.baseName);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 402ef93..efcf8a9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -132,6 +132,10 @@
.read(app.getProguardMapOutputData()));
}
+ public static CodeInspector empty() throws IOException {
+ return new CodeInspector(ImmutableList.of(), null, null);
+ }
+
private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
InternalOptions internalOptions = new InternalOptions();
if (optionsConsumer != null) {
diff --git a/third_party/internal-apps/youtube_15_33.tar.gz.sha1 b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
deleted file mode 100644
index 9a04d6e..0000000
--- a/third_party/internal-apps/youtube_15_33.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-907babdaf04052eed13f22400175307131df1610
\ No newline at end of file
diff --git a/third_party/youtube-developer/20200415.tar.gz.sha1 b/third_party/youtube-developer/20200415.tar.gz.sha1
deleted file mode 100644
index f44d810..0000000
--- a/third_party/youtube-developer/20200415.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-bfc2082c67a28dc43c975ccc3b0e10d3d31cae5d
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_15.33.tar.gz.sha1 b/third_party/youtube/youtube.android_15.33.tar.gz.sha1
deleted file mode 100644
index 3ce1c4a..0000000
--- a/third_party/youtube/youtube.android_15.33.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-cdff350c62bf8c72d97be51590e90ad9105b5499
\ No newline at end of file
diff --git a/tools/dexsplitter.py b/tools/dexsplitter.py
deleted file mode 100755
index a97d089..0000000
--- a/tools/dexsplitter.py
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-import sys
-import toolhelper
-
-if __name__ == '__main__':
- sys.exit(toolhelper.run('dexsplitter', sys.argv[1:]))
diff --git a/tools/golem.py b/tools/golem.py
deleted file mode 100755
index 6a4e399..0000000
--- a/tools/golem.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-# Utility methods to make running on our performance tracking system easier.
-import os
-import sys
-
-LINKED_THIRD_PARTY_DIRECTORIES = [
- 'android_jar',
- 'android_sdk',
- 'benchmarks',
- 'framework',
- 'gmail',
- 'gmscore',
- 'gradle',
- 'gradle-plugin',
- 'openjdk',
- 'proguard',
- 'proguardsettings',
- 'r8',
- 'remapper',
- 'retrace_benchmark',
- 'sample_libraries',
- 'youtube',
-]
-
-LINKED_TOOL_DIRECTORIES = [
- 'linux/dx',
-]
-
-# Path to our internally updated third party
-THIRD_PARTY_SOURCE = "/usr/local/google/home/golem/r8/third_party"
-TOOLS_SOURCE = "/usr/local/google/home/golem/r8/tools"
-
-def link_third_party():
- assert os.path.exists('third_party')
- for dir in LINKED_THIRD_PARTY_DIRECTORIES:
- src = os.path.join(THIRD_PARTY_SOURCE, dir)
- dest = os.path.join('third_party', dir)
- if os.path.exists(dest):
- raise Exception('Destination "{}" already exists, are you running with'
- ' --golem locally'.format(dest))
- print('Symlinking {} to {}'.format(src, dest))
- os.symlink(src, dest)
- for dir in LINKED_TOOL_DIRECTORIES:
- src = os.path.join(TOOLS_SOURCE, dir)
- dest = os.path.join('tools', dir)
- if os.path.exists(dest):
- raise Exception('Destination "{}" already exists, are you running with'
- ' --golem locally'.format(dest))
- print('Symlinking {} to {}'.format(src, dest))
- if '/' in dir:
- os.makedirs(os.path.dirname(dest))
- os.symlink(src, dest)
-
-if __name__ == '__main__':
- sys.exit(link_third_party())
diff --git a/tools/keeprule_benchmark.py b/tools/keeprule_benchmark.py
index 4b843c6..1401084 100755
--- a/tools/keeprule_benchmark.py
+++ b/tools/keeprule_benchmark.py
@@ -125,10 +125,6 @@
def parse_arguments(argv):
parser = argparse.ArgumentParser(
description = 'Run keep-rule benchmarks.')
- parser.add_argument('--golem',
- help = 'Link in third party dependencies.',
- default = False,
- action = 'store_true')
parser.add_argument('--ignore-java-version',
help='Do not check java version',
default=False,
@@ -239,8 +235,6 @@
if __name__ == '__main__':
options = parse_arguments(sys.argv[1:])
- if options.golem:
- golem.link_third_party()
if not options.ignore_java_version:
utils.check_java_version()
with utils.TempDir() as temp:
diff --git a/tools/run_kotlin_benchmarks.py b/tools/run_kotlin_benchmarks.py
index ce0f1ad..86adbe2 100755
--- a/tools/run_kotlin_benchmarks.py
+++ b/tools/run_kotlin_benchmarks.py
@@ -5,7 +5,6 @@
# Script for running kotlin based benchmarks
-import golem
import optparse
import os
import subprocess
@@ -45,9 +44,6 @@
help='The benchmark to run',
default='rgx',
choices=['rgx', 'deltablue', 'sta', 'empty'])
- result.add_option('--golem',
- help='Don\'t build r8 and link in third_party deps',
- default=False, action='store_true')
result.add_option('--use-device',
help='Run the benchmark on an attaced device',
default=False, action='store_true')
@@ -85,8 +81,6 @@
def Main():
(options, args) = parse_options()
- if options.golem:
- golem.link_third_party()
with utils.TempDir() as temp:
dex_path = os.path.join(temp, "classes.jar")
proguard_conf = os.path.join(temp, 'proguard.conf')
@@ -100,7 +94,7 @@
'--min-api', str(options.api),
benchmark_jar
]
- toolhelper.run('r8', r8_args, build=not options.golem)
+ toolhelper.run('r8', r8_args, True)
if options.use_device:
result = run_art_device(dex_path)
else:
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 8c72939..a0542d4 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -16,7 +16,6 @@
import gradle
import gmail_data
import gmscore_data
-import golem
import nest_data
from sanitize_libraries import SanitizeLibraries, SanitizeLibrariesInPgconf
import toolhelper
@@ -106,10 +105,6 @@
type='int',
default=0,
help='Set timeout instead of waiting for OOM.')
- result.add_option('--golem',
- help='Running on golem, do not build or download',
- default=False,
- action='store_true')
result.add_option('--ignore-java-version',
help='Do not check java version',
default=False,
@@ -138,8 +133,6 @@
'Same as --compiler-flags, keeping it for backward'
' compatibility. ' +
'If passing several options use a quoted string.')
- # TODO(tamaskenez) remove track-memory-to-file as soon as we updated golem
- # to use --print-memoryuse instead
result.add_option('--track-memory-to-file',
help='Track how much memory the jvm is using while ' +
' compiling. Output to the specified file.')
@@ -447,7 +440,7 @@
raise Exception("Unexpected -libraryjars found in " + pgconf)
def should_build(options):
- return not options.no_build and not options.golem
+ return not options.no_build
def build_desugared_library_dex(
options,
@@ -519,9 +512,6 @@
extra_args.append('-Xmx%sM' % options.max_memory)
else:
extra_args.append('-Xmx8G')
- if options.golem:
- golem.link_third_party()
- options.out = os.getcwd()
if not options.ignore_java_version:
utils.check_java_version()
@@ -743,9 +733,8 @@
if options.print_dexsegments:
dex_files = glob(os.path.join(outdir, '*.dex'))
utils.print_dexsegments(options.print_dexsegments, dex_files)
- if not options.golem:
- print('{}-Total(CodeSize): {}'.format(
- options.print_dexsegments, compute_size_of_dex_files(dex_files)))
+ print('{}-Total(CodeSize): {}'.format(
+ options.print_dexsegments, compute_size_of_dex_files(dex_files)))
return 0
def compute_size_of_dex_files(dex_files):
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index b047d72..4f22dc5 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -440,16 +440,6 @@
'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
'folder': 'android/compose-samples/rally',
}),
- App({
- 'id': 'youtube_15_33',
- 'name': 'youtube_15_33',
- 'dump_app': 'dump.zip',
- 'apk_app': 'YouTubeRelease_unsigned.apk',
- 'folder': 'youtube_15_33',
- 'internal': True,
- # TODO(b/181629268): Fix recompilation
- 'skip_recompilation': True,
- })
]
diff --git a/tools/test_gradle_benchmarks.py b/tools/test_gradle_benchmarks.py
index d9c00e6..7346830 100755
--- a/tools/test_gradle_benchmarks.py
+++ b/tools/test_gradle_benchmarks.py
@@ -6,7 +6,6 @@
from __future__ import print_function
import argparse
import gradle
-import golem
import os
import subprocess
import sys
@@ -20,9 +19,6 @@
description='Run D8 or DX on gradle apps located in'
' third_party/benchmarks/.'
' Report Golem-compatible RunTimeRaw values.')
- parser.add_argument('--golem',
- help = 'Running on golem, link in third_party resources.',
- default = False, action = 'store_true')
parser.add_argument('--skip_download',
help='Don\'t automatically pull down dependencies.',
default=False, action='store_true')
@@ -124,7 +120,7 @@
return any(namePattern in taskname for namePattern in acceptedGradleTasks)
-def PrintBuildTimeForGolem(benchmark, stdOut):
+def PrintBuildTime(benchmark, stdOut):
for line in stdOut.splitlines():
if 'BENCH' in line and benchmark.moduleName in line:
commaSplit = line.split(',')
@@ -160,12 +156,6 @@
def Main():
args = parse_arguments()
- if args.golem:
- # Ensure that we don't have a running daemon
- exitcode = subprocess.call(['pkill', 'java'])
- assert exitcode == 0 or exitcode == 1
- golem.link_third_party()
-
if args.tool == 'd8':
tool = Benchmark.Tools.D8
desugarMode = Benchmark.DesugarMode.D8_DESUGARING
@@ -217,7 +207,7 @@
['clean']),
]
- if not args.skip_download and not args.golem:
+ if not args.skip_download:
EnsurePresence(os.path.join('third_party', 'benchmarks', 'android-sdk'),
'android SDK')
EnsurePresence(os.path.join('third_party', 'gradle-plugin'),
@@ -232,7 +222,7 @@
benchmark.EnsurePresence()
benchmark.Clean()
stdOut = benchmark.Build(tool, desugarMode)
- PrintBuildTimeForGolem(benchmark, stdOut)
+ PrintBuildTime(benchmark, stdOut)
if __name__ == '__main__':
diff --git a/tools/test_r8cfsegments.py b/tools/test_r8cfsegments.py
index be8e49c..2081ee2 100755
--- a/tools/test_r8cfsegments.py
+++ b/tools/test_r8cfsegments.py
@@ -22,7 +22,6 @@
from __future__ import print_function
import argparse
-import golem
import minify_tool
import os
import sys
@@ -37,10 +36,6 @@
choices = ['pg', 'r8'],
required = True,
help = 'Compiler tool to use.')
- parser.add_argument('--golem',
- help = 'Running on golem, link in third_party resources.',
- default = False,
- action = 'store_true')
parser.add_argument('--name',
required = True,
help = 'Results will be printed using the specified benchmark name (e.g.'
@@ -59,8 +54,6 @@
def Main():
args = parse_arguments()
- if args.golem:
- golem.link_third_party()
utils.check_java_version()
output_dir = args.output
with utils.TempDir() as temp_dir:
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index cd47f66..0dec031 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -5,15 +5,11 @@
import os
import utils
-ANDROID_H_MR2_API = '13'
ANDROID_L_API = '21'
ANDROID_M_API = '23'
BASE = os.path.join(utils.THIRD_PARTY, 'youtube')
-V15_33_BASE = os.path.join(BASE, 'youtube.android_15.33')
-V15_33_PREFIX = os.path.join(V15_33_BASE, 'YouTubeRelease')
-
V16_20_BASE = os.path.join(BASE, 'youtube.android_16.20')
V16_20_PREFIX = os.path.join(V16_20_BASE, 'YouTubeRelease')
@@ -23,37 +19,6 @@
LATEST_VERSION = '17.19'
VERSIONS = {
- '15.33': {
- 'dex' : {
- 'inputs': [os.path.join(V15_33_BASE, 'YouTubeRelease_unsigned.apk')],
- 'pgmap': '%s_proguard.map' % V15_33_PREFIX,
- 'libraries' : [utils.get_android_jar(25)],
- 'min-api' : ANDROID_L_API,
- },
- 'deploy' : {
- # When -injars and -libraryjars are used for specifying inputs library
- # sanitization is on by default. For this version of YouTube -injars and
- # -libraryjars are not used, but library sanitization is still required.
- 'sanitize_libraries': True,
- 'inputs': ['%s_deploy.jar' % V15_33_PREFIX],
- 'libraries' : [os.path.join(V15_33_BASE, 'legacy_YouTubeRelease_combined_library_jars.jar')],
- 'pgconf': [
- '%s_proguard.config' % V15_33_PREFIX,
- '%s_proguard_missing_classes.config' % V15_33_PREFIX,
- '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY,
- utils.IGNORE_WARNINGS_RULES],
- 'maindexrules' : [
- os.path.join(V15_33_BASE, 'mainDexClasses.rules'),
- os.path.join(V15_33_BASE, 'main-dex-classes-release-optimized.pgcfg'),
- os.path.join(V15_33_BASE, 'main_dex_YouTubeRelease_proguard.cfg')],
- 'min-api' : ANDROID_H_MR2_API,
- },
- 'proguarded' : {
- 'inputs': ['%s_proguard.jar' % V15_33_PREFIX],
- 'pgmap': '%s_proguard.map' % V15_33_PREFIX,
- 'min-api' : ANDROID_L_API,
- }
- },
'16.20': {
'deploy' : {
'sanitize_libraries': False,