Merge commit '4b1e8d39e2fc46d3275b81fff407a41ad7d9b1e1' into dev-release
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 6e3764a..7c4ac49 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -34,6 +34,7 @@
private Path proguardMapFile = null;
private boolean useSmali = false;
private boolean allInfo = false;
+ private boolean noCode = false;
private boolean useIr;
@Override
@@ -70,6 +71,11 @@
return this;
}
+ public Builder setNoCode(boolean noCode) {
+ this.noCode = noCode;
+ return this;
+ }
+
@Override
protected DisassembleCommand makeCommand() {
// If printing versions ignore everything else.
@@ -82,7 +88,8 @@
proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
allInfo,
useSmali,
- useIr);
+ useIr,
+ noCode);
}
}
@@ -93,6 +100,7 @@
+ " --all # Include all information in disassembly.\n"
+ " --smali # Disassemble using smali syntax.\n"
+ " --ir # Print IR before and after optimization.\n"
+ + " --nocode # No printing of code objects.\n"
+ " --pg-map <file> # Proguard map <file> for mapping names.\n"
+ " --pg-map-charset <charset> # Charset for Proguard map file.\n"
+ " --output # Specify a file or directory to write to.\n"
@@ -102,6 +110,7 @@
private final boolean allInfo;
private final boolean useSmali;
private final boolean useIr;
+ private final boolean noCode;
public static Builder builder() {
return new Builder();
@@ -128,6 +137,8 @@
builder.setUseSmali(true);
} else if (arg.equals("--ir")) {
builder.setUseIr(true);
+ } else if (arg.equals("--nocode")) {
+ builder.setNoCode(true);
} else if (arg.equals("--pg-map")) {
builder.setProguardMapFile(Paths.get(args[++i]));
} else if (arg.equals("--pg-map-charset")) {
@@ -161,13 +172,15 @@
StringResource proguardMap,
boolean allInfo,
boolean useSmali,
- boolean useIr) {
+ boolean useIr,
+ boolean noCode) {
super(inputApp);
this.outputPath = outputPath;
this.proguardMap = proguardMap;
this.allInfo = allInfo;
this.useSmali = useSmali;
this.useIr = useIr;
+ this.noCode = noCode;
}
private DisassembleCommand(boolean printHelp, boolean printVersion) {
@@ -177,6 +190,7 @@
allInfo = false;
useSmali = false;
useIr = false;
+ noCode = false;
}
public Path getOutputPath() {
@@ -191,6 +205,10 @@
return useIr;
}
+ public boolean noCode() {
+ return noCode;
+ }
+
@Override
InternalOptions getInternalOptions() {
InternalOptions internal = new InternalOptions();
@@ -226,7 +244,8 @@
DexByteCodeWriter writer =
command.useSmali()
? new SmaliWriter(application, options)
- : new AssemblyWriter(application, options, command.allInfo, command.useIr());
+ : new AssemblyWriter(
+ application, options, command.allInfo, command.useIr(), !command.noCode());
if (command.getOutputPath() != null) {
writer.write(command.getOutputPath());
} else {
diff --git a/src/main/java/com/android/tools/r8/FeatureSplit.java b/src/main/java/com/android/tools/r8/FeatureSplit.java
index 5947786..9bb637e 100644
--- a/src/main/java/com/android/tools/r8/FeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/FeatureSplit.java
@@ -29,6 +29,9 @@
*/
@Keep
public final class FeatureSplit {
+
+ public static final FeatureSplit BASE = new FeatureSplit(null, null);
+
private final ProgramConsumer programConsumer;
private final List<ProgramResourceProvider> programResourceProviders;
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 86abac6..9d93b40 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -183,6 +183,14 @@
return null;
}
+ public boolean isInvokeVirtualRange() {
+ return false;
+ }
+
+ public InvokeVirtualRange asInvokeVirtualRange() {
+ return null;
+ }
+
public boolean isSimpleNop() {
return !isPayload() && this instanceof Nop;
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
index 64e9b6d..cd78458 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -39,6 +39,16 @@
}
@Override
+ public boolean isInvokeVirtualRange() {
+ return true;
+ }
+
+ @Override
+ public InvokeVirtualRange asInvokeVirtualRange() {
+ return this;
+ }
+
+ @Override
public void registerUse(UseRegistry registry) {
registry.registerInvokeVirtual(getMethod());
}
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 9eb7caa..f4e85a8 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
@@ -36,6 +37,8 @@
abstract void recordClassAllAccesses(DexType type);
+ abstract void recordHierarchyOf(DexProgramClass clazz);
+
abstract boolean isNop();
abstract void generateKeepRules(InternalOptions options);
@@ -116,6 +119,14 @@
}
}
+ @Override
+ void recordHierarchyOf(DexProgramClass clazz) {
+ recordClassAllAccesses(clazz.superType);
+ for (DexType itf : clazz.interfaces.values) {
+ recordClassAllAccesses(itf);
+ }
+ }
+
private void keepClass(DexType type) {
DexType baseType = type.lookupBaseType(options.itemFactory);
toKeep.putIfAbsent(baseType, new KeepStruct());
@@ -193,6 +204,9 @@
void recordClassAllAccesses(DexType type) {}
@Override
+ void recordHierarchyOf(DexProgramClass clazz) {}
+
+ @Override
boolean isNop() {
return true;
}
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 0047bf8..8d1eac7 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -467,6 +467,8 @@
}
private void writeClassDefItem(DexProgramClass clazz) {
+ desugaredLibraryCodeToKeep.recordHierarchyOf(clazz);
+
dest.putInt(mapping.getOffsetFor(clazz.type));
dest.putInt(clazz.accessFlags.getAsDexAccessFlags());
dest.putInt(
@@ -659,10 +661,6 @@
private void writeClassData(DexProgramClass clazz) {
assert clazz.hasMethodsOrFields();
- desugaredLibraryCodeToKeep.recordClassAllAccesses(clazz.superType);
- for (DexType itf : clazz.interfaces.values) {
- desugaredLibraryCodeToKeep.recordClassAllAccesses(itf);
- }
mixedSectionOffsets.setOffsetFor(clazz, dest.position());
dest.putUleb128(clazz.staticFields().size());
dest.putUleb128(clazz.instanceFields().size());
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index 5c1c7ad..0dbaa30 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -8,6 +8,8 @@
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
@@ -21,7 +23,7 @@
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -36,9 +38,9 @@
private final AppView<?> appView;
// Mapping from service types to service implementation types.
- private final Map<DexType, List<DexType>> services;
+ private final Map<DexType, Map<FeatureSplit, List<DexType>>> services;
- private AppServices(AppView<?> appView, Map<DexType, List<DexType>> services) {
+ private AppServices(AppView<?> appView, Map<DexType, Map<FeatureSplit, List<DexType>>> services) {
this.appView = appView;
this.services = services;
}
@@ -54,66 +56,124 @@
public List<DexType> serviceImplementationsFor(DexType serviceType) {
assert verifyRewrittenWithLens();
- assert services.containsKey(serviceType);
- List<DexType> serviceImplementationTypes = services.get(serviceType);
- if (serviceImplementationTypes == null) {
+ Map<FeatureSplit, List<DexType>> featureSplitListMap = services.get(serviceType);
+ if (featureSplitListMap == null) {
assert false
: "Unexpected attempt to get service implementations for non-service type `"
+ serviceType.toSourceString()
+ "`";
return ImmutableList.of();
}
- return serviceImplementationTypes;
+ ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+ for (List<DexType> implementations : featureSplitListMap.values()) {
+ builder.addAll(implementations);
+ }
+ return builder.build();
+ }
+
+ public boolean hasServiceImplementationsInFeature(DexType serviceType) {
+ if (appView.options().featureSplitConfiguration == null) {
+ return false;
+ }
+ Map<FeatureSplit, List<DexType>> featureImplementations = services.get(serviceType);
+ if (featureImplementations == null || featureImplementations.isEmpty()) {
+ assert false
+ : "Unexpected attempt to get service implementations for non-service type `"
+ + serviceType.toSourceString()
+ + "`";
+ return true;
+ }
+ if (featureImplementations.size() > 1
+ || !featureImplementations.containsKey(FeatureSplit.BASE)) {
+ return true;
+ }
+ // Check if service is defined feature
+ DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
+ if (appView.options().featureSplitConfiguration.isInFeature(serviceClass)) {
+ return true;
+ }
+ for (DexType dexType : featureImplementations.get(FeatureSplit.BASE)) {
+ DexProgramClass implementationClass = appView.definitionForProgramType(dexType);
+ if (appView.options().featureSplitConfiguration.isInFeature(implementationClass)) {
+ return true;
+ }
+ }
+ return false;
}
public AppServices rewrittenWithLens(GraphLense graphLens) {
- ImmutableMap.Builder<DexType, List<DexType>> rewrittenServices = ImmutableMap.builder();
- for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
+ ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenFeatureMappings =
+ ImmutableMap.builder();
+ for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
DexType rewrittenServiceType = graphLens.lookupType(entry.getKey());
- ImmutableList.Builder<DexType> rewrittenServiceImplementationTypes = ImmutableList.builder();
- for (DexType serviceImplementationType : entry.getValue()) {
- rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+ ImmutableMap.Builder<FeatureSplit, List<DexType>> rewrittenFeatureImplementations =
+ ImmutableMap.builder();
+ for (Entry<FeatureSplit, List<DexType>> featureSplitImpls : entry.getValue().entrySet()) {
+ ImmutableList.Builder<DexType> rewrittenServiceImplementationTypes =
+ ImmutableList.builder();
+ for (DexType serviceImplementationType : featureSplitImpls.getValue()) {
+ rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+ }
+ rewrittenFeatureImplementations.put(
+ featureSplitImpls.getKey(), rewrittenServiceImplementationTypes.build());
}
- rewrittenServices.put(rewrittenServiceType, rewrittenServiceImplementationTypes.build());
+ rewrittenFeatureMappings.put(rewrittenServiceType, rewrittenFeatureImplementations.build());
}
- return new AppServices(appView, rewrittenServices.build());
+ return new AppServices(appView, rewrittenFeatureMappings.build());
}
public AppServices prunedCopy(Collection<DexType> removedClasses) {
- ImmutableMap.Builder<DexType, List<DexType>> rewrittenServicesBuilder = ImmutableMap.builder();
- for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
- if (!removedClasses.contains(entry.getKey())) {
- DexType serviceType = entry.getKey();
+ ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenServicesBuilder =
+ ImmutableMap.builder();
+ for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
+ if (removedClasses.contains(entry.getKey())) {
+ continue;
+ }
+ ImmutableMap.Builder<FeatureSplit, List<DexType>> prunedFeatureSplitImpls =
+ ImmutableMap.builder();
+ for (Entry<FeatureSplit, List<DexType>> featureSplitEntry : entry.getValue().entrySet()) {
ImmutableList.Builder<DexType> rewrittenServiceImplementationTypesBuilder =
ImmutableList.builder();
- for (DexType serviceImplementationType : entry.getValue()) {
+ for (DexType serviceImplementationType : featureSplitEntry.getValue()) {
if (!removedClasses.contains(serviceImplementationType)) {
rewrittenServiceImplementationTypesBuilder.add(serviceImplementationType);
}
}
- List<DexType> rewrittenServiceImplementationTypes =
+ List<DexType> prunedFeatureSplitImplementations =
rewrittenServiceImplementationTypesBuilder.build();
- if (rewrittenServiceImplementationTypes.size() > 0) {
- rewrittenServicesBuilder.put(
- serviceType, rewrittenServiceImplementationTypesBuilder.build());
+ if (prunedFeatureSplitImplementations.size() > 0) {
+ prunedFeatureSplitImpls.put(
+ featureSplitEntry.getKey(), rewrittenServiceImplementationTypesBuilder.build());
}
}
+ ImmutableMap<FeatureSplit, List<DexType>> prunedServiceImplementations =
+ prunedFeatureSplitImpls.build();
+ if (prunedServiceImplementations.size() > 0) {
+ rewrittenServicesBuilder.put(entry.getKey(), prunedServiceImplementations);
+ }
}
return new AppServices(appView, rewrittenServicesBuilder.build());
}
private boolean verifyRewrittenWithLens() {
- for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
+ for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
assert entry.getKey() == appView.graphLense().lookupType(entry.getKey());
- for (DexType type : entry.getValue()) {
- assert type == appView.graphLense().lookupType(type);
+ for (Entry<FeatureSplit, List<DexType>> featureEntry : entry.getValue().entrySet()) {
+ for (DexType type : featureEntry.getValue()) {
+ assert type == appView.graphLense().lookupType(type);
+ }
}
}
return true;
}
public void visit(BiConsumer<DexType, List<DexType>> consumer) {
- services.forEach(consumer);
+ services.forEach(
+ (type, featureImpls) -> {
+ ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+ featureImpls.values().forEach(builder::addAll);
+ consumer.accept(type, builder.build());
+ });
}
public static Builder builder(AppView<?> appView) {
@@ -123,7 +183,7 @@
public static class Builder {
private final AppView<?> appView;
- private final Map<DexType, List<DexType>> services = new IdentityHashMap<>();
+ private final Map<DexType, Map<FeatureSplit, List<DexType>>> services = new LinkedHashMap<>();
private Builder(AppView<?> appView) {
this.appView = appView;
@@ -131,14 +191,27 @@
public AppServices build() {
for (DataResourceProvider provider : appView.appInfo().app().dataResourceProviders) {
- readServices(provider);
+ readServices(provider, FeatureSplit.BASE);
+ }
+ if (appView.options().featureSplitConfiguration != null) {
+ List<FeatureSplit> featureSplits =
+ appView.options().featureSplitConfiguration.getFeatureSplits();
+ for (FeatureSplit featureSplit : featureSplits) {
+ for (ProgramResourceProvider provider : featureSplit.getProgramResourceProviders()) {
+ DataResourceProvider dataResourceProvider = provider.getDataResourceProvider();
+ if (dataResourceProvider != null) {
+ readServices(dataResourceProvider, featureSplit);
+ }
+ }
+ }
}
return new AppServices(appView, services);
}
- private void readServices(DataResourceProvider dataResourceProvider) {
+ private void readServices(
+ DataResourceProvider dataResourceProvider, FeatureSplit featureSplit) {
try {
- dataResourceProvider.accept(new DataResourceProviderVisitor());
+ dataResourceProvider.accept(new DataResourceProviderVisitor(featureSplit));
} catch (ResourceException e) {
throw new CompilationError(e.getMessage(), e);
}
@@ -146,6 +219,12 @@
private class DataResourceProviderVisitor implements Visitor {
+ private final FeatureSplit featureSplit;
+
+ public DataResourceProviderVisitor(FeatureSplit featureSplit) {
+ this.featureSplit = featureSplit;
+ }
+
@Override
public void visit(DataDirectoryResource directory) {
// Ignore.
@@ -162,8 +241,10 @@
DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
String contents = new String(bytes, Charset.defaultCharset());
+ Map<FeatureSplit, List<DexType>> featureSplitImplementations =
+ services.computeIfAbsent(serviceType, k -> new LinkedHashMap<>());
List<DexType> serviceImplementations =
- services.computeIfAbsent(serviceType, (key) -> new ArrayList<>());
+ featureSplitImplementations.computeIfAbsent(featureSplit, f -> new ArrayList<>());
readServiceImplementationsForService(
contents, file.getOrigin(), serviceImplementations);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 3cd5fc5..d10c69f 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -30,17 +30,23 @@
private final boolean writeFields;
private final boolean writeAnnotations;
private final boolean writeIR;
+ private final boolean writeCode;
private final AppInfoWithClassHierarchy appInfo;
private final Kotlin kotlin;
private final Timing timing = new Timing("AssemblyWriter");
public AssemblyWriter(
- DexApplication application, InternalOptions options, boolean allInfo, boolean writeIR) {
+ DexApplication application,
+ InternalOptions options,
+ boolean allInfo,
+ boolean writeIR,
+ boolean writeCode) {
super(application, options);
this.writeAllClassInfo = allInfo;
this.writeFields = allInfo;
this.writeAnnotations = allInfo;
this.writeIR = writeIR;
+ this.writeCode = writeCode;
if (writeIR) {
this.appInfo = new AppInfoWithClassHierarchy(application.toDirect());
if (options.programConsumer == null) {
@@ -128,6 +134,9 @@
ps.println("# " + definition.accessFlags);
ps.println("#");
ps.println();
+ if (!writeCode) {
+ return;
+ }
Code code = definition.getCode();
if (code != null) {
if (writeIR) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 1825fb3..ae39752 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -542,8 +542,10 @@
return false;
}
- public synchronized DexEncodedMethod getClassInitializer() {
- return methodCollection.getClassInitializer();
+ public DexEncodedMethod getClassInitializer() {
+ DexEncodedMethod classInitializer = methodCollection.getClassInitializer();
+ assert classInitializer != DexEncodedMethod.SENTINEL;
+ return classInitializer;
}
public Origin getOrigin() {
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 2269517..4c0c7c5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -120,7 +120,11 @@
public static final DexEncodedMethod[] EMPTY_ARRAY = {};
public static final DexEncodedMethod SENTINEL =
new DexEncodedMethod(
- null, null, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null);
+ null,
+ MethodAccessFlags.fromDexAccessFlags(0),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ null);
public static final Int2ReferenceMap<DebugLocalInfo> NO_PARAMETER_INFO =
new Int2ReferenceArrayMap<>(0);
@@ -247,6 +251,7 @@
this.code = code;
this.classFileVersion = classFileVersion;
this.d8R8Synthesized = d8R8Synthesized;
+ assert accessFlags != null;
assert code == null || !shouldNotHaveCode();
assert parameterAnnotationsList != null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 423acef..0a5014a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -230,8 +230,8 @@
}
@Override
- public void collectIndexedItems(IndexedItemCollection indexedItems,
- DexMethod method, int instructionOffset) {
+ public void collectIndexedItems(
+ IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
if (indexedItems.addClass(this)) {
type.collectIndexedItems(indexedItems, method, instructionOffset);
if (superType != null) {
@@ -260,8 +260,8 @@
}
}
- private static <T extends DexItem> void synchronizedCollectAll(IndexedItemCollection collection,
- T[] items) {
+ private static <T extends DexItem> void synchronizedCollectAll(
+ IndexedItemCollection collection, T[] items) {
synchronized (items) {
collectAll(collection, items);
}
@@ -447,6 +447,50 @@
methodCollection.addDirectMethod(directMethod);
}
+ public void addExtraInterfaces(List<DexType> extraInterfaces, DexItemFactory factory) {
+ if (extraInterfaces.isEmpty()) {
+ return;
+ }
+ addExtraInterfacesToInterfacesArray(extraInterfaces);
+ addExtraInterfacesToSignatureAnnotationIfPresent(extraInterfaces, factory);
+ }
+
+ private void addExtraInterfacesToInterfacesArray(List<DexType> extraInterfaces) {
+ DexType[] newInterfaces =
+ Arrays.copyOf(interfaces.values, interfaces.size() + extraInterfaces.size());
+ for (int i = interfaces.size(); i < newInterfaces.length; i++) {
+ newInterfaces[i] = extraInterfaces.get(i - interfaces.size());
+ }
+ interfaces = new DexTypeList(newInterfaces);
+ }
+
+ private void addExtraInterfacesToSignatureAnnotationIfPresent(
+ List<DexType> extraInterfaces, DexItemFactory factory) {
+ // We need to introduce in the dalvik.annotation.Signature annotation the extra interfaces.
+ // At this point we cheat and pretend the extraInterfaces simply don't use any generic types.
+ DexAnnotation[] annotations = annotations().annotations;
+ for (int i = 0; i < annotations.length; i++) {
+ DexAnnotation annotation = annotations[i];
+ if (DexAnnotation.isSignatureAnnotation(annotation, factory)) {
+ DexAnnotation[] rewrittenAnnotations = annotations.clone();
+ rewrittenAnnotations[i] = rewriteSignatureAnnotation(annotation, extraInterfaces, factory);
+ setAnnotations(new DexAnnotationSet(rewrittenAnnotations));
+ // There is at most one signature annotation, so we can return here.
+ return;
+ }
+ }
+ }
+
+ private DexAnnotation rewriteSignatureAnnotation(
+ DexAnnotation annotation, List<DexType> extraInterfaces, DexItemFactory factory) {
+ String signature = DexAnnotation.getSignature(annotation);
+ StringBuilder newSignatureBuilder = new StringBuilder(signature);
+ for (DexType extraInterface : extraInterfaces) {
+ newSignatureBuilder.append(extraInterface.descriptor.toString());
+ }
+ return DexAnnotation.createSignatureAnnotation(newSignatureBuilder.toString(), factory);
+ }
+
@Override
public DexProgramClass get() {
return this;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index c93d366..b1a8f8e 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -178,7 +178,7 @@
cachedClassInitializer = DexEncodedMethod.SENTINEL;
}
- public DexEncodedMethod getClassInitializer() {
+ public synchronized DexEncodedMethod getClassInitializer() {
if (cachedClassInitializer == DexEncodedMethod.SENTINEL) {
cachedClassInitializer = null;
for (DexEncodedMethod directMethod : directMethods()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 14dee6f..7bdcc84 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -109,12 +109,14 @@
@Override
public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
AbstractValueFactory factory = appView.abstractValueFactory();
- EnumValueInfoMap unboxedEnumInfo = appView.unboxedEnums().getEnumValueInfoMap(field.type);
- if (unboxedEnumInfo != null) {
- // Return the ordinal of the unboxed enum.
- assert unboxedEnumInfo.hasEnumValueInfo(field);
- return factory.createSingleNumberValue(
- unboxedEnumInfo.getEnumValueInfo(field).convertToInt());
+ if (field.holder == field.type) {
+ EnumValueInfoMap unboxedEnumInfo = appView.unboxedEnums().getEnumValueInfoMap(field.type);
+ if (unboxedEnumInfo != null) {
+ // Return the ordinal of the unboxed enum.
+ assert unboxedEnumInfo.hasEnumValueInfo(field);
+ return factory.createSingleNumberValue(
+ unboxedEnumInfo.getEnumValueInfo(field).convertToInt());
+ }
}
return factory.createSingleFieldValue(
lens.lookupField(field), getState().rewrittenWithLens(appView, lens));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 3971c88..7dc7daa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -320,8 +320,10 @@
return;
}
- // If target is a non-interface library class it may be an emulated interface.
- if (!libraryHolder.isInterface()) {
+ // If target is a non-interface library class it may be an emulated interface,
+ // except on a rewritten type, where L8 has already dealt with the desugaring.
+ if (!libraryHolder.isInterface()
+ && !appView.rewritePrefix.hasRewrittenType(libraryHolder.type, appView)) {
// Here we use step-3 of resolution to find a maximally specific default interface method.
DexClassAndMethod result = appInfo.lookupMaximallySpecificMethod(libraryHolder, method);
if (result != null && rewriter.isEmulatedInterface(result.getHolder().type)) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index be1bbfd..5ce5bb8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ResolutionResult;
@@ -33,7 +32,6 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
@@ -325,10 +323,8 @@
// We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
// applies up to 24.
for (DexEncodedMethod method : methods) {
- DexType[] newInterfaces =
- Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
- newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(method);
- clazz.interfaces = new DexTypeList(newInterfaces);
+ clazz.addExtraInterfaces(
+ Collections.singletonList(dispatchInterfaceTypeFor(method)), appView.dexItemFactory());
if (clazz.lookupVirtualMethod(method.getReference()) == null) {
DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
clazz.addVirtualMethod(newMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 27a5224..f537a6f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -920,7 +920,7 @@
private void duplicateEmulatedInterfaces() {
// All classes implementing an emulated interface now implements the interface and the
// emulated one, as well as hidden overrides, for correct emulated dispatch.
- for (DexClass clazz : appView.appInfo().classes()) {
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
if (clazz.type == appView.dexItemFactory().objectType) {
continue;
}
@@ -940,17 +940,11 @@
}
}
// Remove duplicates.
- extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
- }
- if (!extraInterfaces.isEmpty()) {
- DexType[] newInterfaces =
- Arrays.copyOf(
- clazz.interfaces.values, clazz.interfaces.size() + extraInterfaces.size());
- for (int i = clazz.interfaces.size(); i < newInterfaces.length; i++) {
- newInterfaces[i] = extraInterfaces.get(i - clazz.interfaces.size());
+ if (extraInterfaces.size() > 1) {
+ extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
}
- clazz.interfaces = new DexTypeList(newInterfaces);
}
+ clazz.addExtraInterfaces(extraInterfaces, appView.dexItemFactory());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 8f4650a..4cb2fab 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1432,7 +1432,7 @@
// c = ... // Even though we know c is of type A,
// a' = (B) c; // (this could be removed, since chained below.)
// a'' = (A) a'; // this should remain for runtime verification.
- assert !inTypeLattice.isDefinitelyNull();
+ assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType());
assert outTypeLattice.equalUpToNullability(castTypeLattice);
return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 263cadd..71a2a18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -132,6 +132,11 @@
continue;
}
+ // Check that we are not service loading anything from a feature into base.
+ if (appView.appServices().hasServiceImplementationsInFeature(constClass.getValue())) {
+ continue;
+ }
+
// Check that ClassLoader used is the ClassLoader defined for the the service configuration
// that we are instantiating or NULL.
InvokeVirtual classLoaderInvoke =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index f80e1d7..3feac87 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -12,11 +12,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
@@ -26,7 +23,6 @@
import java.util.Map;
import java.util.Set;
-// TODO(b/160634549): Rename or refactor this to reflect its non-cost related analysis.
/** Analysis that estimates the cost of class inlining an object allocation. */
class ClassInlinerCostAnalysis {
@@ -77,28 +73,6 @@
continue;
}
IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
-
- // If the instance is part of a phi, then inlining will invalidate the inliner assumptions.
- // TODO(b/160634549): This is not a budget miss but a hard requirement.
- InstructionIterator iterator = inliningIR.entryBlock().iterator();
- while (iterator.hasNext()) {
- Instruction next = iterator.next();
- if (!next.isArgument()) {
- break;
- }
- Value argumentValue = next.outValue();
- TypeElement argumentType = argumentValue.getType();
- if (argumentType.isClassType()
- && argumentType.asClassType().getClassType() == eligibleClass.type) {
- assert argumentValue.uniqueUsers().stream()
- .noneMatch(
- AssumeAndCheckCastAliasedValueConfiguration.getInstance()::isIntroducingAnAlias);
- if (argumentValue.hasPhiUsers()) {
- return true;
- }
- }
- }
-
int increment =
inlinee.getDefinition().getCode().estimatedSizeForInlining()
- estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
index 8d178a3..35eaaf5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
@@ -22,13 +22,16 @@
final OptionalBool returnsReceiver;
final boolean hasMonitorOnReceiver;
+ final boolean modifiesInstanceFields;
public ClassInlinerEligibilityInfo(
List<Pair<Invoke.Type, DexMethod>> callsReceiver,
OptionalBool returnsReceiver,
- boolean hasMonitorOnReceiver) {
+ boolean hasMonitorOnReceiver,
+ boolean modifiesInstanceFields) {
this.callsReceiver = callsReceiver;
this.returnsReceiver = returnsReceiver;
this.hasMonitorOnReceiver = hasMonitorOnReceiver;
+ this.modifiesInstanceFields = modifiesInstanceFields;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 4e52743..3824962 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.google.common.base.Predicates.alwaysFalse;
+import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
@@ -41,6 +42,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -58,11 +60,11 @@
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -581,11 +583,35 @@
while (!currentUsers.isEmpty()) {
Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
for (Instruction instruction : currentUsers) {
- if (instruction.isAssume() || instruction.isCheckCast()) {
- Value src = ListUtils.first(instruction.inValues());
+ if (aliasesThroughAssumeAndCheckCasts.isIntroducingAnAlias(instruction)) {
+ Value src = aliasesThroughAssumeAndCheckCasts.getAliasForOutValue(instruction);
Value dest = instruction.outValue();
- indirectOutValueUsers.addAll(dest.uniqueUsers());
+ if (dest.hasPhiUsers()) {
+ // It is possible that a trivial phi is constructed upon IR building for the eligible
+ // value. It must actually be trivial so verify that it is indeed trivial and replace
+ // all of the phis involved with the value.
+ WorkList<Phi> worklist = WorkList.newIdentityWorkList(dest.uniquePhiUsers());
+ while (worklist.hasNext()) {
+ Phi phi = worklist.next();
+ for (Value operand : phi.getOperands()) {
+ operand = operand.getAliasedValue(aliasesThroughAssumeAndCheckCasts);
+ if (operand.isPhi()) {
+ worklist.addIfNotSeen(operand.asPhi());
+ } else if (src != operand) {
+ throw new InternalCompilerError(
+ "Unexpected non-trivial phi in method eligible for class inlining");
+ }
+ }
+ }
+ // The only value flowing into any of the phis is src, so replace all phis by src.
+ for (Phi phi : worklist.getSeenSet()) {
+ indirectOutValueUsers.addAll(phi.uniqueUsers());
+ phi.replaceUsers(src);
+ phi.removeDeadPhi();
+ }
+ }
assert !dest.hasPhiUsers();
+ indirectOutValueUsers.addAll(dest.uniqueUsers());
dest.replaceUsers(src);
removeInstruction(instruction);
}
@@ -1039,6 +1065,12 @@
// We will not be able to remove the monitor instruction afterwards.
return false;
}
+ if (eligibility.modifiesInstanceFields) {
+ // The static instance could be accessed from elsewhere. Therefore, we cannot
+ // allow side-effects to be removed and therefore cannot class inline method
+ // calls that modifies the instance.
+ return false;
+ }
}
// If the method returns receiver and the return value is actually
@@ -1189,8 +1221,8 @@
}
if (root.isStaticGet()) {
- // If we are class inlining a singleton instance from a static-get, then we don't the value of
- // the fields.
+ // If we are class inlining a singleton instance from a static-get, then we don't know
+ // the value of the fields.
if (parameterUsage.hasFieldRead) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 1ffca3d..3be53b2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -771,7 +771,7 @@
factory.createString(
enumUnboxerRewriter.compatibleName(method.holder)
+ "$"
- + (encodedMethod.isDirectMethod() ? "d" : "v")
+ + (encodedMethod.isStatic() ? "s" : "v")
+ "$"
+ method.name.toString());
DexProto proto =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 8a25765..d88f9d2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -192,6 +192,7 @@
List<Pair<Invoke.Type, DexMethod>> callsReceiver = new ArrayList<>();
boolean seenSuperInitCall = false;
boolean seenMonitor = false;
+ boolean modifiesInstanceFields = false;
AliasedValueConfiguration configuration =
AssumeAndCheckCastAliasedValueConfiguration.getInstance();
@@ -220,6 +221,7 @@
if (isReceiverAlias.test(instancePutInstruction.value())) {
return;
}
+ modifiesInstanceFields = true;
}
DexField field = insn.asFieldInstruction().getField();
if (appView.appInfo().resolveField(field).isFailedOrUnknownResolution()) {
@@ -293,7 +295,8 @@
new ClassInlinerEligibilityInfo(
callsReceiver,
new ClassInlinerReceiverAnalysis(appView, definition, code).computeReturnsReceiver(),
- seenMonitor || synchronizedVirtualMethod));
+ seenMonitor || synchronizedVirtualMethod,
+ modifiesInstanceFields));
}
private void identifyParameterUsages(
@@ -540,7 +543,7 @@
if (!value.onlyDependsOnArgument()) {
builder.setInstanceFieldInitializationMayDependOnEnvironment();
}
- if (value == receiver) {
+ if (couldBeReceiverValue(value, receiver, aliasesThroughAssumeAndCheckCasts)) {
builder.setReceiverMayEscapeOutsideConstructorChain();
}
}
@@ -569,7 +572,7 @@
for (int i = 1; i < invoke.arguments().size(); i++) {
Value argument =
invoke.arguments().get(i).getAliasedValue(aliasesThroughAssumeAndCheckCasts);
- if (argument == receiver) {
+ if (couldBeReceiverValue(argument, receiver, aliasesThroughAssumeAndCheckCasts)) {
// In the analysis of the parent constructor, we don't consider the non-receiver
// arguments as being aliases of the receiver. Therefore, we explicitly mark
// that the receiver escapes from this constructor.
@@ -587,7 +590,7 @@
.markAllFieldsAsRead()
.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
for (Value inValue : invoke.inValues()) {
- if (inValue.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
+ if (couldBeReceiverValue(inValue, receiver, aliasesThroughAssumeAndCheckCasts)) {
builder.setReceiverMayEscapeOutsideConstructorChain();
break;
}
@@ -603,7 +606,7 @@
builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
}
for (Value argument : invoke.arguments()) {
- if (argument.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
+ if (couldBeReceiverValue(argument, receiver, aliasesThroughAssumeAndCheckCasts)) {
builder.setReceiverMayEscapeOutsideConstructorChain();
break;
}
@@ -620,7 +623,7 @@
.markAllFieldsAsRead()
.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
for (Value argument : invoke.arguments()) {
- if (argument.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
+ if (couldBeReceiverValue(argument, receiver, aliasesThroughAssumeAndCheckCasts)) {
builder.setReceiverMayEscapeOutsideConstructorChain();
break;
}
@@ -669,6 +672,18 @@
return builder.build();
}
+ private static boolean couldBeReceiverValue(
+ Value value, Value receiver, AliasedValueConfiguration aliasing) {
+ if (value.isPhi() && receiver.hasPhiUsers()) {
+ // Conservatively assume that the receiver might be an input dependency of the phi value.
+ return true;
+ }
+ if (value.getAliasedValue(aliasing) == receiver) {
+ return true;
+ }
+ return false;
+ }
+
private void identifyInvokeSemanticsForInlining(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
timing.begin("Identify invoke semantics for inlining");
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 54decce..1cfe21f 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.InvokeVirtualRange;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
@@ -304,49 +305,66 @@
private CfCode createCfCodeForVirtualBridge(CfCode code, DexMethod methodToInvoke) {
List<CfInstruction> newInstructions = new ArrayList<>();
+ boolean modified = false;
for (CfInstruction instruction : code.getInstructions()) {
- if (instruction.isInvoke()) {
+ if (instruction.isInvoke() && instruction.asInvoke().getMethod() != methodToInvoke) {
CfInvoke invoke = instruction.asInvoke();
assert invoke.isInvokeVirtual();
assert !invoke.isInterface();
assert invoke.getMethod().match(methodToInvoke);
newInstructions.add(new CfInvoke(invoke.getOpcode(), methodToInvoke, false));
+ modified = true;
} else {
newInstructions.add(instruction);
}
}
- return new CfCode(
- methodToInvoke.holder,
- code.getMaxStack(),
- code.getMaxLocals(),
- newInstructions,
- code.getTryCatchRanges(),
- code.getLocalVariables());
+ return modified
+ ? new CfCode(
+ methodToInvoke.holder,
+ code.getMaxStack(),
+ code.getMaxLocals(),
+ newInstructions,
+ code.getTryCatchRanges(),
+ code.getLocalVariables())
+ : code;
}
private DexCode createDexCodeForVirtualBridge(DexCode code, DexMethod methodToInvoke) {
Instruction[] newInstructions = new Instruction[code.instructions.length];
+ boolean modified = false;
for (int i = 0; i < code.instructions.length; i++) {
Instruction instruction = code.instructions[i];
- if (instruction.isInvokeVirtual()) {
+ if (instruction.isInvokeVirtual()
+ && instruction.asInvokeVirtual().getMethod() != methodToInvoke) {
InvokeVirtual invoke = instruction.asInvokeVirtual();
InvokeVirtual newInvoke =
new InvokeVirtual(
invoke.A, methodToInvoke, invoke.C, invoke.D, invoke.E, invoke.F, invoke.G);
newInvoke.setOffset(invoke.getOffset());
newInstructions[i] = newInvoke;
+ modified = true;
+ } else if (instruction.isInvokeVirtualRange()
+ && instruction.asInvokeVirtualRange().getMethod() != methodToInvoke) {
+ InvokeVirtualRange invoke = instruction.asInvokeVirtualRange();
+ InvokeVirtualRange newInvoke =
+ new InvokeVirtualRange(invoke.CCCC, invoke.AA, methodToInvoke);
+ newInvoke.setOffset(invoke.getOffset());
+ modified = true;
+ newInstructions[i] = newInvoke;
} else {
newInstructions[i] = instruction;
}
}
- return new DexCode(
- code.registerSize,
- code.incomingRegisterSize,
- code.outgoingRegisterSize,
- newInstructions,
- code.tries,
- code.handlers,
- code.getDebugInfo());
+ return modified
+ ? new DexCode(
+ code.registerSize,
+ code.incomingRegisterSize,
+ code.outgoingRegisterSize,
+ newInstructions,
+ code.tries,
+ code.handlers,
+ code.getDebugInfo())
+ : code;
}
static class BridgeHoistingLens extends NestedGraphLense {
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index f2c5442..444eee3 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -60,7 +60,11 @@
private static Reference getInstance() {
if (instance == null) {
- instance = new Reference();
+ synchronized (Reference.class) {
+ if (instance == null) {
+ instance = new Reference();
+ }
+ }
}
return instance;
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 9dd4865..f0340a3 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -646,7 +646,7 @@
ZipOutputStream dexArchiveOutputStream,
ProgramResource programResource)
throws ResourceException, IOException {
- byte[] bytes = ByteStreams.toByteArray(programResource.getByteStream());
+ byte[] bytes = StreamUtils.StreamToByteArrayClose(programResource.getByteStream());
if (programResource.getKind() == Kind.CF) {
Set<String> classDescriptors = programResource.getClassDescriptors();
String classDescriptor =
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 f7353d9..6cc73f4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -225,7 +225,8 @@
public boolean enableInlining =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableinlining") == null;
- public boolean enableEnumUnboxing = true;
+ // TODO(b/160854837): re-enable enum unboxing.
+ public boolean enableEnumUnboxing = false;
// TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
public boolean applyInliningToInlinee =
System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index d8bd420..092a4fc 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -6,6 +6,7 @@
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
+import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
@@ -78,6 +79,10 @@
return workingList.removeFirst();
}
+ public Set<T> getSeenSet() {
+ return Collections.unmodifiableSet(seen);
+ }
+
public enum EqualityTest {
HASH,
IDENTITY
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9ad7a70..31150c4 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -2129,7 +2129,7 @@
throws IOException, ExecutionException {
DexApplication application =
new ApplicationReader(app, new InternalOptions(), Timing.empty()).read().toDirect();
- new AssemblyWriter(application, new InternalOptions(), true, false).write(ps);
+ new AssemblyWriter(application, new InternalOptions(), true, false, true).write(ps);
}
public static Path getTestFolderForClass(Class<?> clazz) {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
index 16db7d8..410d418 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -13,7 +13,9 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.AWithRangedInvoke;
import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.User;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.UserWithRangedInvoke;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
@@ -45,6 +47,28 @@
.transform(),
transformer(C.class)
.setBridge(C.class.getDeclaredMethod("bridgeC", Object.class))
+ .transform(),
+ transformer(BWithRangedInvoke.class)
+ .setBridge(
+ BWithRangedInvoke.class.getDeclaredMethod(
+ "bridgeB",
+ Object.class,
+ int.class,
+ int.class,
+ int.class,
+ int.class,
+ int.class))
+ .transform(),
+ transformer(CWithRangedInvoke.class)
+ .setBridge(
+ CWithRangedInvoke.class.getDeclaredMethod(
+ "bridgeC",
+ Object.class,
+ int.class,
+ int.class,
+ int.class,
+ int.class,
+ int.class))
.transform())
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
@@ -54,7 +78,7 @@
.compile()
.inspect(this::inspect)
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello world!");
+ .assertSuccessWithOutputLines("Hello world!", "Hello 12345 world! 12345");
}
private void inspect(CodeInspector inspector) {
@@ -69,6 +93,11 @@
ClassSubject cClassSubject = inspector.clazz(C.class);
assertThat(cClassSubject, isPresent());
+
+ ClassSubject axClassSubject = inspector.clazz(AWithRangedInvoke.class);
+ assertThat(axClassSubject, isPresent());
+ assertThat(axClassSubject.uniqueMethodWithName("m"), isPresent());
+ assertThat(axClassSubject.uniqueMethodWithName("bridgeC"), isPresent());
}
static class TestClass {
@@ -77,6 +106,10 @@
C instance = new C();
System.out.print(instance.bridgeB("Hello"));
System.out.println(User.invokeBridgeC(instance));
+
+ CWithRangedInvoke instanceWithRangedInvoke = new CWithRangedInvoke();
+ System.out.print(instanceWithRangedInvoke.bridgeB("Hello ", 1, 2, 3, 4, 5));
+ System.out.println(UserWithRangedInvoke.invokeBridgeC(instanceWithRangedInvoke));
}
}
@@ -102,4 +135,27 @@
return (String) m((String) o);
}
}
+
+ @NeverMerge
+ static class BWithRangedInvoke extends AWithRangedInvoke {
+
+ // This bridge cannot be hoisted to A, since it would then become inaccessible to the call site
+ // in TestClass.main().
+ @NeverInline
+ /*bridge*/ String bridgeB(Object o, int a, int b, int c, int d, int e) {
+ return (String) m((String) o, a, b, c, d, e);
+ }
+ }
+
+ @NeverClassInline
+ public static class CWithRangedInvoke extends BWithRangedInvoke {
+
+ // This bridge is invoked from another package. However, this does not prevent us from hoisting
+ // the bridge to B, although B is not public, since users from outside this package can still
+ // access bridgeC() via class C. From B, the bridge can be hoisted again to A.
+ @NeverInline
+ public /*bridge*/ String bridgeC(Object o, int a, int b, int c, int d, int e) {
+ return (String) m((String) o, a, b, c, d, e);
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
index bc91e00..55daf92 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest;
+import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest.CWithRangedInvoke;
public class BridgeHoistingAccessibilityTestClasses {
@@ -26,4 +27,21 @@
return instance.bridgeC(" world!");
}
}
+
+ @NeverMerge
+ public static class AWithRangedInvoke {
+
+ @NeverInline
+ public Object m(String arg, int a, int b, int c, int d, int e) {
+ return System.currentTimeMillis() > 0 ? arg + a + b + c + d + e : null;
+ }
+ }
+
+ public static class UserWithRangedInvoke {
+
+ @NeverInline
+ public static String invokeBridgeC(CWithRangedInvoke instance) {
+ return instance.bridgeC(" world! ", 1, 2, 3, 4, 5);
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
index f13eddb..ae709f3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -18,6 +19,9 @@
@RunWith(Parameterized.class)
public class ConcurrentHashMapSubclassTest extends DesugaredLibraryTestBase {
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("1.0", "2.0", "10.0", "1.0", "2.0", "10.0", "1.0", "2.0", "10.0");
+
private final TestParameters parameters;
private final boolean shrinkDesugaredLibrary;
@@ -46,7 +50,7 @@
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutputLines("1.0", "10.0", "1.0", "10.0", "1.0", "10.0");
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
@@ -64,7 +68,7 @@
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutputLines("1.0", "10.0", "1.0", "10.0", "1.0", "10.0");
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
@SuppressWarnings("unchecked")
@@ -78,24 +82,33 @@
static void itfType() {
Map map = new NullableConcurrentHashMap<Integer, Double>();
map.put(1, 1.0);
+ map.putIfAbsent(2, 2.0);
+ map.putIfAbsent(2, 3.0);
map.putAll(example());
System.out.println(map.get(1));
+ System.out.println(map.get(2));
System.out.println(map.get(10));
}
static void classType() {
ConcurrentHashMap map = new NullableConcurrentHashMap<Integer, Double>();
map.put(1, 1.0);
+ map.putIfAbsent(2, 2.0);
+ map.putIfAbsent(2, 3.0);
map.putAll(example());
System.out.println(map.get(1));
+ System.out.println(map.get(2));
System.out.println(map.get(10));
}
static void directType() {
NullableConcurrentHashMap map = new NullableConcurrentHashMap<Integer, Double>();
map.put(1, 1.0);
+ map.putIfAbsent(2, 2.0);
+ map.putIfAbsent(2, 3.0);
map.putAll(example());
System.out.println(map.get(1));
+ System.out.println(map.get(2));
System.out.println(map.get(10));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
new file mode 100644
index 0000000..0eab37e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+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 LibraryEmptySubclassInterfaceTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public LibraryEmptySubclassInterfaceTest(
+ boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines(getResult());
+ assertExpectedKeepRules(keepRuleConsumer);
+ }
+
+ private void assertExpectedKeepRules(KeepRuleConsumer keepRuleConsumer) {
+ if (!requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
+ return;
+ }
+ String keepRules = keepRuleConsumer.get();
+ assertThat(keepRules, containsString("-keep class j$.util.Map"));
+ assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentHashMap"));
+ assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentMap"));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(Backend.DEX)
+ .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
+ .addKeepMainRule(Executor.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines(getResult());
+ assertExpectedKeepRules(keepRuleConsumer);
+ }
+
+ private String getResult() {
+ return requiresEmulatedInterfaceCoreLibDesugaring(parameters)
+ ? "class j$.util.concurrent.ConcurrentHashMap"
+ : "class java.util.concurrent.ConcurrentHashMap";
+ }
+
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ static class Executor {
+
+ public static void main(String[] args) {
+ System.out.println(NullableConcurrentHashMap.class.getSuperclass());
+ }
+ }
+
+ static class NullableConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
+ NullableConcurrentHashMap() {
+ super();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
new file mode 100644
index 0000000..6e300ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
@@ -0,0 +1,1012 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import dalvik.system.PathClassLoader;
+import java.sql.SQLDataException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+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 LibrarySubclassInterfaceTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public LibrarySubclassInterfaceTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCustomCollectionD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ String stdOut =
+ testForD8()
+ .addInnerClasses(LibrarySubclassInterfaceTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertValidInterfaces(stdOut);
+ }
+
+ @Test
+ public void testCustomCollectionR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ String stdOut =
+ testForR8(Backend.DEX)
+ .addInnerClasses(LibrarySubclassInterfaceTest.class)
+ .addKeepMainRule(Executor.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertValidInterfaces(stdOut);
+ }
+
+ private void assertValidInterfaces(String stdOut) {
+ // The value of getGenericInterfaces has to be the value of getInterfaces with generic types.
+ // Here are two examples:
+ // - class A implements I {}
+ // getInterfaces -> [interface I]
+ // getGenericInterfaces -> [interface I]
+ // - class B<E> implements J<E> {}
+ // getInterfaces -> [interface J]
+ // getGenericInterfaces -> [J<E>]
+ // Both arrays have to be of the same size and each class has to be present in the same order.
+ String[] lines = stdOut.split("\n");
+ for (int i = 0; i < lines.length; i += 4) {
+ String className = lines[i];
+ String[] interfaces1 = lines[i + 1].split("(, com|, interface|, j)");
+ String[] interfaces2 = lines[i + 2].split("(, com|, interface|, j)");
+ assertEquals(
+ "Invalid number of interfaces in "
+ + className
+ + "\n "
+ + Arrays.toString(interfaces1)
+ + "\n "
+ + Arrays.toString(interfaces2),
+ interfaces1.length,
+ interfaces2.length);
+ // Ignore the empty list of interface case.
+ if (!interfaces1[0].equals("[]")) {
+ for (int j = 0; j < interfaces1.length; j++) {
+ String interfaceName = interfaces1[j].substring("interface ".length()).trim();
+ while (interfaceName.charAt(interfaceName.length() - 1) == ']') {
+ interfaceName = interfaceName.substring(0, interfaceName.length() - 2).trim();
+ }
+ assertTrue(
+ "Invalid interface in " + className + "\n " + interfaces1[j] + "\n " + interfaces2[j],
+ interfaces2[j].contains(interfaceName));
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ static class Executor {
+
+ // The output of the test is, in stdOut, composed of 4 lines entries:
+ // line 1: class name
+ // line 2: getInterfaces() for the class
+ // line 3: getGenericInterfaces() for the class
+ // line 4: empty.
+ public static void main(String[] args) {
+ mapTest();
+ collectionTest();
+ collectionMapTest();
+ sqlDateTest();
+ }
+
+ private static void mapTest() {
+ System.out.println(NullableConcurrentHashMapExtendDifferentLetters.class);
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapExtendDifferentLetters.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(
+ NullableConcurrentHashMapExtendDifferentLetters.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableConcurrentHashMapExtend.class);
+ System.out.println(Arrays.toString(NullableConcurrentHashMapExtend.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapExtend.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableConcurrentHashMapExtendZ.class);
+ System.out.println(Arrays.toString(NullableConcurrentHashMapExtendZ.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapExtendZ.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableConcurrentHashMapImplement.class);
+ System.out.println(Arrays.toString(NullableConcurrentHashMapImplement.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapImplement.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableConcurrentHashMapImplementZ.class);
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapImplementZ.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapImplementZ.class.getGenericInterfaces()));
+ System.out.println();
+ }
+
+ private static void collectionTest() {
+ System.out.println(NullableArrayListExtend.class);
+ System.out.println(Arrays.toString(NullableArrayListExtend.class.getInterfaces()));
+ System.out.println(Arrays.toString(NullableArrayListExtend.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableArrayListExtendZ.class);
+ System.out.println(Arrays.toString(NullableArrayListExtendZ.class.getInterfaces()));
+ System.out.println(Arrays.toString(NullableArrayListExtendZ.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableArrayListImplement.class);
+ System.out.println(Arrays.toString(NullableArrayListImplement.class.getInterfaces()));
+ System.out.println(Arrays.toString(NullableArrayListImplement.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableArrayListImplementZ.class);
+ System.out.println(Arrays.toString(NullableArrayListImplementZ.class.getInterfaces()));
+ System.out.println(Arrays.toString(NullableArrayListImplementZ.class.getGenericInterfaces()));
+ System.out.println();
+ }
+
+ private static void collectionMapTest() {
+ System.out.println(CollectionMapImplements2.class);
+ System.out.println(Arrays.toString(CollectionMapImplements2.class.getInterfaces()));
+ System.out.println(Arrays.toString(CollectionMapImplements2.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapExtendImplement.class);
+ System.out.println(Arrays.toString(CollectionMapExtendImplement.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplement.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapImplements2Integer1.class);
+ System.out.println(Arrays.toString(CollectionMapImplements2Integer1.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapImplements2Integer1.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapExtendImplementInteger1.class);
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplementInteger1.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplementInteger1.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapImplements2Integer2.class);
+ System.out.println(Arrays.toString(CollectionMapImplements2Integer2.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapImplements2Integer2.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapExtendImplementInteger2.class);
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplementInteger2.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplementInteger2.class.getGenericInterfaces()));
+ System.out.println();
+ }
+
+ private static void sqlDateTest() {
+ System.out.println(MySQLDataException.class);
+ System.out.println(Arrays.toString(MySQLDataException.class.getInterfaces()));
+ System.out.println(Arrays.toString(MySQLDataException.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(MyDate.class);
+ System.out.println(Arrays.toString(MyDate.class.getInterfaces()));
+ System.out.println(Arrays.toString(MyDate.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(MyDateZ.class);
+ System.out.println(Arrays.toString(MyDateZ.class.getInterfaces()));
+ System.out.println(Arrays.toString(MyDateZ.class.getGenericInterfaces()));
+ System.out.println();
+ }
+ }
+
+ interface MyInterface<Z> {
+ void print(Z z);
+ }
+
+ static class NullableConcurrentHashMapExtendDifferentLetters<R, T>
+ extends ConcurrentHashMap<R, T> {
+ NullableConcurrentHashMapExtendDifferentLetters() {
+ super();
+ }
+ }
+
+ static class NullableConcurrentHashMapExtend<K, V> extends ConcurrentHashMap<K, V> {
+ NullableConcurrentHashMapExtend() {
+ super();
+ }
+ }
+
+ static class NullableConcurrentHashMapImplement<K, V> implements ConcurrentMap<K, V> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public V get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public V put(K k, V v) {
+ return null;
+ }
+
+ @Override
+ public V remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends K, ? extends V> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<K> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<V> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ return null;
+ }
+
+ @Override
+ public V putIfAbsent(@NotNull K k, V v) {
+ return null;
+ }
+
+ @Override
+ public boolean remove(@NotNull Object o, Object o1) {
+ return false;
+ }
+
+ @Override
+ public boolean replace(@NotNull K k, @NotNull V v, @NotNull V v1) {
+ return false;
+ }
+
+ @Override
+ public V replace(@NotNull K k, @NotNull V v) {
+ return null;
+ }
+ }
+
+ static class NullableConcurrentHashMapExtendZ<K, V> extends ConcurrentHashMap<K, V>
+ implements MyInterface<K> {
+ NullableConcurrentHashMapExtendZ() {
+ super();
+ }
+
+ @Override
+ public void print(K k) {}
+ }
+
+ static class NullableConcurrentHashMapImplementZ<K, V>
+ implements ConcurrentMap<K, V>, MyInterface<K> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public V get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public V put(K k, V v) {
+ return null;
+ }
+
+ @Override
+ public V remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends K, ? extends V> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<K> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<V> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ return null;
+ }
+
+ @Override
+ public V putIfAbsent(@NotNull K k, V v) {
+ return null;
+ }
+
+ @Override
+ public boolean remove(@NotNull Object o, Object o1) {
+ return false;
+ }
+
+ @Override
+ public boolean replace(@NotNull K k, @NotNull V v, @NotNull V v1) {
+ return false;
+ }
+
+ @Override
+ public V replace(@NotNull K k, @NotNull V v) {
+ return null;
+ }
+
+ @Override
+ public void print(K k) {}
+ }
+
+ static class NullableArrayListExtend<E> extends ArrayList<E> {
+ NullableArrayListExtend() {
+ super();
+ }
+ }
+
+ static class NullableArrayListImplement<E> implements List<E> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] ts) {
+ return null;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(int i, @NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public E get(int i) {
+ return null;
+ }
+
+ @Override
+ public E set(int i, E e) {
+ return null;
+ }
+
+ @Override
+ public void add(int i, E e) {}
+
+ @Override
+ public E remove(int i) {
+ return null;
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return 0;
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return 0;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator(int i) {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public List<E> subList(int i, int i1) {
+ return null;
+ }
+ }
+
+ static class NullableArrayListExtendZ<E> extends ArrayList<E> implements MyInterface<E> {
+ NullableArrayListExtendZ() {
+ super();
+ }
+
+ @Override
+ public void print(E e) {}
+ }
+
+ static class NullableArrayListImplementZ<E> implements List<E>, MyInterface<E> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] ts) {
+ return null;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(int i, @NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public E get(int i) {
+ return null;
+ }
+
+ @Override
+ public E set(int i, E e) {
+ return null;
+ }
+
+ @Override
+ public void add(int i, E e) {}
+
+ @Override
+ public E remove(int i) {
+ return null;
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return 0;
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return 0;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator(int i) {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public List<E> subList(int i, int i1) {
+ return null;
+ }
+
+ @Override
+ public void print(E e) {}
+ }
+
+ static class CollectionMapImplements2<R, C> implements Iterable<R>, Map<R, C> {
+ @NotNull
+ @Override
+ public Iterator<R> iterator() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public C get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public C put(R r, C c) {
+ return null;
+ }
+
+ @Override
+ public C remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends R, ? extends C> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<R> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<C> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<R, C>> entrySet() {
+ return null;
+ }
+ }
+
+ static class CollectionMapExtendImplement<R, C> extends HashMap<R, C> implements Iterable<R> {
+ @NotNull
+ @Override
+ public Iterator<R> iterator() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public C get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public C put(R r, C c) {
+ return null;
+ }
+
+ @Override
+ public C remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends R, ? extends C> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<R> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<C> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<R, C>> entrySet() {
+ return null;
+ }
+ }
+
+ static class CollectionMapImplements2Integer1<C>
+ implements Iterable<PathClassLoader>, Map<PathClassLoader, C> {
+ @NotNull
+ @Override
+ public Iterator<PathClassLoader> iterator() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public C get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public C put(PathClassLoader unsafe, C c) {
+ return null;
+ }
+
+ @Override
+ public C remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends PathClassLoader, ? extends C> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<PathClassLoader> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<C> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<PathClassLoader, C>> entrySet() {
+ return null;
+ }
+ }
+
+ static class CollectionMapExtendImplementInteger1<C> extends HashMap<PathClassLoader, C>
+ implements Iterable<PathClassLoader> {
+ @NotNull
+ @Override
+ public Iterator<PathClassLoader> iterator() {
+ return null;
+ }
+ }
+
+ static class CollectionMapImplements2Integer2<R> implements Iterable<R>, Map<R, PathClassLoader> {
+ @NotNull
+ @Override
+ public Iterator<R> iterator() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public PathClassLoader get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public PathClassLoader put(R r, PathClassLoader unsafe) {
+ return null;
+ }
+
+ @Override
+ public PathClassLoader remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends R, ? extends PathClassLoader> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<R> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<PathClassLoader> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<R, PathClassLoader>> entrySet() {
+ return null;
+ }
+ }
+
+ static class CollectionMapExtendImplementInteger2<R> extends HashMap<R, PathClassLoader>
+ implements Iterable<R> {
+ @NotNull
+ @Override
+ public Iterator<R> iterator() {
+ return null;
+ }
+ }
+
+ // SQLDataException implements Iterable<Throwable>.
+ static class MySQLDataException extends SQLDataException {}
+
+ // java.util.Date for the extra dispatch case.
+ static class MyDate extends Date {}
+
+ static class MyDateZ<Z> extends Date implements MyInterface<Z> {
+ @Override
+ public void print(Z z) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 8f6a2d6..3e3a02c 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -38,7 +38,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection params() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
}
private final TestParameters parameters;
@@ -91,6 +91,7 @@
@NeverMerge
public abstract static class BaseSuperClass implements RunInterface {
+ @Override
public void run() {
System.out.println(getFromFeature());
}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index fa3ae88..e6d0ee9 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -37,7 +37,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection params() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 067f920..5a0fd21 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -38,7 +38,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection params() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
@@ -99,6 +99,7 @@
@NeverMerge
public static class BaseClass implements RunInterface {
+ @Override
@NeverInline
public void run() {
System.out.println(BaseWithStatic.getBase42());
@@ -119,6 +120,7 @@
public static class FeatureClass extends BaseClass {
+ @Override
public void run() {
super.run();
System.out.println(AFeatureWithStatic.getFoobar());
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
new file mode 100644
index 0000000..c290f26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
@@ -0,0 +1,275 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.rewrite.ServiceLoaderRewritingTest.getServiceLoaderLoads;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.R8TestRunResult;
+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.origin.Origin;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+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 R8FeatureSplitServiceLoaderTest extends SplitterTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public R8FeatureSplitServiceLoaderTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8AllServiceConfigurationInBaseAndNoTypesInFeatures() throws Exception {
+ Path base = temp.newFile("base.zip").toPath();
+ Path feature1Path = temp.newFile("feature1.zip").toPath();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, I.class, Feature1I.class, Feature2I.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Base.class)
+ .addFeatureSplit(
+ builder -> simpleSplitProvider(builder, feature1Path, temp, Feature3Dummy.class))
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(Feature1I.class.getTypeName(), Feature2I.class.getTypeName())
+ .getBytes(),
+ "META-INF/services/" + I.class.getTypeName(),
+ Origin.unknown()))
+ .compile()
+ .inspect(
+ inspector -> {
+ assertEquals(0, getServiceLoaderLoads(inspector, Base.class));
+ })
+ .writeToZip(base)
+ .run(parameters.getRuntime(), Base.class)
+ .assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+ }
+
+ @Test
+ public void testR8AllServiceConfigurationInBase() throws Exception {
+ Path base = temp.newFile("base.zip").toPath();
+ Path feature1Path = temp.newFile("feature1.zip").toPath();
+ Path feature2Path = temp.newFile("feature2.zip").toPath();
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, I.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Base.class)
+ .addFeatureSplit(
+ builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
+ .addFeatureSplit(
+ builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(Feature1I.class.getTypeName(), Feature2I.class.getTypeName())
+ .getBytes(),
+ "META-INF/services/" + I.class.getTypeName(),
+ Origin.unknown()))
+ .compile()
+ .inspect(
+ inspector -> {
+ assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+ })
+ .writeToZip(base)
+ .addRunClasspathFiles(feature1Path, feature2Path)
+ .run(parameters.getRuntime(), Base.class);
+ // TODO(b/160888348): This is failing on 7.0
+ if (parameters.getRuntime().isDex()
+ && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+ runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+ } else {
+ runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+ }
+ }
+
+ @Test
+ public void testR8AllLoaded() throws Exception {
+ Path base = temp.newFile("base.zip").toPath();
+ Path feature1Path = temp.newFile("feature1.zip").toPath();
+ Path feature2Path = temp.newFile("feature2.zip").toPath();
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, I.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Base.class)
+ .addFeatureSplit(
+ builder ->
+ splitWithNonJavaFile(
+ builder,
+ feature1Path,
+ temp,
+ ImmutableList.of(
+ new Pair<>(
+ "META-INF/services/" + I.class.getTypeName(),
+ StringUtils.lines(Feature1I.class.getTypeName()))),
+ true,
+ Feature1I.class))
+ .addFeatureSplit(
+ builder ->
+ splitWithNonJavaFile(
+ builder,
+ feature2Path,
+ temp,
+ ImmutableList.of(
+ new Pair<>(
+ "META-INF/services/" + I.class.getTypeName(),
+ StringUtils.lines(Feature2I.class.getTypeName()))),
+ true,
+ Feature2I.class))
+ .compile()
+ .inspect(
+ inspector -> {
+ assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+ })
+ .writeToZip(base)
+ .addRunClasspathFiles(feature1Path, feature2Path)
+ .run(parameters.getRuntime(), Base.class);
+ // TODO(b/160888348): This is failing on 7.0
+ if (parameters.getRuntime().isDex()
+ && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+ runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+ } else {
+ runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+ }
+ }
+
+ @Test
+ public void testR8WithServiceFileInSeparateFeature() throws Exception {
+ Path base = temp.newFile("base.zip").toPath();
+ Path feature1Path = temp.newFile("feature1.zip").toPath();
+ Path feature2Path = temp.newFile("feature2.zip").toPath();
+ Path feature3Path = temp.newFile("feature3.zip").toPath();
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, I.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Base.class)
+ .addFeatureSplit(
+ builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
+ .addFeatureSplit(
+ builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
+ .addFeatureSplit(
+ builder ->
+ splitWithNonJavaFile(
+ builder,
+ feature3Path,
+ temp,
+ ImmutableList.of(
+ new Pair<>(
+ "META-INF/services/" + I.class.getTypeName(),
+ StringUtils.lines(
+ Feature1I.class.getTypeName(), Feature2I.class.getTypeName()))),
+ true,
+ Feature3Dummy.class))
+ .compile()
+ .inspect(
+ inspector -> {
+ assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+ })
+ .writeToZip(base)
+ .addRunClasspathFiles(feature1Path, feature2Path, feature3Path)
+ .run(parameters.getRuntime(), Base.class);
+ // TODO(b/160888348): This is failing on 7.0
+ if (parameters.getRuntime().isDex()
+ && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+ runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+ } else {
+ runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+ }
+ }
+
+ @Test
+ public void testR8OnlyFeature2() throws Exception {
+ Path base = temp.newFile("base.zip").toPath();
+ Path feature1Path = temp.newFile("feature1.zip").toPath();
+ Path feature2Path = temp.newFile("feature2.zip").toPath();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, I.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Base.class)
+ .addFeatureSplit(
+ builder ->
+ splitWithNonJavaFile(
+ builder,
+ feature1Path,
+ temp,
+ ImmutableList.of(
+ new Pair<>(
+ "META-INF/services/" + I.class.getTypeName(),
+ StringUtils.lines(Feature1I.class.getTypeName()))),
+ true,
+ Feature1I.class))
+ .addFeatureSplit(
+ builder ->
+ splitWithNonJavaFile(
+ builder,
+ feature2Path,
+ temp,
+ ImmutableList.of(
+ new Pair<>(
+ "META-INF/services/" + I.class.getTypeName(),
+ StringUtils.lines(Feature2I.class.getTypeName()))),
+ true,
+ Feature2I.class))
+ .compile()
+ .inspect(
+ inspector -> {
+ assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+ })
+ .writeToZip(base)
+ .addRunClasspathFiles(feature2Path)
+ .run(parameters.getRuntime(), Base.class)
+ // TODO(b/160889305): This should work.
+ .assertFailureWithErrorThatMatches(containsString("java.lang.ClassNotFoundException"));
+ }
+
+ public interface I {
+ void foo();
+ }
+
+ public static class Base {
+
+ public static void main(String[] args) {
+ for (I i : ServiceLoader.load(I.class, null)) {
+ i.foo();
+ }
+ }
+ }
+
+ public static class Feature1I implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("Feature1I.foo()");
+ }
+ }
+
+ public static class Feature2I implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("Feature2I.foo()");
+ }
+ }
+
+ public static class Feature3Dummy {}
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
index 6f609de..60477b6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
@@ -40,7 +41,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection params() {
- return getTestParameters().withDexRuntimes().build();
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
}
private final TestParameters parameters;
@@ -122,11 +123,12 @@
Path basePath = temp.newFile("base.zip").toPath();
Path feature1Path = temp.newFile("feature1.zip").toPath();
Path feature2Path = temp.newFile("feature2.zip").toPath();
- Collection<String> nonJavaFiles = ImmutableList.of("foobar", "barfoo");
+ Collection<Pair<String, String>> nonJavaFiles =
+ ImmutableList.of(new Pair<>("foobar", "foobar"), new Pair<>("barfoo", "barfoo"));
testForR8(parameters.getBackend())
.addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.addFeatureSplit(
builder ->
splitWithNonJavaFile(
@@ -140,16 +142,16 @@
.writeToZip(basePath);
for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
ZipFile zipFile = new ZipFile(feature.toFile());
- for (String nonJavaFile : nonJavaFiles) {
- ZipEntry entry = zipFile.getEntry(nonJavaFile);
+ for (Pair<String, String> nonJavaFile : nonJavaFiles) {
+ ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
assertNotNull(entry);
String content = new String(ByteStreams.toByteArray(zipFile.getInputStream(entry)));
- assertEquals(content, nonJavaFile);
+ assertEquals(content, nonJavaFile.getSecond());
}
}
ZipFile zipFile = new ZipFile(basePath.toFile());
- for (String nonJavaFile : nonJavaFiles) {
- ZipEntry entry = zipFile.getEntry(nonJavaFile);
+ for (Pair<String, String> nonJavaFile : nonJavaFiles) {
+ ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
assertNull(entry);
}
}
@@ -160,12 +162,14 @@
Path feature1Path = temp.newFile("feature1.zip").toPath();
Path feature2Path = temp.newFile("feature2.zip").toPath();
// Make the content of the data resource be class names
- Collection<String> nonJavaFiles =
- ImmutableList.of(FeatureClass.class.getName(), FeatureClass2.class.getName());
+ Collection<Pair<String, String>> nonJavaFiles =
+ ImmutableList.of(
+ new Pair<>(FeatureClass.class.getName(), FeatureClass.class.getName()),
+ new Pair<>(FeatureClass2.class.getName(), FeatureClass2.class.getName()));
testForR8(parameters.getBackend())
.addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.addFeatureSplit(
builder ->
splitWithNonJavaFile(
@@ -181,16 +185,16 @@
.writeToZip(basePath);
for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
ZipFile zipFile = new ZipFile(feature.toFile());
- for (String nonJavaFile : nonJavaFiles) {
- ZipEntry entry = zipFile.getEntry(nonJavaFile);
+ for (Pair<String, String> nonJavaFile : nonJavaFiles) {
+ ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
assertNotNull(entry);
String content = new String(ByteStreams.toByteArray(zipFile.getInputStream(entry)));
- assertNotEquals(content, nonJavaFile);
+ assertNotEquals(content, nonJavaFile.getSecond());
}
}
ZipFile zipFile = new ZipFile(basePath.toFile());
- for (String nonJavaFile : nonJavaFiles) {
- ZipEntry entry = zipFile.getEntry(nonJavaFile);
+ for (Pair<String, String> nonJavaFile : nonJavaFiles) {
+ ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
assertNull(entry);
}
}
@@ -261,7 +265,7 @@
testForR8(parameters.getBackend())
.addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.addFeatureSplit(
builder -> simpleSplitProvider(builder, feature1Path, temp, FeatureClass.class))
.addFeatureSplit(
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
index 331403a..def7782 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -37,7 +37,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection params() {
- return getTestParameters().withDexRuntimes().build();
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
}
private final TestParameters parameters;
@@ -72,6 +72,7 @@
@NeverMerge
public abstract static class BaseSuperClass implements RunInterface {
+ @Override
public void run() {
System.out.println(getFromFeature());
}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 16291c4..536d19d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -1,6 +1,7 @@
package com.android.tools.r8.dexsplitter;
import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.ByteDataView;
@@ -19,6 +20,7 @@
import com.android.tools.r8.dexsplitter.DexSplitter.Options;
import com.android.tools.r8.utils.ArchiveResourceProvider;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
@@ -42,11 +44,11 @@
public class SplitterTestBase extends TestBase {
public static FeatureSplit simpleSplitProvider(
- FeatureSplit.Builder builder, Path outputPath, TemporaryFolder temp, Class... classes) {
+ FeatureSplit.Builder builder, Path outputPath, TemporaryFolder temp, Class<?>... classes) {
return simpleSplitProvider(builder, outputPath, temp, Arrays.asList(classes));
}
- public static FeatureSplit simpleSplitProvider(
+ private static FeatureSplit simpleSplitProvider(
FeatureSplit.Builder builder,
Path outputPath,
TemporaryFolder temp,
@@ -59,7 +61,7 @@
FeatureSplit.Builder builder,
Path outputPath,
TemporaryFolder temp,
- Collection<String> nonJavaResources,
+ Collection<Pair<String, String>> nonJavaResources,
boolean ensureClassesInOutput,
Collection<Class<?>> classes) {
List<String> classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
@@ -82,15 +84,18 @@
next = inputStream.getNextEntry();
}
- for (String nonJavaResource : nonJavaResources) {
+ for (Pair<String, String> nonJavaResource : nonJavaResources) {
ZipUtils.writeToZipStream(
- outputStream, nonJavaResource, nonJavaResource.getBytes(), ZipEntry.STORED);
+ outputStream,
+ nonJavaResource.getFirst(),
+ nonJavaResource.getSecond().getBytes(),
+ ZipEntry.STORED);
}
outputStream.close();
featureJar = newFeatureJar;
}
} catch (IOException e) {
- assertTrue(false);
+ fail();
return;
}
@@ -115,18 +120,19 @@
});
}
- protected static FeatureSplit splitWithNonJavaFile(
+ static FeatureSplit splitWithNonJavaFile(
FeatureSplit.Builder builder,
Path outputPath,
TemporaryFolder temp,
- Collection<String> nonJavaFiles,
+ Collection<Pair<String, String>> nonJavaFiles,
boolean ensureClassesInOutput,
Class<?>... classes) {
- addConsumers(builder, outputPath, temp, nonJavaFiles, true, Arrays.asList(classes));
+ addConsumers(
+ builder, outputPath, temp, nonJavaFiles, ensureClassesInOutput, Arrays.asList(classes));
return builder.build();
}
- protected <E extends Throwable> ProcessResult testR8Splitter(
+ <E extends Throwable> ProcessResult testR8Splitter(
TestParameters parameters,
Set<Class<?>> baseClasses,
Set<Class<?>> featureClasses,
@@ -150,7 +156,7 @@
.addProgramClasses(baseClasses)
.addFeatureSplit(
builder -> simpleSplitProvider(builder, featureOutput, temp, featureClasses))
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.addKeepMainRule(SplitRunner.class)
.addKeepClassRules(toRun);
@@ -165,7 +171,7 @@
// Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
// based on the base/feature sets. toRun must implement the BaseRunInterface
- protected <E extends Throwable> ProcessResult testDexSplitter(
+ <E extends Throwable> ProcessResult testDexSplitter(
TestParameters parameters,
Set<Class<?>> baseClasses,
Set<Class<?>> featureClasses,
@@ -189,7 +195,7 @@
.addClasspathClasses(baseClasses)
.addClasspathClasses(RunInterface.class)
.addKeepAllClassesRule()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.writeToZip();
if (parameters.isDexRuntime()) {
@@ -199,7 +205,7 @@
testForD8()
.addProgramClasses(SplitRunner.class, RunInterface.class)
.addProgramClasses(baseClasses)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.run(
parameters.getRuntime(),
@@ -220,7 +226,7 @@
R8FullTestBuilder r8FullTestBuilder =
builder
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(SplitRunner.class, RunInterface.class)
.addProgramClasses(baseClasses)
.addProgramClasses(featureClasses)
@@ -252,7 +258,7 @@
toRun, splitterBaseDexFile, splitterFeatureDexFile, parameters.getRuntime());
}
- protected ProcessResult runFeatureOnArt(
+ ProcessResult runFeatureOnArt(
Class toRun, Path splitterBaseDexFile, Path splitterFeatureDexFile, TestRuntime runtime)
throws IOException {
assumeTrue(runtime.isDex());
@@ -261,8 +267,7 @@
commandBuilder.appendProgramArgument(toRun.getName());
commandBuilder.appendProgramArgument(splitterFeatureDexFile.toString());
commandBuilder.setMainClass(SplitRunner.class.getName());
- ProcessResult processResult = ToolHelper.runArtRaw(commandBuilder);
- return processResult;
+ return ToolHelper.runArtRaw(commandBuilder);
}
public interface RunInterface {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java
new file mode 100644
index 0000000..f6b55cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumMissingFieldsUnboxingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EnumMissingFieldsUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(getEnumProgramData())
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .enableNeverClassInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsBoxed(CompilationEnum.class, TestClass.class.getSimpleName(), m))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatMatches(containsString("NoSuchFieldError"));
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ private byte[] getEnumProgramData() throws IOException {
+ return transformer(RuntimeEnum.class)
+ .setClassDescriptor(descriptor(CompilationEnum.class))
+ .transform();
+ }
+
+ // CompilationEnum is used for the compilation of TestClass.
+ @NeverClassInline
+ public enum CompilationEnum {
+ A,
+ B,
+ C,
+ D
+ }
+
+ // CompilationEnum is used for the runtime execution of TestClass.
+ @NeverClassInline
+ public enum RuntimeEnum {
+ A,
+ D
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(CompilationEnum.A.ordinal());
+ System.out.println(0);
+ // The field C will be missing at runtime.
+ System.out.println(CompilationEnum.C.ordinal());
+ System.out.println(2);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index 1f82de1..65aee25 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -63,6 +63,7 @@
}
void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) {
+ // TODO(b/160854837): re-enable enum unboxing.
options.enableEnumUnboxing = true;
options.enableEnumValueOptimization = enumValueOptimization;
options.enableEnumSwitchMapRemoval = enumValueOptimization;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
index 6323e8b..2e3466f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.enumunboxing;
+import static org.hamcrest.CoreMatchers.containsString;
+
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import java.util.List;
@@ -36,10 +39,11 @@
@Test
public void testEnumUnboxing() throws Exception {
Class<?> classToTest = VirtualMethods.class;
- R8TestRunResult run =
+ R8TestCompileResult compile =
testForR8(parameters.getBackend())
.addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
.addKeepMainRule(classToTest)
+ .addKeepMainRule(VirtualMethodsFail.class)
.addKeepRules(enumKeepRules.getKeepRules())
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
@@ -51,10 +55,16 @@
m -> {
assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m);
assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m);
- })
- .run(parameters.getRuntime(), classToTest)
- .assertSuccess();
+ assertEnumIsUnboxed(MyEnumWithCollisions.class, classToTest.getSimpleName(), m);
+ assertEnumIsUnboxed(
+ MyEnumWithPackagePrivateCall.class, classToTest.getSimpleName(), m);
+ });
+ R8TestRunResult run = compile.run(parameters.getRuntime(), classToTest).assertSuccess();
assertLines2By2Correct(run.getStdOut());
+ // TODO(b/160854837): This test should actually be successful.
+ compile
+ .run(parameters.getRuntime(), VirtualMethodsFail.class)
+ .assertFailureWithErrorThatMatches(containsString("IllegalAccessError"));
}
@SuppressWarnings("SameParameterValue")
@@ -102,6 +112,7 @@
}
// Use two enums to test collision.
+ @NeverClassInline
enum MyEnum2 {
A,
B,
@@ -123,6 +134,76 @@
}
}
+ @NeverClassInline
+ enum MyEnumWithCollisions {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ public int get() {
+ return get(this);
+ }
+
+ @NeverInline
+ public static int get(MyEnumWithCollisions e) {
+ switch (e) {
+ case A:
+ return 5;
+ case B:
+ return 2;
+ case C:
+ return 1;
+ }
+ return -1;
+ }
+ }
+
+ @NeverClassInline
+ static class PackagePrivateClass {
+ @NeverInline
+ static void print() {
+ System.out.println("print");
+ }
+ }
+
+ @NeverClassInline
+ enum MyEnumWithPackagePrivateCall {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ public static void callPackagePrivate() {
+ PackagePrivateClass.print();
+ }
+ }
+
+ static class VirtualMethodsFail {
+ public static void main(String[] args) {
+ testCollisions();
+ testPackagePrivate();
+ }
+
+ @NeverInline
+ private static void testPackagePrivate() {
+ System.out.println(MyEnumWithPackagePrivateCall.A.ordinal());
+ System.out.println(0);
+ MyEnumWithPackagePrivateCall.callPackagePrivate();
+ System.out.println("print");
+ }
+
+ @NeverInline
+ private static void testCollisions() {
+ System.out.println(MyEnumWithCollisions.A.get());
+ System.out.println(5);
+ System.out.println(MyEnumWithCollisions.B.get());
+ System.out.println(2);
+ System.out.println(MyEnumWithCollisions.C.get());
+ System.out.println(1);
+ }
+ }
+
static class VirtualMethods {
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
index 2d56233..0465bc5 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
@@ -50,7 +51,11 @@
new ProgramMethod(
clazz,
new DexEncodedMethod(
- signature, null, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null));
+ signature,
+ MethodAccessFlags.fromDexAccessFlags(0),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ null));
return new Node(method);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 75c45d1..1d7f782 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -369,16 +370,27 @@
InstructionSubject instruction = iterator.next();
if (instruction.isInstanceGet()) {
++instanceGetCount;
- } else if (instruction.isInvoke()
- && !instruction
- .getMethod()
- .name
- .toString()
- .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX)) {
+ } else if (instruction.isInvoke() && !isEnumInvoke(instruction)) {
++invokeCount;
}
}
assertEquals(1, instanceGetCount);
- assertEquals(BooleanUtils.intValue(parameters.isCfRuntime()), invokeCount);
+ assertEquals(0, invokeCount);
+ }
+
+ private boolean isEnumInvoke(InstructionSubject instruction) {
+ InternalOptions defaults = new InternalOptions();
+ if (parameters.isDexRuntime() && defaults.enableEnumUnboxing) {
+ return instruction
+ .getMethod()
+ .name
+ .toString()
+ .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX);
+ } else {
+ return ((InvokeInstructionSubject) instruction)
+ .holder()
+ .toString()
+ .contains("java.lang.Enum");
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
new file mode 100644
index 0000000..4f60021
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+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 com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a reproduction of b/160856783. */
+@RunWith(Parameterized.class)
+public class CheckCastNullForTypeTest extends TestBase {
+
+ private final TestParameters parameters;
+ private static final String EXPECTED = StringUtils.lines("null", "null");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public CheckCastNullForTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ codeInspector -> {
+ ClassSubject main = codeInspector.clazz(Main.class);
+ assertThat(main, isPresent());
+ MethodSubject mainMethod = main.uniqueMethodWithName("main");
+ assertThat(mainMethod, isPresent());
+ // TODO(b/160856783): Investigate if this can be removed.
+ assertEquals(
+ 1,
+ mainMethod
+ .streamInstructions()
+ .filter(instruction -> instruction.isCheckCast(Main.class.getTypeName()))
+ .count());
+ });
+ }
+
+ public static class A {}
+
+ public static class Main {
+
+ private static void print(Main main) {
+ System.out.println(main);
+ }
+
+ public static void main(String[] args) {
+ A a = null;
+ Main main;
+ do {
+ main = (Main) (Object) a;
+ } while ((a = (A) null) != null);
+ System.out.println(a);
+ print(main);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerInstanceEscapeViaPhiTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerInstanceEscapeViaPhiTest.java
new file mode 100644
index 0000000..147d2ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerInstanceEscapeViaPhiTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+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.Parameters;
+
+/** Reproduction of b/160901582 where we inline class with escaping instance variable. */
+@RunWith(Parameterized.class)
+public class ClassInlinerInstanceEscapeViaPhiTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlinerInstanceEscapeViaPhiTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(ClassInlinerInstanceEscapeViaPhiTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("false");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlinerInstanceEscapeViaPhiTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("false");
+ }
+
+ static class A {
+
+ public A() {
+ B.foo(System.nanoTime() > 0 ? this : null);
+ }
+ }
+
+ static class B {
+
+ static void foo(A a) {
+ System.out.println((System.nanoTime() > 0 ? a : null) == null);
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new A();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
index 0c3206c..383a7ef 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
@@ -57,15 +57,13 @@
}
private void checkJoinerIsClassInlined(CodeInspector inspector) {
- // TODO(b/160640028): When compiling to DEX a trivial phi remains in the inline code preventing
- // class inlining of Joiner and the anonymous Joiner subclass.
+ assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), not(isPresent()));
+ // TODO(b/160640028): When compiling to DEX the outer Joiner class is not inlined.
if (parameters.isDexRuntime()) {
assertThat(inspector.clazz(Joiner.class), isPresent());
- assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), isPresent());
- return;
+ } else {
+ assertThat(inspector.clazz(Joiner.class), not(isPresent()));
}
- assertThat(inspector.clazz(Joiner.class), not(isPresent()));
- assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160831625Test.java b/src/test/java/com/android/tools/r8/regress/Regress160831625Test.java
new file mode 100644
index 0000000..15c0c75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/Regress160831625Test.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.regress;
+
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress160831625Test extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A", "0");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public Regress160831625Test(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(getClass())
+ .enableMemberValuePropagationAnnotations()
+ .addKeepMainRule(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ enum MyEnum {
+ // Ensure that the enum field value is not inlined in the alias in MyClass.B
+ @NeverPropagateValue
+ A
+ }
+
+ static class MyClass {
+
+ private static final MyEnum B = MyEnum.A;
+
+ public static MyEnum getB() {
+ return B;
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(MyClass.getB().name());
+ System.out.println(MyClass.getB().ordinal());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
index 5f46a45..5e18734 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
@@ -326,7 +326,7 @@
assertNotNull(zip.getEntry("META-INF/services/" + service.getFinalName()));
}
- private static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
+ public static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
ClassSubject classSubject = inspector.clazz(clazz);
assertTrue(classSubject.isPresent());
return classSubject.allMethods().stream()
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 962bc23..2dcfeb6 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -208,6 +208,10 @@
if not os.path.exists(temp):
os.makedirs(temp)
dump = read_dump(args, temp)
+ if not dump.program_jar():
+ error("Cannot compile dump with no program classes")
+ if not dump.library_jar():
+ print "WARNING: Unexpected lack of library classes in dump"
build_properties = determine_build_properties(args, dump)
version = determine_version(args, dump)
compiler = determine_compiler(args, dump)
diff --git a/tools/r8lib_size_compare.py b/tools/r8lib_size_compare.py
deleted file mode 100755
index 568f254..0000000
--- a/tools/r8lib_size_compare.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
-'''
-Build r8lib.jar with both R8 and ProGuard and print a size comparison.
-
-By default, inlining is disabled in both R8 and ProGuard to make
-method-by-method comparison much easier. Pass --inlining to enable inlining.
-
-By default, only shows methods where R8's DEX output is 5 or more instructions
-larger than ProGuard+D8's output. Pass --threshold 0 to display all methods.
-'''
-
-import argparse
-import build_r8lib
-import os
-import subprocess
-import toolhelper
-import utils
-
-
-parser = argparse.ArgumentParser(description=__doc__.strip(),
- formatter_class=argparse.RawTextHelpFormatter)
-parser.add_argument('-t', '--tmpdir',
- help='Store auxiliary files in given directory')
-parser.add_argument('-i', '--inlining', action='store_true',
- help='Enable inlining')
-parser.add_argument('--threshold')
-
-R8_RELOCATIONS = [
- ('com.google.common', 'com.android.tools.r8.com.google.common'),
- ('com.google.gson', 'com.android.tools.r8.com.google.gson'),
- ('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'),
- ('joptsimple', 'com.android.tools.r8.joptsimple'),
- ('org.apache.commons', 'com.android.tools.r8.org.apache.commons'),
- ('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'),
- ('it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'),
-]
-
-
-def is_output_newer(input, output):
- if not os.path.exists(output):
- return False
- return os.stat(input).st_mtime < os.stat(output).st_mtime
-
-
-def check_call(args, **kwargs):
- utils.PrintCmd(args)
- return subprocess.check_call(args, **kwargs)
-
-
-def main(tmpdir=None, inlining=True,
- run_jarsizecompare=True, threshold=None):
- if tmpdir is None:
- with utils.TempDir() as tmpdir:
- return main(tmpdir, inlining)
-
- inline_suffix = '-inline' if inlining else '-noinline'
-
- pg_config = utils.R8LIB_KEEP_RULES
- r8lib_jar = os.path.join(utils.LIBS, 'r8lib%s.jar' % inline_suffix)
- r8lib_map = os.path.join(utils.LIBS, 'r8lib%s-map.txt' % inline_suffix)
- r8lib_args = None
- if not inlining:
- r8lib_args = ['-Dcom.android.tools.r8.disableinlining=1']
- pg_config = os.path.join(tmpdir, 'keep-noinline.txt')
- with open(pg_config, 'w') as new_config:
- with open(utils.R8LIB_KEEP_RULES) as old_config:
- new_config.write(old_config.read().rstrip('\n') +
- '\n-optimizations !method/inlining/*\n')
-
- if not is_output_newer(utils.R8_JAR, r8lib_jar):
- r8lib_memory = os.path.join(tmpdir, 'r8lib%s-memory.txt' % inline_suffix)
- # TODO(b/160420801): The signature of build_r8lib has changed.
- build_r8lib.build_r8lib(
- output_path=r8lib_jar,
- output_map=r8lib_map,
- extra_args=r8lib_args,
- track_memory_file=r8lib_memory)
-
- pg_output = os.path.join(tmpdir, 'r8lib-pg%s.jar' % inline_suffix)
- pg_memory = os.path.join(tmpdir, 'r8lib-pg%s-memory.txt' % inline_suffix)
- pg_map = os.path.join(tmpdir, 'r8lib-pg%s-map.txt' % inline_suffix)
- # TODO(b/160420801): This must use proguard.* utils once working again.
- pg_args = ['tools/track_memory.sh', pg_memory,
- 'third_party/proguard/proguard6.0.2/bin/proguard.sh',
- '@' + pg_config,
- '-lib', utils.RT_JAR,
- '-injar', utils.R8_JAR,
- '-printmapping', pg_map,
- '-outjar', pg_output]
- for library_name, relocated_package in R8_RELOCATIONS:
- pg_args.extend(['-dontwarn', relocated_package + '.**',
- '-dontnote', relocated_package + '.**'])
- check_call(pg_args)
- if threshold is None:
- threshold = 5
- toolhelper.run('jarsizecompare',
- ['--threshold', str(threshold),
- '--lib', utils.RT_JAR,
- '--input', 'input', utils.R8_JAR,
- '--input', 'r8', r8lib_jar, r8lib_map,
- '--input', 'pg', pg_output, pg_map])
-
-
-if __name__ == '__main__':
- main(**vars(parser.parse_args()))
diff --git a/tools/test.py b/tools/test.py
index ad99485..5bc9d6c 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -143,6 +143,8 @@
help='Test parameter runtimes to use, separated by : (eg, none:jdk9).'
' Special values include: all (for all runtimes)'
' and empty (for no runtimes).')
+ result.add_option('--print-hanging-stacks', '--print_hanging_stacks',
+ default=-1, type="int", help='Print hanging stacks after timeout in seconds')
return result.parse_args()
def archive_failures():
@@ -269,13 +271,16 @@
'%s.tar.gz' % sha1)
utils.unpack_archive('%s.tar.gz' % sha1)
- if utils.is_bot() and not utils.IsWindows():
+ print_stacks_timeout = options.print_hanging_stacks
+ if (utils.is_bot() and not utils.IsWindows()) or print_stacks_timeout > -1:
timestamp_file = os.path.join(utils.BUILD, 'last_test_time')
if os.path.exists(timestamp_file):
os.remove(timestamp_file)
gradle_args.append('-Pupdate_test_timestamp=' + timestamp_file)
- thread.start_new_thread(timeout_handler, (timestamp_file,))
-
+ print_stacks_timeout = (print_stacks_timeout
+ if print_stacks_timeout != -1
+ else TIMEOUT_HANDLER_PERIOD)
+ thread.start_new_thread(timeout_handler, (timestamp_file, print_stacks_timeout,))
rotate_test_reports()
if options.only_jctf:
@@ -367,10 +372,10 @@
sys.stdout.flush()
return None
-def timeout_handler(timestamp_file):
+def timeout_handler(timestamp_file, timeout_handler_period):
last_timestamp = None
while True:
- time.sleep(TIMEOUT_HANDLER_PERIOD)
+ time.sleep(timeout_handler_period)
new_timestamp = get_time_from_file(timestamp_file)
if last_timestamp and new_timestamp == last_timestamp:
print_jstacks()