Merge commit '85103b9e6dff19fead2d852f975e75d769fc6215' into dev-release
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 591b1f3..c9073be 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -13,13 +13,14 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Closer;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.io.IOException;
import java.nio.file.Paths;
-import java.util.LinkedHashMap;
import java.util.Map;
public class DexSegments {
- private static class Command extends BaseCommand {
+ public static class Command extends BaseCommand {
public static class Builder
extends BaseCommand.Builder<Command, Builder> {
@@ -89,14 +90,27 @@
public static void main(String[] args)
throws IOException, CompilationFailedException, ResourceException {
Command.Builder builder = Command.parse(args);
- Command command = builder.build();
+ Map<Integer, SegmentInfo> result = run(builder.build());
+ if (result == null) {
+ return;
+ }
+ System.out.println("Segments in dex application (name: size / items):");
+ // This output is parsed by tools/test_framework.py. Check the parsing there when updating.
+ result.forEach(
+ (key, value) ->
+ System.out.println(
+ " - " + DexSection.typeName(key) + ": " + value.size + " / " + value.items));
+ }
+
+ public static Map<Integer, SegmentInfo> run(Command command)
+ throws CompilationFailedException, IOException, ResourceException {
if (command.isPrintHelp()) {
System.out.println(Command.USAGE_MESSAGE);
- return;
+ return null;
}
AndroidApp app = command.getInputApp();
- Map<String, SegmentInfo> result = new LinkedHashMap<>();
+ Int2ReferenceMap<SegmentInfo> result = new Int2ReferenceLinkedOpenHashMap<>();
// Fill the results with all benchmark items otherwise golem may report missing benchmarks.
int[] benchmarks =
new int[] {
@@ -119,7 +133,7 @@
Constants.TYPE_CLASS_DEF_ITEM
};
for (int benchmark : benchmarks) {
- result.computeIfAbsent(DexSection.typeName(benchmark), (key) -> new SegmentInfo());
+ result.computeIfAbsent(benchmark, (key) -> new SegmentInfo());
}
try (Closer closer = Closer.create()) {
for (ProgramResource resource : app.computeAllProgramResources()) {
@@ -127,20 +141,16 @@
for (DexSection dexSection :
DexParser.parseMapFrom(
closer.register(resource.getByteStream()), resource.getOrigin())) {
- SegmentInfo info =
- result.computeIfAbsent(dexSection.typeName(), (key) -> new SegmentInfo());
+ SegmentInfo info = result.computeIfAbsent(dexSection.type, (key) -> new SegmentInfo());
info.increment(dexSection.length, dexSection.size());
}
}
}
}
- System.out.println("Segments in dex application (name: size / items):");
- // This output is parsed by tools/test_framework.py. Check the parsing there when updating.
- result.forEach(
- (key, value) -> System.out.println(" - " + key + ": " + value.size + " / " + value.items));
+ return result;
}
- private static class SegmentInfo {
+ public static class SegmentInfo {
private int items;
private int size;
@@ -153,5 +163,13 @@
this.items += items;
this.size += size;
}
+
+ public int getItemCount() {
+ return items;
+ }
+
+ public int getSegmentSize() {
+ return size;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
index 48d4d4f..4afafc1 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
@@ -47,7 +47,11 @@
public static AndroidApiLevelCompute create(AppView<?> appView) {
return appView.options().apiModelingOptions().enableApiCallerIdentification
? new DefaultAndroidApiLevelCompute(appView)
- : new NoAndroidApiLevelCompute();
+ : noAndroidApiLevelCompute();
+ }
+
+ public static AndroidApiLevelCompute noAndroidApiLevelCompute() {
+ return new NoAndroidApiLevelCompute();
}
public static ComputedApiLevel computeInitialMinApiLevel(InternalOptions options) {
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index 4e17f60..27fbc38 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -63,10 +63,7 @@
return appView.computedMinApiLevel();
}
DexClass clazz = appView.definitionFor(contextType);
- if (clazz == null) {
- return unknownValue;
- }
- if (!clazz.isLibraryClass()) {
+ if (clazz != null && clazz.isProgramClass()) {
return appView.computedMinApiLevel();
}
if (reference.getContextType() == factory.objectType) {
diff --git a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
index 2f5ab5e..a2e9e87 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
@@ -38,19 +38,23 @@
return isGreaterThanOrEqualTo(other) ? this : other;
}
- default boolean isGreaterThanOrEqualTo(ComputedApiLevel other) {
+ default boolean isGreaterThan(ComputedApiLevel other) {
assert !isNotSetApiLevel() && !other.isNotSetApiLevel()
: "Cannot compute relationship for not set";
- if (isUnknownApiLevel()) {
- return true;
- }
if (other.isUnknownApiLevel()) {
return false;
}
+ if (isUnknownApiLevel()) {
+ return true;
+ }
assert isKnownApiLevel() && other.isKnownApiLevel();
- return asKnownApiLevel()
- .getApiLevel()
- .isGreaterThanOrEqualTo(other.asKnownApiLevel().getApiLevel());
+ return asKnownApiLevel().getApiLevel().isGreaterThan(other.asKnownApiLevel().getApiLevel());
+ }
+
+ default boolean isGreaterThanOrEqualTo(ComputedApiLevel other) {
+ assert !isNotSetApiLevel() && !other.isNotSetApiLevel()
+ : "Cannot compute relationship for not set";
+ return other.equals(this) || isGreaterThan(other);
}
default boolean isKnownApiLevel() {
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 8209749..3acd908 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -63,13 +64,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
-import java.util.IdentityHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -229,31 +227,18 @@
}
}
+ private boolean willComputeProguardMap() {
+ return proguardMapSupplier != null && options.proguardMapConsumer != null;
+ }
+
public void write(ExecutorService executorService) throws IOException, ExecutionException {
Timing timing = appView.appInfo().app().timing;
timing.begin("DexApplication.write");
- ProguardMapId proguardMapId =
- (proguardMapSupplier != null && options.proguardMapConsumer != null)
- ? proguardMapSupplier.writeProguardMap()
- : null;
- // If we do have a map then we're called from R8. In that case we have at least one marker.
- assert proguardMapId == null || (markers != null && markers.size() >= 1);
-
- if (markers != null && !markers.isEmpty()) {
- if (proguardMapId != null) {
- markers.get(0).setPgMapId(proguardMapId.getId());
- }
- markerStrings = new ArrayList<>(markers.size());
- for (Marker marker : markers) {
- markerStrings.add(appView.dexItemFactory().createString(marker.toString()));
- }
- }
-
- if (options.sourceFileProvider != null) {
- SourceFileEnvironment environment = createSourceFileEnvironment(proguardMapId);
- appView.appInfo().classes().forEach(clazz -> rewriteSourceFile(clazz, environment));
- }
+ Box<ProguardMapId> delayedProguardMapId = new Box<>();
+ List<LazyDexString> lazyDexStrings = new ArrayList<>();
+ computeMarkerStrings(delayedProguardMapId, lazyDexStrings);
+ computeSourceFileString(delayedProguardMapId, lazyDexStrings);
try {
timing.begin("Insert Attribute Annotations");
@@ -269,7 +254,6 @@
}
// Generate the dex file contents.
- List<Future<Boolean>> dexDataFutures = new ArrayList<>();
timing.begin("Distribute");
List<VirtualFile> virtualFiles = distribute(executorService);
timing.end();
@@ -291,20 +275,59 @@
appView.appInfo().classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
timing.end();
- TimingMerger merger =
- timing.beginMerger("Write files", ThreadUtils.getNumberOfThreads(executorService));
- Collection<Timing> timings =
- ThreadUtils.processItemsWithResults(
- virtualFiles,
- virtualFile -> {
- Timing fileTiming = Timing.create("VirtualFile " + virtualFile.getId(), options);
- writeVirtualFile(virtualFile, fileTiming);
- fileTiming.end();
- return fileTiming;
- },
- executorService);
- merger.add(timings);
- merger.end();
+ {
+ // Compute offsets and rewrite jumbo strings so that code offsets are fixed.
+ TimingMerger merger =
+ timing.beginMerger("Pre-write phase", ThreadUtils.getNumberOfThreads(executorService));
+ Collection<Timing> timings =
+ ThreadUtils.processItemsWithResults(
+ virtualFiles,
+ virtualFile -> {
+ Timing fileTiming = Timing.create("VirtualFile " + virtualFile.getId(), options);
+ computeOffsetMappingAndRewriteJumboStrings(
+ virtualFile, lazyDexStrings, fileTiming);
+ fileTiming.end();
+ return fileTiming;
+ },
+ executorService);
+ merger.add(timings);
+ merger.end();
+ }
+
+ // Now code offsets are fixed, compute the mapping file content.
+ // TODO(b/207765416): Move the line number optimizer to this point so PC info can be used.
+ if (willComputeProguardMap()) {
+ timing.begin("Write proguard map");
+ delayedProguardMapId.set(proguardMapSupplier.writeProguardMap());
+ timing.end();
+ }
+
+ // With the mapping id/hash known, it is safe to compute the remaining dex strings.
+ timing.begin("Compute lazy strings");
+ List<DexString> forcedStrings = new ArrayList<>();
+ for (LazyDexString lazyDexString : lazyDexStrings) {
+ forcedStrings.add(lazyDexString.compute());
+ }
+ timing.end();
+
+ {
+ // Write the actual dex code.
+ TimingMerger merger =
+ timing.beginMerger("Write files", ThreadUtils.getNumberOfThreads(executorService));
+ Collection<Timing> timings =
+ ThreadUtils.processItemsWithResults(
+ virtualFiles,
+ virtualFile -> {
+ Timing fileTiming = Timing.create("VirtualFile " + virtualFile.getId(), options);
+ writeVirtualFile(virtualFile, fileTiming, forcedStrings);
+ fileTiming.end();
+ return fileTiming;
+ },
+ executorService);
+ merger.add(timings);
+ merger.end();
+ }
+
// A consumer can manage the generated keep rules.
if (options.desugaredLibraryKeepRuleConsumer != null && !desugaredLibraryCodeToKeep.isNop()) {
assert !options.isDesugaredLibraryCompilation();
@@ -319,6 +342,51 @@
}
}
+ private void computeMarkerStrings(
+ Box<ProguardMapId> delayedProguardMapId, List<LazyDexString> lazyDexStrings) {
+ if (markers != null && !markers.isEmpty()) {
+ int firstNonLazyMarker = 0;
+ if (willComputeProguardMap()) {
+ firstNonLazyMarker++;
+ lazyDexStrings.add(
+ new LazyDexString() {
+
+ @Override
+ public DexString internalCompute() {
+ Marker marker = markers.get(0);
+ marker.setPgMapId(delayedProguardMapId.get().getId());
+ return marker.toDexString(appView.dexItemFactory());
+ }
+ });
+ }
+ markerStrings = new ArrayList<>(markers.size() - firstNonLazyMarker);
+ for (int i = firstNonLazyMarker; i < markers.size(); i++) {
+ markerStrings.add(markers.get(i).toDexString(appView.dexItemFactory()));
+ }
+ }
+ }
+
+ private void computeSourceFileString(
+ Box<ProguardMapId> delayedProguardMapId, List<LazyDexString> lazyDexStrings) {
+ if (options.sourceFileProvider == null) {
+ return;
+ }
+ if (!willComputeProguardMap()) {
+ rewriteSourceFile(null);
+ return;
+ }
+ // Clear all source files so as not to collect the original files.
+ appView.appInfo().classes().forEach(clazz -> clazz.setSourceFile(null));
+ // Add a lazy dex string computation to defer construction of the actual string.
+ lazyDexStrings.add(
+ new LazyDexString() {
+ @Override
+ public DexString internalCompute() {
+ return rewriteSourceFile(delayedProguardMapId.get());
+ }
+ });
+ }
+
public static SourceFileEnvironment createSourceFileEnvironment(ProguardMapId proguardMapId) {
if (proguardMapId == null) {
return new SourceFileEnvironment() {
@@ -346,16 +414,37 @@
};
}
- private void rewriteSourceFile(DexProgramClass clazz, SourceFileEnvironment environment) {
+ private DexString rewriteSourceFile(ProguardMapId proguardMapId) {
assert options.sourceFileProvider != null;
+ SourceFileEnvironment environment = createSourceFileEnvironment(proguardMapId);
String sourceFile = options.sourceFileProvider.get(environment);
- clazz.setSourceFile(sourceFile == null ? null : options.itemFactory.createString(sourceFile));
+ DexString dexSourceFile =
+ sourceFile == null ? null : options.itemFactory.createString(sourceFile);
+ appView.appInfo().classes().forEach(clazz -> clazz.setSourceFile(dexSourceFile));
+ return dexSourceFile;
}
- private void writeVirtualFile(VirtualFile virtualFile, Timing timing) {
+ private void computeOffsetMappingAndRewriteJumboStrings(
+ VirtualFile virtualFile, List<LazyDexString> lazyDexStrings, Timing timing) {
if (virtualFile.isEmpty()) {
return;
}
+ timing.begin("Compute object offset mapping");
+ virtualFile.computeMapping(
+ appView, graphLens, namingLens, initClassLens, lazyDexStrings.size(), timing);
+ timing.end();
+ timing.begin("Rewrite jumbo strings");
+ rewriteCodeWithJumboStrings(
+ virtualFile.getObjectMapping(), virtualFile.classes(), appView.appInfo().app());
+ timing.end();
+ }
+
+ private void writeVirtualFile(
+ VirtualFile virtualFile, Timing timing, List<DexString> forcedStrings) {
+ if (virtualFile.isEmpty()) {
+ return;
+ }
+
ProgramConsumer consumer;
ByteBufferProvider byteBufferProvider;
if (programConsumer != null) {
@@ -375,16 +464,14 @@
byteBufferProvider = options.getDexIndexedConsumer();
}
}
- timing.begin("Compute object offset mapping");
- ObjectToOffsetMapping objectMapping =
- virtualFile.computeMapping(appView, graphLens, namingLens, initClassLens, timing);
+
+ timing.begin("Reindex for lazy strings");
+ ObjectToOffsetMapping objectMapping = virtualFile.getObjectMapping();
+ objectMapping.computeAndReindexForLazyDexStrings(forcedStrings);
timing.end();
- timing.begin("Rewrite jumbo strings");
- MethodToCodeObjectMapping codeMapping =
- rewriteCodeWithJumboStrings(objectMapping, virtualFile.classes(), appView.appInfo().app());
- timing.end();
+
timing.begin("Write bytes");
- ByteBufferResult result = writeDexFile(objectMapping, codeMapping, byteBufferProvider);
+ ByteBufferResult result = writeDexFile(objectMapping, byteBufferProvider);
ByteDataView data =
new ByteDataView(result.buffer.array(), result.buffer.arrayOffset(), result.length);
timing.end();
@@ -652,7 +739,7 @@
* <p>If run multiple times on a class, the lowest index that is required to be a JumboString will
* be used.
*/
- private MethodToCodeObjectMapping rewriteCodeWithJumboStrings(
+ private void rewriteCodeWithJumboStrings(
ObjectToOffsetMapping mapping,
Collection<DexProgramClass> classes,
DexApplication application) {
@@ -660,19 +747,14 @@
if (!options.testing.forceJumboStringProcessing) {
// If there are no strings with jumbo indices at all this is a no-op.
if (!mapping.hasJumboStrings()) {
- return MethodToCodeObjectMapping.fromMethodBacking();
+ return;
}
// If the globally highest sorting string is not a jumbo string this is also a no-op.
if (application.highestSortingString != null
&& application.highestSortingString.compareTo(mapping.getFirstJumboString()) < 0) {
- return MethodToCodeObjectMapping.fromMethodBacking();
+ return;
}
}
- // At least one method needs a jumbo string in which case we construct a thread local mapping
- // for all code objects and write the processed results into that map.
- // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never duplicated
- // in outputs.
- Map<DexEncodedMethod, DexWritableCode> codeMapping = new IdentityHashMap<>();
for (DexProgramClass clazz : classes) {
clazz.forEachProgramMethodMatching(
DexEncodedMethod::hasCode,
@@ -684,25 +766,18 @@
mapping,
application.dexItemFactory,
options.testing.forceJumboStringProcessing);
- codeMapping.put(method.getDefinition(), rewrittenCode);
- // The mapping now has ownership of the methods code object. This ensures freeing of
- // code resources once the map entry is cleared and also ensures that we don't end up
- // using the incorrect code pointer again later!
- method.getDefinition().unsetCode();
+ method.getDefinition().setCode(rewrittenCode.asCode(), appView);
});
}
- return MethodToCodeObjectMapping.fromMapBacking(codeMapping);
}
private ByteBufferResult writeDexFile(
ObjectToOffsetMapping objectMapping,
- MethodToCodeObjectMapping codeMapping,
ByteBufferProvider provider) {
FileWriter fileWriter =
new FileWriter(
provider,
objectMapping,
- codeMapping,
appView.appInfo(),
options,
namingLens,
@@ -729,4 +804,17 @@
type -> builder.append(mapMainDexListName(type, namingLens)).append('\n'));
return builder.toString();
}
+
+ public abstract static class LazyDexString {
+ private boolean computed = false;
+
+ public abstract DexString internalCompute();
+
+ public final DexString compute() {
+ assert !computed;
+ DexString value = internalCompute();
+ computed = true;
+ return value;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index c937839..84b245c 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -98,8 +98,6 @@
}
private final ObjectToOffsetMapping mapping;
- private final MethodToCodeObjectMapping codeMapping;
- private final AppInfo appInfo;
private final DexApplication application;
private final InternalOptions options;
private final GraphLens graphLens;
@@ -112,20 +110,17 @@
public FileWriter(
ByteBufferProvider provider,
ObjectToOffsetMapping mapping,
- MethodToCodeObjectMapping codeMapping,
AppInfo appInfo,
InternalOptions options,
NamingLens namingLens,
CodeToKeep desugaredLibraryCodeToKeep) {
this.mapping = mapping;
- this.codeMapping = codeMapping;
- this.appInfo = appInfo;
this.application = appInfo.app();
this.options = options;
this.graphLens = mapping.getGraphLens();
this.namingLens = namingLens;
this.dest = new DexOutputBuffer(provider);
- this.mixedSectionOffsets = new MixedSectionOffsets(options, codeMapping);
+ this.mixedSectionOffsets = new MixedSectionOffsets(options);
this.desugaredLibraryCodeToKeep = desugaredLibraryCodeToKeep;
}
@@ -179,9 +174,6 @@
Layout layout = Layout.from(mapping);
layout.setCodesOffset(layout.dataSectionOffset);
- // Check code objects in the code-mapping are consistent with the collected code objects.
- assert codeMapping.verifyCodeObjects(mixedSectionOffsets.getCodes());
-
// Sort the codes first, as their order might impact size due to alignment constraints.
List<ProgramDexCode> codes = sortDexCodesByClassName();
@@ -340,7 +332,7 @@
for (DexProgramClass clazz : mapping.getClasses()) {
clazz.forEachProgramMethod(
method -> {
- DexWritableCode code = codeMapping.getCode(method.getDefinition());
+ DexWritableCode code = method.getDefinition().getDexWritableCodeOrNull();
assert code != null || method.getDefinition().shouldNotHaveCode();
if (code != null) {
ProgramDexCode programCode = new ProgramDexCode(code, method);
@@ -661,7 +653,7 @@
dest.putUleb128(nextOffset - currentOffset);
currentOffset = nextOffset;
dest.putUleb128(method.accessFlags.getAsDexAccessFlags());
- DexWritableCode code = codeMapping.getCode(method);
+ DexWritableCode code = method.getDexWritableCodeOrNull();
desugaredLibraryCodeToKeep.recordMethod(method.getReference());
if (code == null) {
assert method.shouldNotHaveCode();
@@ -670,7 +662,7 @@
dest.putUleb128(mixedSectionOffsets.getOffsetFor(method, code));
// Writing the methods starts to take up memory so we are going to flush the
// code objects since they are no longer necessary after this.
- codeMapping.clearCode(method);
+ method.unsetCode();
}
}
}
@@ -1077,8 +1069,6 @@
private static final int NOT_SET = -1;
private static final int NOT_KNOWN = -2;
- private final MethodToCodeObjectMapping codeMapping;
-
private final Reference2IntMap<DexEncodedMethod> codes = createReference2IntMap();
private final Object2IntMap<DexDebugInfo> debugInfos = createObject2IntMap();
private final Object2IntMap<DexTypeList> typeLists = createObject2IntMap();
@@ -1108,9 +1098,8 @@
return result;
}
- private MixedSectionOffsets(InternalOptions options, MethodToCodeObjectMapping codeMapping) {
+ private MixedSectionOffsets(InternalOptions options) {
this.minApiLevel = options.getMinApiLevel();
- this.codeMapping = codeMapping;
}
private <T> boolean add(Object2IntMap<T> map, T item) {
@@ -1151,7 +1140,7 @@
@Override
public void visit(DexEncodedMethod method) {
- method.collectMixedSectionItemsWithCodeMapping(this, codeMapping);
+ method.collectMixedSectionItemsWithCodeMapping(this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 4770922..8d57a1c 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -291,6 +292,10 @@
return tool.hashCode() + 3 * jsonObject.hashCode();
}
+ public DexString toDexString(DexItemFactory factory) {
+ return factory.createString(toString());
+ }
+
// Try to parse str as a marker.
// Returns null if parsing fails.
public static Marker parse(DexString dexString) {
diff --git a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
deleted file mode 100644
index dfdb07a..0000000
--- a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.dex;
-
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexWritableCode;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-public abstract class MethodToCodeObjectMapping {
-
- public abstract DexWritableCode getCode(DexEncodedMethod method);
-
- public abstract void clearCode(DexEncodedMethod method);
-
- public abstract boolean verifyCodeObjects(Collection<DexEncodedMethod> codes);
-
- public static MethodToCodeObjectMapping fromMethodBacking() {
- return MethodBacking.INSTANCE;
- }
-
- public static MethodToCodeObjectMapping fromMapBacking(
- Map<DexEncodedMethod, DexWritableCode> map) {
- return new MapBacking(map);
- }
-
- private static class MethodBacking extends MethodToCodeObjectMapping {
-
- private static final MethodBacking INSTANCE = new MethodBacking();
-
- @Override
- public DexWritableCode getCode(DexEncodedMethod method) {
- Code code = method.getCode();
- assert code == null || code.isDexWritableCode();
- return code == null ? null : code.asDexWritableCode();
- }
-
- @Override
- public void clearCode(DexEncodedMethod method) {
- method.unsetCode();
- }
-
- @Override
- public boolean verifyCodeObjects(Collection<DexEncodedMethod> codes) {
- return true;
- }
- }
-
- private static class MapBacking extends MethodToCodeObjectMapping {
-
- private final Map<DexEncodedMethod, DexWritableCode> codes;
-
- public MapBacking(Map<DexEncodedMethod, DexWritableCode> codes) {
- this.codes = codes;
- }
-
- @Override
- public DexWritableCode getCode(DexEncodedMethod method) {
- return codes.get(method);
- }
-
- @Override
- public void clearCode(DexEncodedMethod method) {
- // We can safely clear the thread local pointer to even shared methods.
- codes.put(method, null);
- }
-
- @Override
- public boolean verifyCodeObjects(Collection<DexEncodedMethod> methods) {
- // TODO(b/204056443): Convert to a Set<DexWritableCode> when DexCode#hashCode() works.
- List<DexWritableCode> codes =
- methods.stream().map(this::getCode).collect(Collectors.toList());
- assert this.codes.values().containsAll(codes);
- return true;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index 0ca695e..e082a31 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -59,8 +59,7 @@
* <p>Allows overriding the behavior when dex-file writing.
*/
public void visit(DexEncodedMethod dexEncodedMethod) {
- dexEncodedMethod.collectMixedSectionItemsWithCodeMapping(
- this, MethodToCodeObjectMapping.fromMethodBacking());
+ dexEncodedMethod.collectMixedSectionItemsWithCodeMapping(this);
}
/**
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 072fab7..e7c7a1d 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -209,28 +209,39 @@
return prefix;
}
- public ObjectToOffsetMapping computeMapping(
+ private ObjectToOffsetMapping objectMapping = null;
+
+ public ObjectToOffsetMapping getObjectMapping() {
+ assert objectMapping != null;
+ return objectMapping;
+ }
+
+ public void computeMapping(
AppView<?> appView,
GraphLens graphLens,
NamingLens namingLens,
InitClassLens initClassLens,
+ int lazyDexStringsCount,
Timing timing) {
assert transaction.isEmpty();
- return new ObjectToOffsetMapping(
- appView,
- graphLens,
- namingLens,
- initClassLens,
- transaction.rewriter,
- indexedItems.classes,
- indexedItems.protos,
- indexedItems.types,
- indexedItems.methods,
- indexedItems.fields,
- indexedItems.strings,
- indexedItems.callSites,
- indexedItems.methodHandles,
- timing);
+ assert objectMapping == null;
+ objectMapping =
+ new ObjectToOffsetMapping(
+ appView,
+ graphLens,
+ namingLens,
+ initClassLens,
+ transaction.rewriter,
+ indexedItems.classes,
+ indexedItems.protos,
+ indexedItems.types,
+ indexedItems.methods,
+ indexedItems.fields,
+ indexedItems.strings,
+ indexedItems.callSites,
+ indexedItems.methodHandles,
+ lazyDexStringsCount,
+ timing);
}
void addClass(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index f336d89..e25cb47 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -123,6 +123,11 @@
}
@Override
+ public Code asCode() {
+ return this;
+ }
+
+ @Override
public void acceptHashing(HashingVisitor visitor) {
visitor.visitInt(getCfWritableCodeKind().hashCode());
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index d49c43f..703977e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -351,6 +351,11 @@
}
@Override
+ public Code asCode() {
+ return this;
+ }
+
+ @Override
public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
DexSourceCode source =
new DexSourceCode(
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index a68f038..2a9e275 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -38,7 +38,6 @@
import com.android.tools.r8.code.Return;
import com.android.tools.r8.code.Throw;
import com.android.tools.r8.code.XorIntLit8;
-import com.android.tools.r8.dex.MethodToCodeObjectMapping;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
@@ -286,7 +285,6 @@
.withBool(DexEncodedMember::isD8R8Synthesized)
// TODO(b/171867022): Make signatures structural and include it in the definition.
.withAssert(m -> m.genericSignature.hasNoSignature())
- .withAssert(DexEncodedMethod::hasCode)
.withCustomItem(
DexEncodedMethod::getCode,
DexEncodedMethod::compareCodeObject,
@@ -294,6 +292,13 @@
}
private static int compareCodeObject(Code code1, Code code2, CompareToVisitor visitor) {
+ if (code1 == code2) {
+ return 0;
+ }
+ if (code1 == null || code2 == null) {
+ // This call is to remain order consistent with the 'withNullableItem' code.
+ return visitor.visitBool(code1 != null, code2 != null);
+ }
if (code1.isCfWritableCode() && code2.isCfWritableCode()) {
return code1.asCfWritableCode().acceptCompareTo(code2.asCfWritableCode(), visitor);
}
@@ -305,7 +310,10 @@
}
private static void hashCodeObject(Code code, HashingVisitor visitor) {
- if (code.isCfWritableCode()) {
+ if (code == null) {
+ // The null code does not contribute to the hash. This should be distinct from non-null as
+ // code otherwise has a non-empty instruction payload.
+ } else if (code.isCfWritableCode()) {
code.asCfWritableCode().acceptHashing(visitor);
} else {
assert code.isDexWritableCode();
@@ -768,9 +776,8 @@
mixedItems.visit(this);
}
- public void collectMixedSectionItemsWithCodeMapping(
- MixedSectionCollection mixedItems, MethodToCodeObjectMapping mapping) {
- DexWritableCode code = mapping.getCode(this);
+ public void collectMixedSectionItemsWithCodeMapping(MixedSectionCollection mixedItems) {
+ DexWritableCode code = getDexWritableCodeOrNull();
if (code != null && mixedItems.add(this, code)) {
code.collectMixedSectionItems(mixedItems);
}
@@ -1315,6 +1322,12 @@
this.genericSignature = MethodTypeSignature.noSignature();
}
+ public DexWritableCode getDexWritableCodeOrNull() {
+ Code code = getCode();
+ assert code == null || code.isDexWritableCode();
+ return code == null ? null : code.asDexWritableCode();
+ }
+
public static Builder syntheticBuilder() {
return new Builder(true);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 4879d46..c108238 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2582,6 +2582,10 @@
return prependTypeToProto(method.holder, method.proto);
}
+ public DexProto prependHolderToProtoIf(DexMethod method, boolean condition) {
+ return condition ? prependHolderToProto(method) : method.getProto();
+ }
+
public DexProto prependTypeToProto(DexType extraFirstType, DexProto initialProto) {
DexType[] parameterTypes = new DexType[initialProto.parameters.size() + 1];
parameterTypes[0] = extraFirstType;
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
index 664b4e8..fffff66 100644
--- a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -71,6 +71,8 @@
int getOutgoingRegisterSize();
+ Code asCode();
+
default boolean isDexCode() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index 04b3eb0..fcdafdd 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.CompareToVisitorWithStringTable;
@@ -29,6 +30,7 @@
private final static int NOT_FOUND = -1;
private final static int NOT_SET = -2;
+ private final int lazyDexStringsCount;
private final AppView<?> appView;
private final GraphLens graphLens;
private final NamingLens namingLens;
@@ -41,7 +43,8 @@
private final Reference2IntLinkedOpenHashMap<DexType> types;
private final Reference2IntLinkedOpenHashMap<DexMethod> methods;
private final Reference2IntLinkedOpenHashMap<DexField> fields;
- private final Reference2IntLinkedOpenHashMap<DexString> strings;
+ // Non-final to support (order consistent) reindexing in case of lazy computed strings.
+ private Reference2IntLinkedOpenHashMap<DexString> strings;
private final Reference2IntLinkedOpenHashMap<DexCallSite> callSites;
private final Reference2IntLinkedOpenHashMap<DexMethodHandle> methodHandles;
@@ -63,6 +66,7 @@
Collection<DexString> strings,
Collection<DexCallSite> callSites,
Collection<DexMethodHandle> methodHandles,
+ int lazyDexStringsCount,
Timing timing) {
assert appView != null;
assert graphLens != null;
@@ -75,13 +79,16 @@
assert callSites != null;
assert methodHandles != null;
assert initClassLens != null;
+ this.lazyDexStringsCount = lazyDexStringsCount;
this.appView = appView;
this.graphLens = graphLens;
this.namingLens = namingLens;
this.initClassLens = initClassLens;
this.lensCodeRewriter = lensCodeRewriter;
timing.begin("Sort strings");
- this.strings = createSortedMap(strings, DexString::compareTo, this::setFirstJumboString);
+ this.strings =
+ createSortedMap(
+ strings, DexString::compareTo, this::setFirstJumboString, lazyDexStringsCount);
CompareToVisitor visitor =
new CompareToVisitorWithStringTable(namingLens, this.strings::getInt);
timing.end();
@@ -126,6 +133,27 @@
};
}
+ public void computeAndReindexForLazyDexStrings(List<DexString> forcedStrings) {
+ assert lazyDexStringsCount == forcedStrings.size();
+ if (forcedStrings.isEmpty()) {
+ return;
+ }
+ // If there are any lazy strings we need to recompute the offsets, even if the strings
+ // are already in the set as we have shifted the initial offset by the size of lazy strings.
+ for (DexString forcedString : forcedStrings) {
+ // Amend the string table to ensure all strings are now present.
+ if (forcedString != null) {
+ strings.put(forcedString, -1);
+ }
+ }
+ Box<DexString> newJumboString = new Box<>();
+ strings = createSortedMap(strings.keySet(), DexString::compareTo, newJumboString::set);
+ // After reindexing it must hold that the new jumbo start is on the same or a larger string.
+ // The new jumbo string is not set as the first determined string is still the cut-off point
+ // where JumboString instructions are used.
+ assert !hasJumboStrings() || newJumboString.get().isGreaterThanOrEqualTo(getFirstJumboString());
+ }
+
public CompareToVisitor getCompareToVisitor() {
return compareToVisitor;
}
@@ -145,6 +173,14 @@
private <T> Reference2IntLinkedOpenHashMap<T> createSortedMap(
Collection<T> items, Comparator<T> comparator, Consumer<T> onUInt16Overflow) {
+ return createSortedMap(items, comparator, onUInt16Overflow, 0);
+ }
+
+ private <T> Reference2IntLinkedOpenHashMap<T> createSortedMap(
+ Collection<T> items,
+ Comparator<T> comparator,
+ Consumer<T> onUInt16Overflow,
+ int reservedIndicesBeforeOverflow) {
if (items.isEmpty()) {
return new Reference2IntLinkedOpenHashMap<>();
}
@@ -155,7 +191,7 @@
map.defaultReturnValue(NOT_FOUND);
int index = 0;
for (T item : sorted) {
- if (index == Constants.U16BIT_MAX + 1) {
+ if (index + reservedIndicesBeforeOverflow == Constants.U16BIT_MAX + 1) {
onUInt16Overflow.accept(item);
}
map.put(item, index++);
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index eb7728c..c573130 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -41,6 +41,11 @@
}
@Override
+ public Code asCode() {
+ return this;
+ }
+
+ @Override
public void acceptHashing(HashingVisitor visitor) {
visitor.visitInt(getCfWritableCodeKind().hashCode());
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
index fa985fa..0ce069a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
@@ -235,11 +235,21 @@
return hasDynamicLowerBoundType();
}
return hasDynamicLowerBoundType()
- && getDynamicLowerBoundType()
- .strictlyLessThan(dynamicType.getDynamicLowerBoundType(), appView);
+ && dynamicType
+ .getDynamicLowerBoundType()
+ .strictlyLessThan(getDynamicLowerBoundType(), appView);
}
- return getDynamicUpperBoundType()
- .strictlyLessThan(dynamicType.getDynamicUpperBoundType(), appView);
+ if (!getDynamicUpperBoundType()
+ .strictlyLessThan(dynamicType.getDynamicUpperBoundType(), appView)) {
+ return false;
+ }
+ if (!dynamicType.hasDynamicLowerBoundType()) {
+ return true;
+ }
+ return hasDynamicLowerBoundType()
+ && dynamicType
+ .getDynamicLowerBoundType()
+ .lessThanOrEqualUpToNullability(getDynamicUpperBoundType(), appView);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 047894d..1250ada 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -204,6 +204,7 @@
.map(prefix -> "L" + DescriptorUtils.getPackageBinaryNameFromJavaType(prefix))
.map(options.itemFactory::createString)
.collect(Collectors.toList());
+ AndroidApiLevelCompute apiLevelCompute = AndroidApiLevelCompute.create(appView);
if (options.isDesugaredLibraryCompilation()) {
// Specific L8 Settings, performs all desugaring including L8 specific desugaring.
//
@@ -221,7 +222,8 @@
// - nest based access desugaring,
// - invoke-special desugaring.
assert options.desugarState.isOn();
- this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
+ this.instructionDesugaring =
+ CfInstructionDesugaringCollection.create(appView, apiLevelCompute);
this.covariantReturnTypeAnnotationTransformer = null;
this.dynamicTypeOptimization = null;
this.classInliner = null;
@@ -246,7 +248,7 @@
this.instructionDesugaring =
appView.enableWholeProgramOptimizations()
? CfInstructionDesugaringCollection.empty()
- : CfInstructionDesugaringCollection.create(appView);
+ : CfInstructionDesugaringCollection.create(appView, apiLevelCompute);
this.covariantReturnTypeAnnotationTransformer =
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
@@ -285,8 +287,7 @@
this.typeChecker = new TypeChecker(appViewWithLiveness, VerifyTypesHelper.create(appView));
this.serviceLoaderRewriter =
options.enableServiceLoaderRewriting
- ? new ServiceLoaderRewriter(
- appViewWithLiveness, AndroidApiLevelCompute.create(appView))
+ ? new ServiceLoaderRewriter(appViewWithLiveness, apiLevelCompute)
: null;
this.enumValueOptimizer =
options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index b34a515..2257925 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar;
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.graph.AppView;
@@ -27,9 +28,10 @@
*/
public abstract class CfInstructionDesugaringCollection {
- public static CfInstructionDesugaringCollection create(AppView<?> appView) {
+ public static CfInstructionDesugaringCollection create(
+ AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
if (appView.options().desugarState.isOn()) {
- return new NonEmptyCfInstructionDesugaringCollection(appView);
+ return new NonEmptyCfInstructionDesugaringCollection(appView, apiLevelCompute);
}
// TODO(b/145775365): invoke-special desugaring is mandatory, since we currently can't map
// invoke-special instructions that require desugaring into IR.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index bc5c0fa..f722159 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.ir.desugar;
+import static com.android.tools.r8.androidapi.AndroidApiLevelCompute.noAndroidApiLevelCompute;
+
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.errors.Unreachable;
@@ -11,6 +14,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.apimodel.ApiInvokeOutlinerDesugaring;
import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicInstructionDesugaring;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
@@ -51,9 +55,12 @@
private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
private final InterfaceMethodRewriter interfaceMethodRewriter;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
+ private final AndroidApiLevelCompute apiLevelCompute;
- NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
+ NonEmptyCfInstructionDesugaringCollection(
+ AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
this.appView = appView;
+ this.apiLevelCompute = apiLevelCompute;
AlwaysThrowingInstructionDesugaring alwaysThrowingInstructionDesugaring =
appView.enableWholeProgramOptimizations()
? new AlwaysThrowingInstructionDesugaring(appView.withClassHierarchy())
@@ -81,6 +88,9 @@
if (appView.options().enableBackportedMethodRewriting()) {
backportedMethodRewriter = new BackportedMethodRewriter(appView);
}
+ if (appView.options().apiModelingOptions().enableOutliningOfMethods) {
+ desugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute));
+ }
if (appView.options().enableTryWithResourcesDesugaring()) {
desugarings.add(new TwrInstructionDesugaring(appView));
}
@@ -131,7 +141,7 @@
assert appView.options().desugarState.isOff();
assert appView.options().isGeneratingClassFiles();
NonEmptyCfInstructionDesugaringCollection desugaringCollection =
- new NonEmptyCfInstructionDesugaringCollection(appView);
+ new NonEmptyCfInstructionDesugaringCollection(appView, noAndroidApiLevelCompute());
// TODO(b/145775365): special constructor for cf-to-cf compilations with desugaring disabled.
// This should be removed once we can represent invoke-special instructions in the IR.
desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
@@ -142,7 +152,7 @@
assert appView.options().desugarState.isOff();
assert appView.options().isGeneratingDex();
NonEmptyCfInstructionDesugaringCollection desugaringCollection =
- new NonEmptyCfInstructionDesugaringCollection(appView);
+ new NonEmptyCfInstructionDesugaringCollection(appView, noAndroidApiLevelCompute());
desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
desugaringCollection.desugarings.add(new InvokeToPrivateRewriter());
return desugaringCollection;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
new file mode 100644
index 0000000..62c0445
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.apimodel;
+
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+
+public class ApiInvokeOutlinerDesugaring implements CfInstructionDesugaring {
+
+ private final AppView<?> appView;
+ private final AndroidApiLevelCompute apiLevelCompute;
+
+ public ApiInvokeOutlinerDesugaring(AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+ this.appView = appView;
+ this.apiLevelCompute = apiLevelCompute;
+ }
+
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ CfInstructionDesugaringCollection desugaringCollection,
+ DexItemFactory dexItemFactory) {
+ ComputedApiLevel computedApiLevel = getComputedApiLevelForMethodOnHolderWithMinApi(instruction);
+ if (computedApiLevel.isGreaterThan(appView.computedMinApiLevel())) {
+ return desugarLibraryCall(
+ methodProcessingContext.createUniqueContext(),
+ instruction,
+ computedApiLevel,
+ dexItemFactory);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ if (context.getDefinition().isD8R8Synthesized()) {
+ return false;
+ }
+ return getComputedApiLevelForMethodOnHolderWithMinApi(instruction)
+ .isGreaterThan(appView.computedMinApiLevel());
+ }
+
+ private ComputedApiLevel getComputedApiLevelForMethodOnHolderWithMinApi(
+ CfInstruction instruction) {
+ if (!instruction.isInvoke()) {
+ return appView.computedMinApiLevel();
+ }
+ CfInvoke cfInvoke = instruction.asInvoke();
+ if (cfInvoke.isInvokeSpecial()) {
+ return appView.computedMinApiLevel();
+ }
+ DexType holderType = cfInvoke.getMethod().getHolderType();
+ if (!holderType.isClassType()) {
+ return appView.computedMinApiLevel();
+ }
+ DexClass clazz = appView.definitionFor(holderType);
+ if (clazz == null || !clazz.isLibraryClass()) {
+ return appView.computedMinApiLevel();
+ }
+ ComputedApiLevel apiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ cfInvoke.getMethod(), ComputedApiLevel.unknown());
+ if (apiLevel.isGreaterThan(appView.computedMinApiLevel())) {
+ ComputedApiLevel holderApiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ holderType, ComputedApiLevel.unknown());
+ if (holderApiLevel.isGreaterThan(appView.computedMinApiLevel())) {
+ // Do not outline where the holder is unknown or introduced later then min api.
+ // TODO(b/208978971): Describe where mocking is done when landing.
+ return appView.computedMinApiLevel();
+ }
+ return apiLevel;
+ }
+ return appView.computedMinApiLevel();
+ }
+
+ private Collection<CfInstruction> desugarLibraryCall(
+ UniqueContext context,
+ CfInstruction instruction,
+ ComputedApiLevel computedApiLevel,
+ DexItemFactory factory) {
+ DexMethod method = instruction.asInvoke().getMethod();
+ ProgramMethod programMethod = ensureOutlineMethod(context, method, computedApiLevel, factory);
+ return ImmutableList.of(new CfInvoke(INVOKESTATIC, programMethod.getReference(), false));
+ }
+
+ private ProgramMethod ensureOutlineMethod(
+ UniqueContext context,
+ DexMethod apiMethod,
+ ComputedApiLevel apiLevel,
+ DexItemFactory factory) {
+ DexClass libraryHolder = appView.definitionFor(apiMethod.getHolderType());
+ assert libraryHolder != null;
+ DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod);
+ DexProto proto =
+ factory.prependHolderToProtoIf(apiMethod, libraryApiMethodDefinition.isVirtualMethod());
+ return appView
+ .getSyntheticItems()
+ .createMethod(
+ SyntheticKind.API_MODEL_OUTLINE,
+ context,
+ appView,
+ syntheticMethodBuilder -> {
+ syntheticMethodBuilder
+ .setProto(proto)
+ .setAccessFlags(
+ MethodAccessFlags.builder()
+ .setPublic()
+ .setSynthetic()
+ .setStatic()
+ .setBridge()
+ .build())
+ .setApiLevelForDefinition(apiLevel)
+ .setApiLevelForCode(apiLevel)
+ .setCode(
+ m -> {
+ if (libraryApiMethodDefinition.isStatic()) {
+ return ForwardMethodBuilder.builder(factory)
+ .setStaticTarget(apiMethod, libraryHolder.isInterface())
+ .setStaticSource(apiMethod)
+ .build();
+ } else {
+ return ForwardMethodBuilder.builder(factory)
+ .setVirtualTarget(apiMethod, libraryHolder.isInterface())
+ .setNonStaticSource(apiMethod)
+ .build();
+ }
+ });
+ });
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
index 5c6d6c1..01c504c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
@@ -128,6 +128,13 @@
if (retargetLibraryMember.isEmpty() || !instruction.isInvoke()) {
return NO_REWRITING;
}
+ if (appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getDontRetargetLibMember()
+ .contains(context.getContextType())) {
+ return NO_REWRITING;
+ }
CfInvoke cfInvoke = instruction.asInvoke();
DexMethod invokedMethod = cfInvoke.getMethod();
InvokeRetargetingResult retarget =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
index b2308ae..af7fb9f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -15,9 +14,7 @@
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.utils.IterableUtils;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -38,11 +35,6 @@
for (DexProgramClass clazz : builder.getProgramClasses()) {
if (emulatedInterfaces.containsKey(clazz.type)) {
newProgramClasses.add(rewriteEmulatedInterface(clazz));
- } else if (clazz.isInterface()
- && !appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
- && isEmulatedInterfaceSubInterface(clazz)) {
- // Intentionally filter such classes out.
- handleEmulatedInterfaceSubInterface(clazz);
} else {
newProgramClasses.add(clazz);
}
@@ -50,38 +42,6 @@
builder.replaceProgramClasses(newProgramClasses);
}
- private void handleEmulatedInterfaceSubInterface(DexProgramClass clazz) {
- // TODO(b/183918843): Investigate how to specify these in the json file.
- // These are interfaces which needs a companion class for desugared library to work, but
- // the interface is not supported outside of desugared library. The interface has to be
- // present during the compilation for the companion class to be generated, but filtered out
- // afterwards. The companion class needs to be rewritten to have the desugared library
- // prefix since all classes in desugared library should have the prefix, we used the
- // questionable method convertJavaNameToDesugaredLibrary to generate a correct type.
- String newName =
- appView
- .options()
- .desugaredLibraryConfiguration
- .convertJavaNameToDesugaredLibrary(clazz.type);
- InterfaceMethodRewriter.addCompanionClassRewriteRule(clazz.type, newName, appView);
- }
-
- private boolean isEmulatedInterfaceSubInterface(DexClass subInterface) {
- assert !emulatedInterfaces.containsKey(subInterface.type);
- LinkedList<DexType> workList = new LinkedList<>(Arrays.asList(subInterface.interfaces.values));
- while (!workList.isEmpty()) {
- DexType next = workList.removeFirst();
- if (emulatedInterfaces.containsKey(next)) {
- return true;
- }
- DexClass nextClass = appView.definitionFor(next);
- if (nextClass != null) {
- workList.addAll(Arrays.asList(nextClass.interfaces.values));
- }
- }
- return false;
- }
-
// The method transforms emulated interface such as they now have the rewritten type and
// implement the rewritten version of each emulated interface they implement.
private DexProgramClass rewriteEmulatedInterface(DexProgramClass emulatedInterface) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index ca6c387..27a4893 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -126,8 +126,8 @@
if (current.isInvokeSuper()) {
InvokeSuper invoke = current.asInvokeSuper();
- // Check if the instruction can be rewritten to invoke-super. This allows inlining of the
- // enclosing method into contexts outside the current class.
+ // Check if the instruction can be rewritten to invoke-virtual. This allows inlining of
+ // the enclosing method into contexts outside the current class.
if (options.testing.enableInvokeSuperToInvokeVirtualRewriting) {
DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
if (singleTarget != null) {
@@ -150,16 +150,19 @@
// Rebind the invoke to the most specific target.
DexMethod invokedMethod = invoke.getInvokedMethod();
- DexClassAndMethod reboundTarget = rebindSuperInvokeToMostSpecific(invokedMethod, context);
- if (reboundTarget != null
- && reboundTarget.getReference() != invokedMethod
- && !isRebindingNewClassIntoMainDex(context, reboundTarget.getReference())) {
- it.replaceCurrentInstruction(
- new InvokeSuper(
- reboundTarget.getReference(),
- invoke.outValue(),
- invoke.arguments(),
- reboundTarget.getHolder().isInterface()));
+ DexClass reboundTargetClass = rebindSuperInvokeToMostSpecific(invokedMethod, context);
+ if (reboundTargetClass != null) {
+ DexMethod reboundMethod =
+ invokedMethod.withHolder(reboundTargetClass, appView.dexItemFactory());
+ if (reboundMethod != invokedMethod
+ && !isRebindingNewClassIntoMainDex(context, reboundMethod)) {
+ it.replaceCurrentInstruction(
+ new InvokeSuper(
+ reboundMethod,
+ invoke.outValue(),
+ invoke.arguments(),
+ reboundTargetClass.isInterface()));
+ }
}
continue;
}
@@ -318,8 +321,7 @@
}
/** This rebinds invoke-super instructions to their most specific target. */
- private DexClassAndMethod rebindSuperInvokeToMostSpecific(
- DexMethod target, ProgramMethod context) {
+ private DexClass rebindSuperInvokeToMostSpecific(DexMethod target, ProgramMethod context) {
DexClassAndMethod method = appView.appInfo().lookupSuperTarget(target, context);
if (method == null) {
return null;
@@ -336,7 +338,20 @@
return null;
}
- return method;
+ if (method.getHolder().isLibraryClass()) {
+ // We've found a library class as the new holder of the method. Since the library can only
+ // rebind to the library class boundary. Search from the target upwards until we find a
+ // library class.
+ DexClass lowerBound = appView.definitionFor(target.getHolderType(), context);
+ while (lowerBound != null
+ && lowerBound.isProgramClass()
+ && lowerBound != method.getHolder()) {
+ lowerBound = appView.definitionFor(lowerBound.superType, lowerBound.asProgramClass());
+ }
+ return lowerBound;
+ }
+
+ return method.getHolder();
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 4a24a4b..90065e6 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -37,7 +37,8 @@
private enum InvokeType {
STATIC,
VIRTUAL,
- SPECIAL
+ INTERFACE,
+ SPECIAL,
}
private final DexItemFactory factory;
@@ -115,7 +116,7 @@
public ForwardMethodBuilder setVirtualTarget(DexMethod method, boolean isInterface) {
targetMethod = method;
- invokeType = InvokeType.VIRTUAL;
+ invokeType = isInterface ? InvokeType.INTERFACE : InvokeType.VIRTUAL;
this.isInterface = isInterface;
return this;
}
@@ -243,6 +244,8 @@
return Opcodes.INVOKEVIRTUAL;
case SPECIAL:
return Opcodes.INVOKESPECIAL;
+ case INTERFACE:
+ return Opcodes.INVOKEINTERFACE;
}
throw new Unreachable("Unexpected invoke type: " + invokeType);
}
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 2121adc..351815d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -502,7 +502,7 @@
liveFields = new LiveFieldsSet(graphReporter::registerField);
apiLevelCompute = AndroidApiLevelCompute.create(appView);
if (mode.isInitialTreeShaking()) {
- desugaring = CfInstructionDesugaringCollection.create(appView);
+ desugaring = CfInstructionDesugaringCollection.create(appView, apiLevelCompute);
interfaceProcessor = new InterfaceProcessor(appView);
} else {
desugaring = CfInstructionDesugaringCollection.empty();
diff --git a/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java b/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
index 57f27cf..c42b3e0 100644
--- a/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
@@ -5,16 +5,13 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.IdentityHashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -38,15 +35,12 @@
public DexApplication prune(DexApplication app, PrefixRewritingMapper rewritePrefix) {
Map<DexType, DexProgramClass> typeMap = new IdentityHashMap<>();
- for (DexProgramClass aClass : app.classes()) {
- typeMap.put(aClass.type, aClass);
- }
List<DexProgramClass> toKeep = new ArrayList<>();
boolean pruneNestMember = false;
for (DexProgramClass aClass : app.classes()) {
+ typeMap.put(aClass.type, aClass);
if (rewritePrefix.hasRewrittenType(aClass.type, null)
- || emulatedInterfaces.contains(aClass.type)
- || interfaceImplementsEmulatedInterface(aClass, typeMap)) {
+ || emulatedInterfaces.contains(aClass.type)) {
toKeep.add(aClass);
} else {
pruneNestMember |= aClass.isInANest();
@@ -63,24 +57,4 @@
// of just doing nothing with it.
return app.builder().replaceProgramClasses(toKeep).build();
}
-
- private boolean interfaceImplementsEmulatedInterface(
- DexClass itf, Map<DexType, DexProgramClass> typeMap) {
- if (!itf.isInterface()) {
- return false;
- }
- LinkedList<DexType> workList = new LinkedList<>();
- Collections.addAll(workList, itf.interfaces.values);
- while (!workList.isEmpty()) {
- DexType dexType = workList.removeFirst();
- if (emulatedInterfaces.contains(dexType)) {
- return true;
- }
- if (typeMap.containsKey(dexType)) {
- DexProgramClass dexProgramClass = typeMap.get(dexType);
- Collections.addAll(workList, dexProgramClass.interfaces.values);
- }
- }
- return false;
- }
}
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 f8e241b..2d16065 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -713,7 +713,7 @@
new SyntheticProgramClassBuilder(type, kind, outerContext, appView.dexItemFactory());
DexProgramClass clazz =
classBuilder
- .addMethod(fn.andThen(m -> m.setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)))
+ .addMethod(fn.andThen(m -> m.setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_NAME)))
.build();
ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next());
addPendingDefinition(new SyntheticMethodDefinition(kind, outerContext, method));
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index 0af4bf0..b7861e3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -55,6 +55,10 @@
this.syntheticKind = syntheticKind;
}
+ public boolean hasName() {
+ return name != null;
+ }
+
public SyntheticMethodBuilder setName(String name) {
return setName(factory.createString(name));
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 41020e2..1bc77c4 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -34,13 +34,11 @@
RETARGET_INTERFACE("RetargetInterface", 21, false, true),
WRAPPER("$Wrapper", 22, false, true),
VIVIFIED_WRAPPER("$VivifiedWrapper", 23, false, true),
- ENUM_CONVERSION("$EnumConversion", 31, false, true),
LAMBDA("Lambda", 4, false),
INIT_TYPE_ARGUMENT("-IA", 5, false, true),
HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
- CONST_DYNAMIC("$Condy", 30, false),
// Method synthetics.
ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
RECORD_HELPER("Record", 9, true),
@@ -56,7 +54,10 @@
OUTLINE("Outline", 19, true),
API_CONVERSION("APIConversion", 26, true),
API_CONVERSION_PARAMETERS("APIConversionParameters", 28, true),
- EMULATED_INTERFACE_MARKER_CLASS("", 29, false, true, true);
+ EMULATED_INTERFACE_MARKER_CLASS("", 29, false, true, true),
+ CONST_DYNAMIC("$Condy", 30, false),
+ ENUM_CONVERSION("$EnumConversion", 31, false, true),
+ API_MODEL_OUTLINE("ApiModelOutline", 32, true, false, false);
static {
assert verifyNoOverlappingIds();
@@ -140,8 +141,8 @@
*/
private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR =
SYNTHETIC_CLASS_SEPARATOR + "ExternalSynthetic";
- /** Method prefix when generating synthetic methods in a class. */
- static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m";
+ /** Method name when generating synthetic methods in a class. */
+ static final String INTERNAL_SYNTHETIC_METHOD_NAME = "m";
static String getPrefixForExternalSyntheticType(SyntheticKind kind, DexType type) {
String binaryName = type.toBinaryName();
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 0498949..6e54e2a 100644
--- a/src/main/java/com/android/tools/r8/utils/AccessUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AccessUtils.java
@@ -4,33 +4,56 @@
package com.android.tools.r8.utils;
+import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
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 {
public static boolean isAccessibleInSameContextsAs(
- DexType newType, DexType oldType, DexDefinitionSupplier definitions) {
- DexItemFactory dexItemFactory = definitions.dexItemFactory();
+ DexType newType, DexType oldType, AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
DexType newBaseType = newType.toBaseType(dexItemFactory);
if (!newBaseType.isClassType()) {
return true;
}
- DexClass newBaseClass = definitions.definitionFor(newBaseType);
+ DexClass newBaseClass = appView.definitionFor(newBaseType);
if (newBaseClass == null) {
return false;
}
- if (newBaseClass.isPublic()) {
- return true;
- }
+
+ // If the new class is not public, then the old class must be non-public as well and reside in
+ // the same package as the new class.
DexType oldBaseType = oldType.toBaseType(dexItemFactory);
- assert oldBaseType.isClassType();
- DexClass oldBaseClass = definitions.definitionFor(oldBaseType);
- if (oldBaseClass == null || oldBaseClass.isPublic()) {
- return false;
+ if (!newBaseClass.isPublic()) {
+ assert oldBaseType.isClassType();
+ DexClass oldBaseClass = appView.definitionFor(oldBaseType);
+ if (oldBaseClass == null
+ || oldBaseClass.isPublic()
+ || !newBaseType.isSamePackage(oldBaseType)) {
+ return false;
+ }
}
- return newBaseType.isSamePackage(oldBaseType);
+
+ // 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);
+ if (!newFeatureSplit.isBase()
+ && newFeatureSplit
+ != classToFeatureSplitMap.getFeatureSplit(oldBaseType, syntheticItems)) {
+ return false;
+ }
+ }
+ }
+ return true;
}
}
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 fadaaa0..c1fbc42 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1382,7 +1382,7 @@
// TODO(b/205611444): Enable by default.
private boolean enableClassInitializerDeadlockDetection = true;
private boolean enableInterfaceMerging =
- System.getProperty("com.android.tools.r8.disableHorizontalInterfaceMerging") == null;
+ System.getProperty("com.android.tools.r8.enableHorizontalInterfaceMerging") != null;
private boolean enableInterfaceMergingInInitial = false;
private boolean enableSyntheticMerging = true;
private boolean ignoreRuntimeTypeChecksForTesting = false;
@@ -1458,6 +1458,10 @@
enableClassInitializerDeadlockDetection = true;
}
+ public void setEnableInterfaceMerging() {
+ enableInterfaceMerging = true;
+ }
+
public void setEnableInterfaceMergingInInitial() {
enableInterfaceMergingInInitial = true;
}
@@ -1481,6 +1485,8 @@
public boolean enableApiCallerIdentification = true;
public boolean checkAllApiReferencesAreSet = true;
+ public boolean enableStubbingOfClasses = false;
+ public boolean enableOutliningOfMethods = false;
public void visitMockedApiLevelsForReferences(
DexItemFactory factory, Consumer<AndroidApiForHashingClass> consumer) {
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 726acbd..68e132a 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -119,6 +119,7 @@
return self();
}
+ @Override
public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer)
throws IOException, E {
assertFailure();
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index af079d9..ebf30b6 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.debug.DexDebugTestConfig;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -39,6 +40,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -255,6 +257,35 @@
}
}
+ public CR addRunClasspathClassFileData(byte[]... classes) throws Exception {
+ return addRunClasspathClassFileData(Arrays.asList(classes));
+ }
+
+ public CR addRunClasspathClassFileData(Collection<byte[]> classes) throws Exception {
+ if (getBackend() == Backend.DEX) {
+ additionalRunClassPath.add(
+ testForD8(state.getTempFolder())
+ .addProgramClassFileData(classes)
+ .setMinApi(minApiLevel)
+ .compile()
+ .writeToZip());
+ return self();
+ }
+ assert getBackend() == Backend.CF;
+ try {
+ AndroidApp.Builder appBuilder = AndroidApp.builder();
+ for (byte[] clazz : classes) {
+ appBuilder.addClassProgramData(clazz, Origin.unknown());
+ }
+ Path path = state.getNewTempFolder().resolve("runtime-classes.jar");
+ appBuilder.build().writeToZip(path, OutputMode.ClassFile);
+ additionalRunClassPath.add(path);
+ return self();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
public CR addBootClasspathClasses(Class<?>... classes) throws Exception {
return addBootClasspathClasses(Arrays.asList(classes));
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index c4e86e3..4a6db32 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -56,6 +56,7 @@
options.testing.allowUnnecessaryDontWarnWildcards = false;
options.testing.reportUnusedProguardConfigurationRules = true;
options.horizontalClassMergerOptions().enable();
+ options.horizontalClassMergerOptions().setEnableInterfaceMerging();
};
final Backend backend;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMethodMissingClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMethodMissingClassTest.java
deleted file mode 100644
index 6b91898..0000000
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMethodMissingClassTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.apimodel;
-
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assume.assumeFalse;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.testing.AndroidBuildVersion;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.codeinspector.Matchers;
-import java.lang.reflect.Method;
-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 ApiModelMockMethodMissingClassTest extends TestBase {
-
- private final AndroidApiLevel initialLibraryMockLevel = AndroidApiLevel.M;
- private final AndroidApiLevel finalLibraryMethodLevel = AndroidApiLevel.O_MR1;
-
- @Parameter public TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
- }
-
- @Test
- public void testR8() throws Exception {
- // TODO(b/197078995): Make this work on 12.
- assumeFalse(
- parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V12_0_0));
- boolean preMockApis =
- parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(initialLibraryMockLevel);
- boolean postMockApis =
- !preMockApis && parameters.getApiLevel().isGreaterThanOrEqualTo(finalLibraryMethodLevel);
- boolean betweenMockApis = !preMockApis && !postMockApis;
- Method addedOn23 = LibraryClass.class.getMethod("addedOn23");
- Method adeddOn27 = LibraryClass.class.getMethod("addedOn27");
- testForR8(parameters.getBackend())
- .addProgramClasses(Main.class)
- .addLibraryClasses(LibraryClass.class)
- .addDefaultRuntimeLibrary(parameters)
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .addAndroidBuildVersion()
- .apply(setMockApiLevelForClass(LibraryClass.class, initialLibraryMockLevel))
- .apply(
- setMockApiLevelForDefaultInstanceInitializer(
- LibraryClass.class, initialLibraryMockLevel))
- .apply(setMockApiLevelForMethod(addedOn23, initialLibraryMockLevel))
- .apply(setMockApiLevelForMethod(adeddOn27, finalLibraryMethodLevel))
- .compile()
- .applyIf(
- parameters.isDexRuntime()
- && parameters
- .getRuntime()
- .maxSupportedApiLevel()
- .isGreaterThanOrEqualTo(initialLibraryMockLevel),
- b -> b.addBootClasspathClasses(LibraryClass.class))
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLinesIf(preMockApis, "Hello World")
- .assertSuccessWithOutputLinesIf(
- betweenMockApis,
- "LibraryClass::addedOn23",
- "LibraryClass::missingAndReferenced",
- "Hello World")
- .assertSuccessWithOutputLinesIf(
- postMockApis,
- "LibraryClass::addedOn23",
- "LibraryClass::missingAndReferenced",
- "LibraryCLass::addedOn27",
- "Hello World")
- .inspect(
- inspector -> {
- // TODO(b/204982782): Should be stubbed for api-level 1-23 with methods.
- assertThat(
- inspector.clazz(ApiModelMockClassTest.LibraryClass.class), Matchers.isAbsent());
- });
- }
-
- // Only present from api level 23.
- public static class LibraryClass {
-
- public void addedOn23() {
- System.out.println("LibraryClass::addedOn23");
- }
-
- public void addedOn27() {
- System.out.println("LibraryCLass::addedOn27");
- }
-
- public void missingAndReferenced() {
- System.out.println("LibraryClass::missingAndReferenced");
- }
-
- public void missingNotReferenced() {
- System.out.println("LibraryClass::missingNotReferenced");
- }
- }
-
- public static class Main {
-
- public static void main(String[] args) {
- if (AndroidBuildVersion.VERSION >= 23) {
- LibraryClass libraryClass = new LibraryClass();
- libraryClass.addedOn23();
- libraryClass.missingAndReferenced();
- if (AndroidBuildVersion.VERSION >= 27) {
- libraryClass.addedOn27();
- }
- }
- System.out.println("Hello World");
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoLibraryReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoLibraryReferenceTest.java
new file mode 100644
index 0000000..f80e5d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoLibraryReferenceTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+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.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import org.junit.Assert;
+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 ApiModelNoLibraryReferenceTest extends TestBase {
+
+ private static final String API_TYPE_NAME = "android.view.accessibility.AccessibilityEvent";
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ MethodReference main =
+ Reference.methodFromMethod(Main.class.getDeclaredMethod("main", String[].class));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(Main.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(AccessibilityEvent.class),
+ DescriptorUtils.javaTypeToDescriptor(API_TYPE_NAME))
+ .transform())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .setMinApi(parameters.getApiLevel())
+ .addDontWarn(API_TYPE_NAME)
+ .addKeepMainRule(Main.class)
+ .addAndroidBuildVersion()
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(
+ ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
+ (methodReference, apiLevel) -> {
+ if (methodReference.equals(main)) {
+ Assert.assertEquals(
+ parameters.isCfRuntime()
+ ? AndroidApiLevel.R
+ : AndroidApiLevel.R.max(parameters.getApiLevel()),
+ apiLevel);
+ }
+ }))
+ .compile();
+ }
+
+ /* Only here to get the test to compile */
+ public static class AccessibilityEvent {
+ public AccessibilityEvent() {}
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (AndroidBuildVersion.VERSION >= 4) {
+ // Will be rewritten to android.view.accessibility.AccessibilityEvent.
+ new AccessibilityEvent();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
new file mode 100644
index 0000000..71bb770
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Method;
+import java.util.Optional;
+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 ApiModelOutlineDuplicateMethodTest extends TestBase {
+
+ private final AndroidApiLevel classApiLevel = AndroidApiLevel.K;
+ private final AndroidApiLevel methodApiLevel = AndroidApiLevel.M;
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeFalse(
+ parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V12_0_0));
+ boolean isMethodApiLevel =
+ parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(methodApiLevel);
+ Method adeddOn23 = LibraryClass.class.getMethod("addedOn23");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, TestClass.class)
+ .addLibraryClasses(LibraryClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addAndroidBuildVersion()
+ .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+ .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
+ .apply(setMockApiLevelForMethod(adeddOn23, methodApiLevel))
+ .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+ .enableInliningAnnotations()
+ .compile()
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .maxSupportedApiLevel()
+ .isGreaterThanOrEqualTo(classApiLevel),
+ b -> b.addBootClasspathClasses(LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLinesIf(!isMethodApiLevel, "Hello World")
+ .assertSuccessWithOutputLinesIf(
+ isMethodApiLevel, "LibraryClass::addedOn23", "LibraryClass::addedOn23", "Hello World")
+ .inspect(
+ inspector -> {
+ // No need to check further on CF.
+ Optional<FoundMethodSubject> synthesizedAddedOn23 =
+ inspector.allClasses().stream()
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && invokesMethodWithName("addedOn23").matches(methodSubject))
+ .findFirst();
+ if (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(classApiLevel)) {
+ assertFalse(synthesizedAddedOn23.isPresent());
+ assertEquals(3, inspector.allClasses().size());
+ } else if (parameters.getApiLevel().isLessThan(methodApiLevel)) {
+ assertTrue(synthesizedAddedOn23.isPresent());
+ assertEquals(4, inspector.allClasses().size());
+ ClassSubject testClass = inspector.clazz(TestClass.class);
+ assertThat(testClass, isPresent());
+ MethodSubject testMethod = testClass.uniqueMethodWithName("test");
+ assertThat(testMethod, isPresent());
+ assertEquals(
+ 2,
+ testMethod
+ .streamInstructions()
+ .filter(
+ instructionSubject -> {
+ if (!instructionSubject.isInvoke()) {
+ return false;
+ }
+ return instructionSubject
+ .getMethod()
+ .asMethodReference()
+ .equals(synthesizedAddedOn23.get().asMethodReference());
+ })
+ .count());
+ } else {
+ // No outlining on this api level.
+ assertFalse(synthesizedAddedOn23.isPresent());
+ assertEquals(3, inspector.allClasses().size());
+ }
+ });
+ }
+
+ // Only present from api level 19.
+ public static class LibraryClass {
+
+ public void addedOn23() {
+ System.out.println("LibraryClass::addedOn23");
+ }
+ }
+
+ public static class TestClass {
+
+ @NeverInline
+ public static void test() {
+ if (AndroidBuildVersion.VERSION >= 19) {
+ LibraryClass libraryClass = new LibraryClass();
+ if (AndroidBuildVersion.VERSION >= 23) {
+ libraryClass.addedOn23();
+ libraryClass.addedOn23();
+ }
+ }
+ System.out.println("Hello World");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ TestClass.test();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
new file mode 100644
index 0000000..436d2fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
@@ -0,0 +1,223 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelOutlineHorizontalMergingTest extends TestBase {
+
+ private final AndroidApiLevel libraryClassApiLevel = AndroidApiLevel.K;
+ private final AndroidApiLevel otherLibraryClassApiLevel = AndroidApiLevel.K;
+ private final AndroidApiLevel firstMethodApiLevel = AndroidApiLevel.M;
+ private final AndroidApiLevel secondMethodApiLevel = AndroidApiLevel.O_MR1;
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeFalse(
+ parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V12_0_0));
+ boolean beforeFirstApiMethodLevel =
+ parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(firstMethodApiLevel);
+ boolean afterSecondApiMethodLevel =
+ parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(secondMethodApiLevel);
+ boolean betweenMethodApiLevels = !beforeFirstApiMethodLevel && !afterSecondApiMethodLevel;
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, TestClass.class)
+ .addLibraryClasses(LibraryClass.class, OtherLibraryClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addAndroidBuildVersion()
+ .apply(setMockApiLevelForClass(LibraryClass.class, libraryClassApiLevel))
+ .apply(
+ setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, libraryClassApiLevel))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryClass.class.getMethod("addedOn23"), firstMethodApiLevel))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryClass.class.getMethod("addedOn27"), secondMethodApiLevel))
+ .apply(setMockApiLevelForClass(OtherLibraryClass.class, otherLibraryClassApiLevel))
+ .apply(
+ setMockApiLevelForDefaultInstanceInitializer(
+ OtherLibraryClass.class, otherLibraryClassApiLevel))
+ .apply(
+ setMockApiLevelForMethod(
+ OtherLibraryClass.class.getMethod("addedOn23"), firstMethodApiLevel))
+ .apply(
+ setMockApiLevelForMethod(
+ OtherLibraryClass.class.getMethod("addedOn27"), secondMethodApiLevel))
+ .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+ .enableInliningAnnotations()
+ .compile()
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .maxSupportedApiLevel()
+ .isGreaterThanOrEqualTo(libraryClassApiLevel),
+ b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLinesIf(beforeFirstApiMethodLevel, "Hello World")
+ .assertSuccessWithOutputLinesIf(
+ betweenMethodApiLevels,
+ "LibraryClass::addedOn23",
+ "OtherLibraryClass::addedOn23",
+ "Hello World")
+ .assertSuccessWithOutputLinesIf(
+ afterSecondApiMethodLevel,
+ "LibraryClass::addedOn23",
+ "LibraryClass::addedOn27",
+ "OtherLibraryClass::addedOn23",
+ "OtherLibraryClass::addedOn27",
+ "Hello World")
+ .inspect(
+ inspector -> {
+ // No need to check further on CF.
+ List<FoundMethodSubject> outlinedAddedOn23 =
+ inspector.allClasses().stream()
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && invokesMethodWithName("addedOn23").matches(methodSubject))
+ .collect(Collectors.toList());
+ List<FoundMethodSubject> outlinedAddedOn27 =
+ inspector.allClasses().stream()
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && invokesMethodWithName("addedOn27").matches(methodSubject))
+ .collect(Collectors.toList());
+ if (parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThan(libraryClassApiLevel)) {
+ assertTrue(outlinedAddedOn23.isEmpty());
+ assertTrue(outlinedAddedOn27.isEmpty());
+ assertEquals(3, inspector.allClasses().size());
+ } else if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
+ // We have generated 4 outlines two having api level 23 and two having api level 27.
+ // Check that the levels are horizontally merged.
+ assertEquals(5, inspector.allClasses().size());
+ assertEquals(2, outlinedAddedOn23.size());
+ assertTrue(
+ outlinedAddedOn23.stream()
+ .allMatch(
+ outline ->
+ outline.getMethod().getHolderType()
+ == outlinedAddedOn23.get(0).getMethod().getHolderType()));
+ assertEquals(2, outlinedAddedOn27.size());
+ assertTrue(
+ outlinedAddedOn27.stream()
+ .allMatch(
+ outline ->
+ outline.getMethod().getHolderType()
+ == outlinedAddedOn27.get(0).getMethod().getHolderType()));
+ } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
+ assertTrue(outlinedAddedOn23.isEmpty());
+ assertEquals(4, inspector.allClasses().size());
+ assertEquals(2, outlinedAddedOn27.size());
+ assertTrue(
+ outlinedAddedOn27.stream()
+ .allMatch(
+ outline ->
+ outline.getMethod().getHolderType()
+ == outlinedAddedOn27.get(0).getMethod().getHolderType()));
+ } else {
+ // No outlining on this api level.
+ assertTrue(outlinedAddedOn23.isEmpty());
+ assertTrue(outlinedAddedOn27.isEmpty());
+ assertEquals(3, inspector.allClasses().size());
+ }
+ });
+ }
+
+ // Only present from api level 19.
+ public static class LibraryClass {
+
+ public void addedOn23() {
+ System.out.println("LibraryClass::addedOn23");
+ }
+
+ public void addedOn27() {
+ System.out.println("LibraryClass::addedOn27");
+ }
+ }
+
+ // Only present from api level 19.
+ public static class OtherLibraryClass {
+
+ public void addedOn23() {
+ System.out.println("OtherLibraryClass::addedOn23");
+ }
+
+ public static void addedOn27() {
+ System.out.println("OtherLibraryClass::addedOn27");
+ }
+ }
+
+ public static class TestClass {
+
+ @NeverInline
+ public static void test() {
+ if (AndroidBuildVersion.VERSION >= 19) {
+ LibraryClass libraryClass = new LibraryClass();
+ if (AndroidBuildVersion.VERSION >= 23) {
+ libraryClass.addedOn23();
+ }
+ if (AndroidBuildVersion.VERSION >= 27) {
+ libraryClass.addedOn27();
+ }
+ }
+ if (AndroidBuildVersion.VERSION >= 19) {
+ OtherLibraryClass otherLibraryClass = new OtherLibraryClass();
+ if (AndroidBuildVersion.VERSION >= 23) {
+ otherLibraryClass.addedOn23();
+ }
+ if (AndroidBuildVersion.VERSION >= 27) {
+ OtherLibraryClass.addedOn27();
+ }
+ }
+ System.out.println("Hello World");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ TestClass.test();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
new file mode 100644
index 0000000..4d6a526
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
@@ -0,0 +1,237 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Method;
+import java.util.Optional;
+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 ApiModelOutlineMethodMissingClassTest extends TestBase {
+
+ private final AndroidApiLevel initialLibraryMockLevel = AndroidApiLevel.M;
+ private final AndroidApiLevel finalLibraryMethodLevel = AndroidApiLevel.O_MR1;
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ // TODO(b/197078995): Make this work on 12.
+ assumeFalse(
+ parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V12_0_0));
+ boolean preMockApis =
+ parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(initialLibraryMockLevel);
+ boolean postMockApis =
+ !preMockApis && parameters.getApiLevel().isGreaterThanOrEqualTo(finalLibraryMethodLevel);
+ boolean betweenMockApis = !preMockApis && !postMockApis;
+ Method addedOn23 = LibraryClass.class.getMethod("addedOn23");
+ Method adeddOn27 = LibraryClass.class.getMethod("addedOn27");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, TestClass.class)
+ .addLibraryClasses(LibraryClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addAndroidBuildVersion()
+ .apply(setMockApiLevelForClass(LibraryClass.class, initialLibraryMockLevel))
+ .apply(
+ setMockApiLevelForDefaultInstanceInitializer(
+ LibraryClass.class, initialLibraryMockLevel))
+ .apply(setMockApiLevelForMethod(addedOn23, initialLibraryMockLevel))
+ .apply(setMockApiLevelForMethod(adeddOn27, finalLibraryMethodLevel))
+ .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+ .enableInliningAnnotations()
+ .compile()
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .maxSupportedApiLevel()
+ .isGreaterThanOrEqualTo(initialLibraryMockLevel),
+ b -> b.addBootClasspathClasses(LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLinesIf(preMockApis, "Hello World")
+ .assertSuccessWithOutputLinesIf(
+ betweenMockApis,
+ "LibraryClass::addedOn23",
+ "LibraryClass::missingAndReferenced",
+ "Hello World")
+ .assertSuccessWithOutputLinesIf(
+ postMockApis,
+ "LibraryClass::addedOn23",
+ "LibraryClass::missingAndReferenced",
+ "LibraryCLass::addedOn27",
+ "Hello World")
+ .inspect(
+ inspector -> {
+ // No need to check further on CF.
+ if (parameters.isCfRuntime()) {
+ assertEquals(3, inspector.allClasses().size());
+ return;
+ }
+ ClassSubject testClass = inspector.clazz(TestClass.class);
+ assertThat(testClass, isPresent());
+ MethodSubject testMethod = testClass.uniqueMethodWithName("test");
+ assertThat(testMethod, isPresent());
+ Optional<FoundMethodSubject> synthesizedAddedOn27 =
+ inspector.allClasses().stream()
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && invokesMethodWithName("addedOn27").matches(methodSubject))
+ .findFirst();
+ Optional<FoundMethodSubject> synthesizedMissingAndReferenced =
+ inspector.allClasses().stream()
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && invokesMethodWithName("missingAndReferenced")
+ .matches(methodSubject))
+ .findFirst();
+ Optional<FoundMethodSubject> synthesizedMissingNotReferenced =
+ inspector.allClasses().stream()
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && invokesMethodWithName("missingNotReferenced")
+ .matches(methodSubject))
+ .findFirst();
+ assertFalse(synthesizedMissingNotReferenced.isPresent());
+ if (parameters.getApiLevel().isLessThan(AndroidApiLevel.M)) {
+ assertEquals(3, inspector.allClasses().size());
+ assertFalse(synthesizedAddedOn27.isPresent());
+ assertFalse(synthesizedMissingAndReferenced.isPresent());
+ } else if (parameters.getApiLevel().isLessThan(AndroidApiLevel.O_MR1)) {
+ assertEquals(5, inspector.allClasses().size());
+ assertTrue(synthesizedAddedOn27.isPresent());
+ inspectRewrittenToOutline(testMethod, synthesizedAddedOn27.get(), "addedOn27");
+ assertTrue(synthesizedMissingAndReferenced.isPresent());
+ inspectRewrittenToOutline(
+ testMethod, synthesizedMissingAndReferenced.get(), "missingAndReferenced");
+ } else {
+ assertEquals(4, inspector.allClasses().size());
+ assertFalse(synthesizedAddedOn27.isPresent());
+ assertTrue(synthesizedMissingAndReferenced.isPresent());
+ inspectRewrittenToOutline(
+ testMethod, synthesizedMissingAndReferenced.get(), "missingAndReferenced");
+ }
+ });
+ }
+
+ private void inspectRewrittenToOutline(
+ MethodSubject callerSubject, FoundMethodSubject outline, String apiMethodName)
+ throws Exception {
+ // Check that the library reference is no longer present.
+ MethodReference libraryMethodReference =
+ Reference.methodFromMethod(LibraryClass.class.getDeclaredMethod(apiMethodName));
+ assertFalse(
+ callerSubject
+ .streamInstructions()
+ .anyMatch(
+ instructionSubject -> {
+ if (!instructionSubject.isInvoke()) {
+ return false;
+ }
+ return instructionSubject
+ .getMethod()
+ .asMethodReference()
+ .equals(libraryMethodReference);
+ }));
+ MethodReference outlineReference = outline.getMethod().getReference().asMethodReference();
+ assertEquals(
+ 1,
+ callerSubject
+ .streamInstructions()
+ .filter(
+ instructionSubject -> {
+ if (!instructionSubject.isInvoke()) {
+ return false;
+ }
+ return instructionSubject
+ .getMethod()
+ .asMethodReference()
+ .equals(outlineReference);
+ })
+ .count());
+ }
+
+ // Only present from api level 23.
+ public static class LibraryClass {
+
+ public void addedOn23() {
+ System.out.println("LibraryClass::addedOn23");
+ }
+
+ public void addedOn27() {
+ System.out.println("LibraryCLass::addedOn27");
+ }
+
+ public void missingAndReferenced() {
+ System.out.println("LibraryClass::missingAndReferenced");
+ }
+
+ public void missingNotReferenced() {
+ System.out.println("LibraryClass::missingNotReferenced");
+ }
+ }
+
+ public static class TestClass {
+
+ @NeverInline
+ public static void test() {
+ if (AndroidBuildVersion.VERSION >= 23) {
+ LibraryClass libraryClass = new LibraryClass();
+ libraryClass.addedOn23();
+ libraryClass.missingAndReferenced();
+ if (AndroidBuildVersion.VERSION >= 27) {
+ libraryClass.addedOn27();
+ }
+ }
+ System.out.println("Hello World");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ TestClass.test();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index c8ee027..a5c2487 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -105,6 +105,20 @@
});
}
+ static void enableStubbingOfClasses(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options.apiModelingOptions().enableStubbingOfClasses = true;
+ });
+ }
+
+ static void enableOutliningOfMethods(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options.apiModelingOptions().enableOutliningOfMethods = true;
+ });
+ }
+
static void disableCheckAllApiReferencesAreNotUnknown(
TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
compilerBuilder.addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/apimodel/DuplicateClassTest.java b/src/test/java/com/android/tools/r8/apimodel/DuplicateClassTest.java
new file mode 100644
index 0000000..458bb9d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/DuplicateClassTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * A simple test showing that adding a program class as a duplicate for a bootclasspath class for
+ * dalvik causes an error where the referencing class cannot be optimized (b/208978971).
+ */
+public class DuplicateClassTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(Main.class).removeInnerClasses().transform(),
+ transformer(Foo.class)
+ .setClassDescriptor("Ljava/lang/Exception;")
+ .removeInnerClasses()
+ .transform())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World")
+ .applyIf(
+ parameters.getDexRuntimeVersion().isDalvik(),
+ result ->
+ assertThat(
+ result.getStdErr(),
+ containsString(
+ "DexOpt: not resolving ambiguous class 'Ljava/lang/Exception;'")),
+ result ->
+ assertThat(
+ result.getStdErr(),
+ not(containsString("not resolving ambiguous class 'Ljava/lang/Exception;'"))));
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Exception exception = new Exception("Hello World");
+ System.out.println(exception.getMessage());
+ }
+ }
+
+ public static class /* java.lang.Exception */ Foo {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index ac25355..6eeabe2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -210,7 +210,7 @@
}
private boolean someLibraryDesugaringRequired() {
- return parameters.getApiLevel().getLevel() <= AndroidApiLevel.N.getLevel();
+ return requiresAnyCoreLibDesugaring(parameters);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index 9954a41..5aeaddc 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -62,6 +62,7 @@
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(),
+ 0,
Timing.empty());
}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
new file mode 100644
index 0000000..45a3be4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+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 DexSplitterFieldTypeStrengtheningTest extends SplitterTestBase {
+
+ public static final String EXPECTED = StringUtils.lines("FeatureClass");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public DexSplitterFieldTypeStrengtheningTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @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 =
+ testR8Splitter(
+ parameters,
+ ImmutableSet.of(BaseSuperClass.class),
+ ImmutableSet.of(FeatureClass.class),
+ FeatureClass.class,
+ compileResult ->
+ compileResult.inspect(
+ inspector -> {
+ ClassSubject baseSuperClassSubject = inspector.clazz(BaseSuperClass.class);
+ assertThat(baseSuperClassSubject, isPresent());
+
+ FieldSubject fieldSubject = baseSuperClassSubject.uniqueFieldWithName("f");
+ assertThat(fieldSubject, isPresent());
+ assertEquals(
+ Object.class.getTypeName(),
+ fieldSubject.getField().getType().getTypeName());
+ }),
+ ThrowableConsumer.empty());
+ assertEquals(processResult.exitCode, 0);
+ assertEquals(processResult.stdout, EXPECTED);
+ }
+
+ public abstract static class BaseSuperClass implements RunInterface {
+
+ public static Object f;
+
+ @Override
+ public void run() {
+ setFieldFromFeature();
+ System.out.println(f);
+ }
+
+ public abstract void setFieldFromFeature();
+ }
+
+ public static class FeatureClass extends BaseSuperClass {
+
+ @Override
+ public void setFieldFromFeature() {
+ BaseSuperClass.f = this;
+ }
+
+ @Override
+ public String toString() {
+ return "FeatureClass";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizeLibrarySuperTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizeLibrarySuperTest.java
new file mode 100644
index 0000000..e82bd6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizeLibrarySuperTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.devirtualize;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DevirtualizeLibrarySuperTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ boolean hasNewLibraryHierarchyOnClassPath =
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.M);
+ testForR8(parameters.getBackend())
+ .addLibraryClasses(Library.class, LibraryOverride.class, LibraryBoundary.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addProgramClasses(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject fooMethod = inspector.clazz(Main.class).uniqueMethodWithName("foo");
+ assertThat(fooMethod, isPresent());
+ assertThat(
+ fooMethod,
+ not(invokesMethodWithHolderAndName(LibraryOverride.class.getTypeName(), "foo")));
+ })
+ .applyIf(
+ hasNewLibraryHierarchyOnClassPath,
+ b ->
+ b.addRunClasspathClasses(
+ Library.class, LibraryOverride.class, LibraryBoundary.class),
+ b ->
+ b.addRunClasspathClassFileData(
+ transformer(Library.class).transform(),
+ transformer(LibraryBoundary.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(LibraryOverride.class), descriptor(Library.class))
+ .setSuper(descriptor(Library.class))
+ .transform()))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLinesIf(hasNewLibraryHierarchyOnClassPath, "LibraryOverride::foo")
+ .assertSuccessWithOutputLinesIf(!hasNewLibraryHierarchyOnClassPath, "Library::foo");
+ }
+
+ public static class Library {
+
+ public void foo() {
+ System.out.println("Library::foo");
+ }
+ }
+
+ // This class will is inserted in the hierarchy from api 23.
+ public static class LibraryOverride extends Library {
+
+ @Override
+ public void foo() {
+ System.out.println("LibraryOverride::foo");
+ }
+ }
+
+ public static class LibraryBoundary extends LibraryOverride {}
+
+ public static class Main extends LibraryBoundary {
+
+ @NeverInline
+ @Override
+ public void foo() {
+ super.foo();
+ }
+
+ public static void main(String[] args) {
+ new Main().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/TargetedButNotLiveLambdaAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/TargetedButNotLiveLambdaAfterDevirtualizationTest.java
new file mode 100644
index 0000000..999e2f4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/TargetedButNotLiveLambdaAfterDevirtualizationTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Reproduction of b/209475782. */
+@RunWith(Parameterized.class)
+public class TargetedButNotLiveLambdaAfterDevirtualizationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class Main {
+
+ static I f;
+ static I f2;
+
+ public static void main(String[] args) {
+ if (alwaysFalse()) {
+ setFields();
+ }
+ if (unknownButFalse()) {
+ callLambdaMethods();
+ }
+ }
+
+ static boolean alwaysFalse() {
+ return false;
+ }
+
+ static boolean unknownButFalse() {
+ return System.currentTimeMillis() < 0;
+ }
+
+ @NeverInline
+ static void setFields() {
+ f = () -> System.out.println("Foo!");
+ f2 = () -> System.out.println("Bar!");
+ }
+
+ @NeverInline
+ static void callLambdaMethods() {
+ f.m();
+ f2.m();
+ }
+ }
+
+ interface I {
+
+ void m();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index a03354e..19550d1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -25,10 +24,10 @@
import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
import com.android.tools.r8.utils.codeinspector.KmValueParameterSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -77,13 +76,20 @@
}
@Test
- @Ignore("b/154300326")
- public void testMetadataInExtensionFunction_merged() throws Exception {
+ public void testMetadataInExtensionFunction_merged_compat() throws Exception {
+ testMetadataInExtensionFunction_merged(false);
+ }
+
+ @Test
+ public void testMetadataInExtensionFunction_merged_full() throws Exception {
+ testMetadataInExtensionFunction_merged(true);
+ }
+
+ public void testMetadataInExtensionFunction_merged(boolean full) throws Exception {
Path libJar =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- extLibJarMap.getForConfiguration(kotlinc, targetVersion),
- kotlinc.getKotlinAnnotationJar())
+ (full ? testForR8(parameters.getBackend()) : testForR8Compat(parameters.getBackend()))
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(extLibJarMap.getForConfiguration(kotlinc, targetVersion))
// Keep the B class and its interface (which has the doStuff method).
.addKeepRules("-keep class **.B")
.addKeepRules("-keep class **.I { <methods>; }")
@@ -95,7 +101,7 @@
.addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
.addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
.compile()
- .inspect(this::inspectMerged)
+ .inspect(inspector -> inspectMerged(inspector, full))
.writeToZip();
Path output =
@@ -103,7 +109,10 @@
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/extension_function_app", "main"))
.setOutputPath(temp.newFolder().toPath())
- .compile();
+ .compile(full || kotlinParameters.isOlderThanMinSupported());
+ if (full || kotlinParameters.isOlderThanMinSupported()) {
+ return;
+ }
testForJvm()
.addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
@@ -112,11 +121,11 @@
.assertSuccessWithOutput(EXPECTED);
}
- private void inspectMerged(CodeInspector inspector) {
+ private void inspectMerged(CodeInspector inspector, boolean full) {
String superClassName = PKG + ".extension_function_lib.Super";
String bClassName = PKG + ".extension_function_lib.B";
- assertThat(inspector.clazz(superClassName), not(isPresent()));
+ assertThat(inspector.clazz(superClassName), Matchers.notIf(isPresent(), full));
ClassSubject impl = inspector.clazz(bClassName);
assertThat(impl, isPresentAndNotRenamed());
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index 67401c2..3a8760a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -24,10 +24,10 @@
import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
+import com.android.tools.r8.utils.codeinspector.Matchers;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -76,13 +76,20 @@
}
@Test
- @Ignore("b/154300326")
- public void testMetadataInExtensionProperty_merged() throws Exception {
+ public void testMetadataInExtensionProperty_merged_compat() throws Exception {
+ testMetadataInExtensionProperty_merged(false);
+ }
+
+ @Test
+ public void testMetadataInExtensionProperty_merged_full() throws Exception {
+ testMetadataInExtensionProperty_merged(true);
+ }
+
+ public void testMetadataInExtensionProperty_merged(boolean full) throws Exception {
Path libJar =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- extLibJarMap.getForConfiguration(kotlinc, targetVersion),
- kotlinc.getKotlinAnnotationJar())
+ (full ? testForR8(parameters.getBackend()) : testForR8Compat(parameters.getBackend()))
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(extLibJarMap.getForConfiguration(kotlinc, targetVersion))
// Keep the B class and its interface (which has the doStuff method).
.addKeepRules("-keep class **.B")
.addKeepRules("-keep class **.I { <methods>; }")
@@ -91,7 +98,7 @@
.addKeepRules("-keep class **.BKt { <methods>; }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.compile()
- .inspect(this::inspectMerged)
+ .inspect(inspector -> inspectMerged(inspector, full))
.writeToZip();
Path output =
@@ -99,7 +106,10 @@
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/extension_property_app", "main"))
.setOutputPath(temp.newFolder().toPath())
- .compile();
+ .compile(full || kotlinParameters.isOlderThanMinSupported());
+ if (full || kotlinParameters.isOlderThanMinSupported()) {
+ return;
+ }
testForJvm()
.addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
@@ -108,12 +118,12 @@
.assertSuccessWithOutput(EXPECTED);
}
- private void inspectMerged(CodeInspector inspector) {
+ private void inspectMerged(CodeInspector inspector, boolean full) {
String superClassName = PKG + ".extension_property_lib.Super";
String bClassName = PKG + ".extension_property_lib.B";
String bKtClassName = PKG + ".extension_property_lib.BKt";
- assertThat(inspector.clazz(superClassName), not(isPresent()));
+ assertThat(inspector.clazz(superClassName), Matchers.notIf(isPresent(), full));
ClassSubject impl = inspector.clazz(bClassName);
assertThat(impl, isPresentAndNotRenamed());
@@ -124,7 +134,7 @@
assertTrue(superTypes.stream().noneMatch(
supertype -> supertype.getFinalDescriptor().contains("Super")));
KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName("doStuff");
- assertThat(kmFunction, isPresent());
+ assertThat(kmFunction, not(isPresent()));
assertThat(kmFunction, not(isExtensionFunction()));
ClassSubject bKt = inspector.clazz(bKtClassName);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index 10afff3..875b475 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -22,11 +22,11 @@
import com.android.tools.r8.utils.codeinspector.KmClassSubject;
import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -75,13 +75,20 @@
}
@Test
- @Ignore("b/154300326")
- public void testMetadataInFunction_merged() throws Exception {
+ public void testMetadataInFunction_merged_compat() throws Exception {
+ testMetadataInFunction_merged(false);
+ }
+
+ @Test
+ public void testMetadataInFunction_merged_full() throws Exception {
+ testMetadataInFunction_merged(true);
+ }
+
+ public void testMetadataInFunction_merged(boolean full) throws Exception {
Path libJar =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- funLibJarMap.getForConfiguration(kotlinc, targetVersion),
- kotlinc.getKotlinAnnotationJar())
+ (full ? testForR8(parameters.getBackend()) : testForR8Compat(parameters.getBackend()))
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(funLibJarMap.getForConfiguration(kotlinc, targetVersion))
// Keep the B class and its interface (which has the doStuff method).
.addKeepRules("-keep class **.B")
.addKeepRules("-keep class **.I { <methods>; }")
@@ -89,7 +96,7 @@
.addKeepRules("-keep class **.BKt { <methods>; }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.compile()
- .inspect(this::inspectMerged)
+ .inspect(inspector -> inspectMerged(inspector, full))
.writeToZip();
Path output =
@@ -97,7 +104,10 @@
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/function_app", "main"))
.setOutputPath(temp.newFolder().toPath())
- .compile();
+ .compile(full || kotlinParameters.isOlderThanMinSupported());
+ if (full || kotlinParameters.isOlderThanMinSupported()) {
+ return;
+ }
testForJvm()
.addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
@@ -106,12 +116,12 @@
.assertSuccessWithOutput(EXPECTED);
}
- private void inspectMerged(CodeInspector inspector) {
+ private void inspectMerged(CodeInspector inspector, boolean full) {
String superClassName = PKG + ".function_lib.Super";
String bClassName = PKG + ".function_lib.B";
String bKtClassName = PKG + ".function_lib.BKt";
- assertThat(inspector.clazz(superClassName), not(isPresent()));
+ assertThat(inspector.clazz(superClassName), Matchers.notIf(isPresent(), full));
ClassSubject impl = inspector.clazz(bClassName);
assertThat(impl, isPresentAndNotRenamed());
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/StringPoolSizeWithLazyDexStringsTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/StringPoolSizeWithLazyDexStringsTest.java
new file mode 100644
index 0000000..8981bd9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/StringPoolSizeWithLazyDexStringsTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.sourcefile;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.DexSegments;
+import com.android.tools.r8.DexSegments.Command;
+import com.android.tools.r8.DexSegments.SegmentInfo;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.SourceFileEnvironment;
+import com.android.tools.r8.SourceFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringPoolSizeWithLazyDexStringsTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public StringPoolSizeWithLazyDexStringsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StringPoolSizeWithLazyDexStringsTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ // Ensure we have a source file that depends on the hash.
+ .apply(
+ b ->
+ b.getBuilder()
+ .setSourceFileProvider(
+ new SourceFileProvider() {
+ @Override
+ public String get(SourceFileEnvironment environment) {
+ return environment.getMapHash();
+ }
+ }))
+ .compile()
+ // Ensure we are computing a mapping file.
+ .apply(r -> assertFalse(r.getProguardMap().isEmpty()))
+ .apply(this::checkStringSegmentSize)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput("");
+ }
+
+ private void checkStringSegmentSize(R8TestCompileResult result) throws Exception {
+ Map<Integer, SegmentInfo> segments =
+ DexSegments.run(Command.builder().addProgramFiles(result.writeToZip()).build());
+ SegmentInfo stringInfo = segments.get(Constants.TYPE_STRING_ID_ITEM);
+ assertEquals(8, stringInfo.getItemCount());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ // Empty program to reduce string count.
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 92774e1..6264a4b 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -17,7 +17,7 @@
public class SyntheticItemsTestUtils {
public static String syntheticMethodName() {
- return SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX;
+ return SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_NAME;
}
public static ClassReference syntheticCompanionClass(Class<?> clazz) {
diff --git a/tools/r8_release.py b/tools/r8_release.py
index f9d14f9..d60baa0 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -15,7 +15,7 @@
import utils
-R8_DEV_BRANCH = '3.2'
+R8_DEV_BRANCH = '3.3'
R8_VERSION_FILE = os.path.join(
'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')