Version 1.4.34
Cherry pick: Temporarily disable root item assertion
CL: https://r8-review.googlesource.com/c/r8/+/33846
Cherry pick: Allow synthetic methods in the root set
CL: https://r8-review.googlesource.com/c/r8/+/33623
Cherry pick: Avoid adding static / private interface methods to the root set.
CL: https://r8-review.googlesource.com/c/r8/+/33601
Cherry pick: Replace DexDefinition in RootSet with DexReference.
CL: https://r8-review.googlesource.com/c/r8/+/32860
Cherry pick: Revert "Revert "Forward GraphLense to IRBuilder""
CL: https://r8-review.googlesource.com/c/r8/+/33488
Cherry pick: Reproduce b/122819537: kotlinc-generated synthetic methods are not traced.
CL: https://r8-review.googlesource.com/c/r8/+/33032
Cherry pick: Add tests to process kotlin stdlib and reflection lib.
CL: https://r8-review.googlesource.com/c/r8/+/33022
Cherry pick: Revert "Revert "Verify mapping of field and method signatures to original program""
CL: https://r8-review.googlesource.com/c/r8/+/32676
Bug: 123317382, 120971047, 123861235, 122066794, 122819537, 121295633, 112847660, 120263141, 121240523, 120118197, 123923324
Change-Id: Ic690535cbb5b14e735e85bbedb97ac76e6155ad1
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 14dd1ae..aafe941 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.shaking.DiscardedChecker;
import com.android.tools.r8.shaking.Enqueuer;
@@ -80,9 +80,9 @@
}
// Print -whyareyoukeeping results if any.
if (whyAreYouKeepingConsumer != null) {
- for (DexDefinition definition : mainDexRootSet.reasonAsked) {
+ for (DexReference reference : mainDexRootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(definition), System.out);
+ enqueuer.getGraphNode(reference), System.out);
}
}
diff --git a/src/main/java/com/android/tools/r8/JarSizeCompare.java b/src/main/java/com/android/tools/r8/JarSizeCompare.java
index aa16b1f..2aa124a 100644
--- a/src/main/java/com/android/tools/r8/JarSizeCompare.java
+++ b/src/main/java/com/android/tools/r8/JarSizeCompare.java
@@ -332,7 +332,7 @@
MethodSignature originalSignature =
proguardMap == null
? null
- : ((MethodSignature) proguardMap.originalSignatureOf(dexEncodedMethod.method));
+ : proguardMap.originalSignatureOf(dexEncodedMethod.method);
MethodSignature signature = MethodSignature.fromDexMethod(dexEncodedMethod.method);
consumer.accept(
originalSignature == null ? signature : originalSignature, dexEncodedMethod);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8ef9af6..4da9b6e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -13,11 +13,12 @@
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.AppliedGraphLens;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.conversion.IRConverter;
@@ -370,7 +371,7 @@
// We can now remove visibility bridges. Note that we do not need to update the
// invoke-targets here, as the existing invokes will simply dispatch to the now
// visible super-method. MemberRebinding, if run, will then dispatch it correctly.
- application = new VisibilityBridgeRemover(appView.appInfo(), application).run();
+ new VisibilityBridgeRemover(appView.withLiveness()).run();
}
}
@@ -401,8 +402,10 @@
SeedMapper.seedMapperFromFile(
options.getProguardConfiguration().getApplyMappingFile());
timing.begin("apply-mapping");
- appView.setGraphLense(
- new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing));
+ GraphLense applyMappingLense =
+ new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing);
+ rootSet = rootSet.rewrittenWithLense(applyMappingLense);
+ appView.setGraphLense(applyMappingLense);
application = application.asDirect().rewrittenWithLense(appView.graphLense());
appView.setAppInfo(
appView
@@ -473,18 +476,28 @@
new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
}
+ assert appView.appInfo().hasLiveness();
+
timing.begin("Create IR");
Set<DexCallSite> desugaredCallSites;
CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
try {
IRConverter converter =
- new IRConverter(appView, options, timing, printer, mainDexClasses, rootSet);
+ new IRConverter(
+ appView.withLiveness(), options, timing, printer, mainDexClasses, rootSet);
application = converter.optimize(application, executorService);
desugaredCallSites = converter.getDesugaredCallSites();
} finally {
timing.end();
}
+ // At this point all code has been mapped according to the graph lens. We cannot remove the
+ // graph lens entirely, though, since it is needed for mapping all field and method signatures
+ // back to the original program.
+ timing.begin("AppliedGraphLens construction");
+ appView.setGraphLense(new AppliedGraphLens(appView, application.classes()));
+ timing.end();
+
if (options.printCfg) {
if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
System.out.print(printer.toString());
@@ -527,9 +540,9 @@
.run();
}
if (whyAreYouKeepingConsumer != null) {
- for (DexDefinition dexDefinition : mainDexRootSet.reasonAsked) {
+ for (DexReference reference : mainDexRootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(dexDefinition), System.out);
+ enqueuer.getGraphNode(reference), System.out);
}
}
}
@@ -569,9 +582,9 @@
// Print reasons on the application after pruning, so that we reflect the actual result.
if (whyAreYouKeepingConsumer != null) {
- for (DexDefinition dexDefinition : rootSet.reasonAsked) {
+ for (DexReference reference : rootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(dexDefinition), System.out);
+ enqueuer.getGraphNode(reference), System.out);
}
}
// Remove annotations that refer to types that no longer exist.
@@ -632,7 +645,14 @@
// Validity checks.
assert application.classes().stream().allMatch(DexClass::isValid);
- assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo(), options);
+ assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo());
+ assert appView
+ .graphLense()
+ .verifyMappingToOriginalProgram(
+ application.classesWithDeterministicOrder(),
+ new ApplicationReader(inputApp.withoutMainDexList(), options, timing)
+ .read(executorService),
+ appView.dexItemFactory());
// Report synthetic rules (only for testing).
// TODO(b/120959039): Move this to being reported through the graph consumer.
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 20093ab..ee26dd8 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.33";
+ public static final String LABEL = "1.4.34";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 79ea482..5be17c6 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -716,7 +716,7 @@
private void appendMethod(DexMethod method) {
if (mapper != null) {
- MethodSignature signature = (MethodSignature) mapper.originalSignatureOf(method);
+ MethodSignature signature = mapper.originalSignatureOf(method);
builder.append(mapper.originalNameOf(method.holder)).append('.');
builder.append(signature.name).append(signature.toDescriptor());
return;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index c9edb88..028f5a2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -66,6 +66,17 @@
return app.classesWithDeterministicOrder();
}
+ public DexDefinition definitionFor(DexReference reference) {
+ if (reference.isDexType()) {
+ return definitionFor(reference.asDexType());
+ }
+ if (reference.isDexMethod()) {
+ return definitionFor(reference.asDexMethod());
+ }
+ assert reference.isDexField();
+ return definitionFor(reference.asDexField());
+ }
+
public DexClass definitionFor(DexType type) {
return app.definitionFor(type);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index f9a9dde..e232cb9 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -106,6 +106,11 @@
}
@Override
+ public VerticallyMergedClasses verticallyMergedClasses() {
+ return AppView.this.verticallyMergedClasses();
+ }
+
+ @Override
public AppView<AppInfoWithLiveness> withLiveness() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
new file mode 100644
index 0000000..d2387f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -0,0 +1,138 @@
+// 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.graph;
+
+import com.android.tools.r8.ir.code.Invoke;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A graph lens that will not lead to any code rewritings in the {@link
+ * com.android.tools.r8.ir.conversion.LensCodeRewriter}, or parameter removals in the {@link
+ * com.android.tools.r8.ir.conversion.IRBuilder}.
+ *
+ * <p>The mappings from the original program to the generated program are kept, though.
+ */
+public class AppliedGraphLens extends GraphLense {
+
+ private final AppView<? extends AppInfo> appView;
+
+ private final BiMap<DexType, DexType> originalTypeNames = HashBiMap.create();
+ private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
+ private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+ private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
+ new IdentityHashMap<>();
+
+ public AppliedGraphLens(
+ AppView<? extends AppInfoWithSubtyping> appView, List<DexProgramClass> classes) {
+ this.appView = appView;
+
+ for (DexProgramClass clazz : classes) {
+ // Record original type names.
+ {
+ DexType type = clazz.type;
+ if (appView.verticallyMergedClasses() != null
+ && !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
+ DexType original = appView.graphLense().getOriginalType(type);
+ if (original != type) {
+ DexType existing = originalTypeNames.forcePut(type, original);
+ assert existing == null;
+ }
+ }
+ }
+
+ // Record original field signatures.
+ for (DexEncodedField encodedField : clazz.fields()) {
+ DexField field = encodedField.field;
+ DexField original = appView.graphLense().getOriginalFieldSignature(field);
+ if (original != field) {
+ DexField existing = originalFieldSignatures.forcePut(field, original);
+ assert existing == null;
+ }
+ }
+
+ // Record original method signatures.
+ for (DexEncodedMethod encodedMethod : clazz.methods()) {
+ DexMethod method = encodedMethod.method;
+ DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
+ if (original != method) {
+ DexMethod existing = originalMethodSignatures.inverse().get(original);
+ if (existing == null) {
+ originalMethodSignatures.put(method, original);
+ } else {
+ DexMethod renamed = getRenamedMethodSignature(original);
+ if (renamed == existing) {
+ originalMethodSignaturesForBridges.put(method, original);
+ } else {
+ originalMethodSignatures.forcePut(method, original);
+ originalMethodSignaturesForBridges.put(existing, original);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public DexType getOriginalType(DexType type) {
+ return originalTypeNames.getOrDefault(type, type);
+ }
+
+ @Override
+ public DexField getOriginalFieldSignature(DexField field) {
+ return originalFieldSignatures.getOrDefault(field, field);
+ }
+
+ @Override
+ public DexMethod getOriginalMethodSignature(DexMethod method) {
+ if (originalMethodSignaturesForBridges.containsKey(method)) {
+ return originalMethodSignaturesForBridges.get(method);
+ }
+ return originalMethodSignatures.getOrDefault(method, method);
+ }
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ return originalFieldSignatures.inverse().getOrDefault(originalField, originalField);
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ return originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod);
+ }
+
+ @Override
+ public DexType lookupType(DexType type) {
+ if (appView.verticallyMergedClasses() != null
+ && appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
+ return lookupType(appView.verticallyMergedClasses().getTargetFor(type));
+ }
+ return originalTypeNames.inverse().getOrDefault(type, type);
+ }
+
+ @Override
+ public GraphLenseLookupResult lookupMethod(
+ DexMethod method, DexMethod context, Invoke.Type type) {
+ return new GraphLenseLookupResult(method, type);
+ }
+
+ @Override
+ public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+ return RewrittenPrototypeDescription.none();
+ }
+
+ @Override
+ public DexField lookupField(DexField field) {
+ return field;
+ }
+
+ @Override
+ public boolean isContextFreeForMethods() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index b754c9a..fab4a83 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -193,7 +193,15 @@
graphLense.getOriginalMethodSignature(encodedMethod.method),
null,
appInfo);
- IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options, origin);
+ IRBuilder builder =
+ new IRBuilder(
+ encodedMethod,
+ appInfo,
+ source,
+ options,
+ origin,
+ new ValueNumberGenerator(),
+ graphLense);
return builder.build(encodedMethod);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index acf3e05..7a33ba4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -260,6 +260,10 @@
public final DexProto twrCloseResourceMethodProto =
createProto(voidType, throwableType, autoCloseableType);
+ public final DexString deserializeLambdaMethodName = createString("$deserializeLambda$");
+ public final DexProto deserializeLambdaMethodProto =
+ createProto(objectType, createType("Ljava/lang/invoke/SerializedLambda;"));
+
// Dex system annotations.
// See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
public final DexType annotationDefault = createType("Ldalvik/annotation/AnnotationDefault;");
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 3aba90c..bd86c12 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -185,6 +185,11 @@
return builder.toString();
}
+ public boolean isLambdaDeserializeMethod(DexItemFactory dexItemFactory) {
+ return name == dexItemFactory.deserializeLambdaMethodName
+ && proto == dexItemFactory.deserializeLambdaMethodProto;
+ }
+
synchronized public void setSingleVirtualMethodCache(
DexType receiverType, DexEncodedMethod method) {
if (singleTargetCache == null) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index d5e2fed..0c48ea9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -3,10 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
+import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
+
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -442,6 +448,15 @@
return isPrimitiveType((char) descriptor.content[1]);
}
+ public boolean isD8R8SynthesizedClassType() {
+ String name = toSourceString();
+ return name.contains(COMPANION_CLASS_NAME_SUFFIX)
+ || name.contains(DISPATCH_CLASS_NAME_SUFFIX)
+ || name.contains(LAMBDA_CLASS_NAME_PREFIX)
+ || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
+ || name.contains(OutlineOptions.CLASS_NAME);
+ }
+
public int elementSizeForPrimitiveArrayType() {
assert isPrimitiveArrayType();
switch (descriptor.content[1]) {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index c6a527f..884df4f 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -10,11 +10,16 @@
import com.android.tools.r8.utils.IteratorUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
+import java.util.ArrayDeque;
import java.util.Collections;
+import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
@@ -22,6 +27,9 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Function;
/**
* A GraphLense implements a virtual view on top of the graph, used to delay global rewrites until
@@ -478,6 +486,23 @@
return true;
}
+ public ImmutableList<DexReference> rewriteReferencesConservatively(List<DexReference> original) {
+ ImmutableList.Builder<DexReference> builder = ImmutableList.builder();
+ for (DexReference item : original) {
+ if (item.isDexMethod()) {
+ DexMethod method = item.asDexMethod();
+ if (isContextFreeForMethod(method)) {
+ builder.add(lookupMethod(method));
+ } else {
+ builder.addAll(lookupMethodInAllContexts(method));
+ }
+ } else {
+ builder.add(lookupReference(item));
+ }
+ }
+ return builder.build();
+ }
+
public ImmutableSet<DexReference> rewriteReferencesConservatively(Set<DexReference> original) {
ImmutableSet.Builder<DexReference> builder = ImmutableSet.builder();
for (DexReference item : original) {
@@ -495,6 +520,23 @@
return builder.build();
}
+ public Set<DexReference> rewriteMutableReferencesConservatively(Set<DexReference> original) {
+ Set<DexReference> result = Sets.newIdentityHashSet();
+ for (DexReference item : original) {
+ if (item.isDexMethod()) {
+ DexMethod method = item.asDexMethod();
+ if (isContextFreeForMethod(method)) {
+ result.add(lookupMethod(method));
+ } else {
+ result.addAll(lookupMethodInAllContexts(method));
+ }
+ } else {
+ result.add(lookupReference(item));
+ }
+ }
+ return result;
+ }
+
public Object2BooleanMap<DexReference> rewriteReferencesConservatively(
Object2BooleanMap<DexReference> original) {
Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
@@ -516,6 +558,22 @@
return result;
}
+ public ImmutableSet<DexType> rewriteTypesConservatively(Set<DexType> original) {
+ ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+ for (DexType item : original) {
+ builder.add(lookupType(item));
+ }
+ return builder.build();
+ }
+
+ public Set<DexType> rewriteMutableTypesConservatively(Set<DexType> original) {
+ Set<DexType> result = Sets.newIdentityHashSet();
+ for (DexType item : original) {
+ result.add(lookupType(item));
+ }
+ return result;
+ }
+
public ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(Set<DexMethod> methods) {
ImmutableSortedSet.Builder<DexMethod> builder =
new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
@@ -547,6 +605,121 @@
return builder.build();
}
+ public SortedSet<DexMethod> rewriteMutableMethodsConservatively(Set<DexMethod> original) {
+ SortedSet<DexMethod> result = new TreeSet<>(PresortedComparable::slowCompare);
+ if (isContextFreeForMethods()) {
+ for (DexMethod item : original) {
+ result.add(lookupMethod(item));
+ }
+ } else {
+ for (DexMethod item : original) {
+ // Avoid using lookupMethodInAllContexts when possible.
+ if (isContextFreeForMethod(item)) {
+ result.add(lookupMethod(item));
+ } else {
+ // The lense is context sensitive, but we do not have the context here. Therefore, we
+ // conservatively look up the method in all contexts.
+ result.addAll(lookupMethodInAllContexts(item));
+ }
+ }
+ }
+ return result;
+ }
+
+ public static <T extends DexReference, S> ImmutableMap<T, S> rewriteReferenceKeys(
+ Map<T, S> original, Function<T, T> rewrite) {
+ ImmutableMap.Builder<T, S> builder = new ImmutableMap.Builder<>();
+ for (T item : original.keySet()) {
+ builder.put(rewrite.apply(item), original.get(item));
+ }
+ return builder.build();
+ }
+
+ public static <T extends DexReference, S> Map<T, S> rewriteMutableReferenceKeys(
+ Map<T, S> original, Function<T, T> rewrite) {
+ Map<T, S> result = new IdentityHashMap<>();
+ for (T item : original.keySet()) {
+ result.put(rewrite.apply(item), original.get(item));
+ }
+ return result;
+ }
+
+ public boolean verifyMappingToOriginalProgram(
+ Iterable<DexProgramClass> classes,
+ DexApplication originalApplication,
+ DexItemFactory dexItemFactory) {
+ // Collect all original fields and methods for efficient querying.
+ Set<DexField> originalFields = Sets.newIdentityHashSet();
+ Set<DexMethod> originalMethods = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : originalApplication.classes()) {
+ for (DexEncodedField field : clazz.fields()) {
+ originalFields.add(field.field);
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ originalMethods.add(method.method);
+ }
+ }
+
+ // Check that all fields and methods in the generated program can be mapped back to one of the
+ // original fields or methods.
+ for (DexProgramClass clazz : classes) {
+ if (clazz.type.isD8R8SynthesizedClassType()) {
+ continue;
+ }
+ for (DexEncodedField field : clazz.fields()) {
+ DexField originalField = getOriginalFieldSignature(field.field);
+ assert originalFields.contains(originalField)
+ : "Unable to map field `" + field.field.toSourceString() + "` back to original program";
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (method.accessFlags.isSynthetic()) {
+ // This could be a bridge that has been inserted, for example, as a result of member
+ // rebinding. Consider only skipping the check below for methods that have been
+ // synthesized by R8.
+ continue;
+ }
+ DexMethod originalMethod = getOriginalMethodSignature(method.method);
+ assert originalMethods.contains(originalMethod)
+ || verifyIsBridgeMethod(
+ originalMethod, originalApplication, originalMethods, dexItemFactory)
+ : "Unable to map method `"
+ + originalMethod.toSourceString()
+ + "` back to original program";
+ }
+ }
+
+ return true;
+ }
+
+ // Check if `method` is a bridge method for a method that is in the original application.
+ // This is needed because member rebinding synthesizes bridge methods for visibility.
+ private static boolean verifyIsBridgeMethod(
+ DexMethod method,
+ DexApplication originalApplication,
+ Set<DexMethod> originalMethods,
+ DexItemFactory dexItemFactory) {
+ Deque<DexType> worklist = new ArrayDeque<>();
+ Set<DexType> visited = Sets.newIdentityHashSet();
+ worklist.add(method.holder);
+ while (!worklist.isEmpty()) {
+ DexType holder = worklist.removeFirst();
+ if (!visited.add(holder)) {
+ // Already visited previously.
+ continue;
+ }
+ DexMethod targetMethod = dexItemFactory.createMethod(holder, method.proto, method.name);
+ if (originalMethods.contains(targetMethod)) {
+ return true;
+ }
+ DexClass clazz = originalApplication.definitionFor(holder);
+ if (clazz != null) {
+ worklist.add(clazz.superType);
+ Collections.addAll(worklist, clazz.interfaces.values);
+ }
+ }
+ return false;
+ }
+
private static class IdentityGraphLense extends GraphLense {
private static IdentityGraphLense INSTANCE = new IdentityGraphLense();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index a852d28..d5f86fa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -105,8 +105,8 @@
}
@Override
- public InlineAction computeInlining(InliningOracle decider, DexType inocationContext) {
- return decider.computeForInvokeStatic(this, inocationContext);
+ public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+ return decider.computeForInvokeStatic(this, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 9cb4d46..0343e8a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -160,7 +160,7 @@
InternalOptions options,
Timing timing,
CfgPrinter printer,
- AppView<? extends AppInfoWithSubtyping> appView,
+ AppView<? extends AppInfoWithLiveness> appView,
MainDexClasses mainDexClasses,
RootSet rootSet) {
assert appInfo != null;
@@ -180,8 +180,9 @@
this.stringConcatRewriter = new StringConcatRewriter(appInfo);
this.lambdaRewriter = options.enableDesugaring ? new LambdaRewriter(this) : null;
this.interfaceMethodRewriter =
- (options.enableDesugaring && enableInterfaceMethodDesugaring())
- ? new InterfaceMethodRewriter(this, options) : null;
+ options.isInterfaceMethodDesugaringEnabled()
+ ? new InterfaceMethodRewriter(appView, this, options)
+ : null;
this.twrCloseResourceRewriter =
(options.enableDesugaring && enableTwrCloseResourceDesugaring())
? new TwrCloseResourceRewriter(this) : null;
@@ -276,7 +277,7 @@
* Create an IR converter for processing methods with full program optimization enabled.
*/
public IRConverter(
- AppView<AppInfoWithSubtyping> appView,
+ AppView<? extends AppInfoWithLiveness> appView,
InternalOptions options,
Timing timing,
CfgPrinter printer,
@@ -285,16 +286,6 @@
this(appView.appInfo(), options, timing, printer, appView, mainDexClasses, rootSet);
}
- private boolean enableInterfaceMethodDesugaring() {
- switch (options.interfaceMethodDesugaring) {
- case Off:
- return false;
- case Auto:
- return !options.canUseDefaultAndStaticInterfaceMethods();
- }
- throw new Unreachable();
- }
-
private boolean enableTwrCloseResourceDesugaring() {
return enableTryWithResourcesDesugaring() && !options.canUseTwrCloseResourceMethod();
}
@@ -327,10 +318,11 @@
);
}
- private void removeLambdaDeserializationMethods() {
+ private boolean removeLambdaDeserializationMethods() {
if (lambdaRewriter != null) {
- lambdaRewriter.removeLambdaDeserializationMethods(appInfo.classes());
+ return lambdaRewriter.removeLambdaDeserializationMethods(appInfo.classes());
}
+ return false;
}
private void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
@@ -538,8 +530,13 @@
public DexApplication optimize(DexApplication application, ExecutorService executorService)
throws ExecutionException {
+ if (options.enableTreeShaking) {
+ assert !removeLambdaDeserializationMethods();
+ } else {
+ removeLambdaDeserializationMethods();
+ }
+
computeReachabilitySensitivity(application);
- removeLambdaDeserializationMethods();
collectLambdaMergingCandidates(application);
collectStaticizerCandidates(application);
@@ -1190,8 +1187,11 @@
}
if (paramsCheckedForNull.length() > 0) {
// Check if collected information conforms to non-null parameter hints in Kotlin metadata.
+ // These hints are on the original holder. To find the original holder, we first find the
+ // original method signature (this could have changed as a result of, for example, class
+ // merging). Then, we find the type that now corresponds to the the original holder.
DexMethod originalSignature = graphLense().getOriginalMethodSignature(method.method);
- DexClass originalHolder = definitionFor(originalSignature.holder);
+ DexClass originalHolder = definitionFor(graphLense().lookupType(originalSignature.holder));
if (originalHolder.hasKotlinInfo()) {
KotlinInfo kotlinInfo = originalHolder.getKotlinInfo();
if (kotlinInfo.hasNonNullParameterHints()) {
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 a9a571f..e79017d 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
@@ -32,6 +32,7 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
@@ -78,6 +79,7 @@
public static final String DEFAULT_METHOD_PREFIX = "$default$";
public static final String PRIVATE_METHOD_PREFIX = "$private$";
+ private final AppView<? extends AppInfoWithLiveness> appView;
private final IRConverter converter;
private final InternalOptions options;
final DexItemFactory factory;
@@ -112,8 +114,12 @@
ExcludeDexResources
}
- public InterfaceMethodRewriter(IRConverter converter, InternalOptions options) {
+ public InterfaceMethodRewriter(
+ AppView<? extends AppInfoWithLiveness> appView,
+ IRConverter converter,
+ InternalOptions options) {
assert converter != null;
+ this.appView = appView;
this.converter = converter;
this.options = options;
this.factory = options.itemFactory;
@@ -429,7 +435,7 @@
private Map<DexType, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
NestedGraphLense.Builder graphLensBuilder = GraphLense.builder();
- InterfaceProcessor processor = new InterfaceProcessor(this);
+ InterfaceProcessor processor = new InterfaceProcessor(appView, this);
for (DexProgramClass clazz : builder.getProgramClasses()) {
if (shouldProcess(clazz, flavour, true)) {
processor.process(clazz.asProgramClass(), graphLensBuilder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index f39e1df..afe0c8e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -30,6 +31,7 @@
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,11 +49,16 @@
//
// Also moves static interface methods into a companion class.
final class InterfaceProcessor {
+
+ private final AppView<? extends AppInfoWithLiveness> appView;
private final InterfaceMethodRewriter rewriter;
+
// All created companion and dispatch classes indexed by interface type.
final Map<DexType, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
- InterfaceProcessor(InterfaceMethodRewriter rewriter) {
+ InterfaceProcessor(
+ AppView<? extends AppInfoWithLiveness> appView, InterfaceMethodRewriter rewriter) {
+ this.appView = appView;
this.rewriter = rewriter;
}
@@ -106,7 +113,7 @@
}
}
- // If at least one bridge methods was removed update the table.
+ // If at least one bridge method was removed then update the table.
if (remainingMethods.size() < iface.virtualMethods().length) {
iface.setVirtualMethods(remainingMethods.toArray(
new DexEncodedMethod[remainingMethods.size()]));
@@ -304,6 +311,9 @@
// methods. Bridge methods that does not override an implementation in a super-interface must
// also be kept (such a situation can happen if the vertical class merger merges two interfaces).
private boolean interfaceMethodRemovalChangesApi(DexEncodedMethod method, DexClass iface) {
+ if (appView != null && appView.appInfo().isPinned(method.method)) {
+ return true;
+ }
if (method.accessFlags.isBridge()) {
Deque<DexType> worklist = new ArrayDeque<>();
Set<DexType> seenBefore = new HashSet<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index bf36c20..b78f6df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -517,11 +517,6 @@
for (int i = 0; i < directMethods.length; i++) {
DexEncodedMethod encodedMethod = directMethods[i];
if (implMethod.match(encodedMethod)) {
- // Check that this method is synthetic, since this implies that the method cannot be kept
- // by an explicit -keep rule. This is necessary because we could otherwise be changing
- // the signature of a kept method here, which is not allowed (see b/120971047).
- assert encodedMethod.accessFlags.isSynthetic();
-
// We need to create a new static method with the same code to be able to safely
// relax its accessibility without making it virtual.
MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 6836ad3..235c96d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -152,7 +152,7 @@
}
/** If the lambda delegates to lambda$ method. */
- boolean delegatesToLambdaImplMethod() {
+ public boolean delegatesToLambdaImplMethod() {
DexString methodName = implHandle.asMethod().name;
return methodName.toString().startsWith(LambdaRewriter.EXPECTED_LAMBDA_METHOD_PREFIX);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 5814486..eba995f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -45,12 +45,10 @@
* lambda class generation, and instruction patching.
*/
public class LambdaRewriter {
- private static final String SERIALIZED_LAMBDA_TYPE_DESCR = "Ljava/lang/invoke/SerializedLambda;";
-
- private static final String DESERIALIZE_LAMBDA_METHOD_NAME = "$deserializeLambda$";
// Public for testing.
public static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
+ public static final String LAMBDA_GROUP_CLASS_NAME_PREFIX = "-$$LambdaGroup$";
static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
@@ -64,9 +62,6 @@
final DexString classConstructorName;
final DexString instanceFieldName;
- final DexString deserializeLambdaMethodName;
- final DexProto deserializeLambdaMethodProto;
-
final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
// Maps call sites seen so far to inferred lambda descriptor. It is intended
@@ -96,10 +91,6 @@
this.objectInitMethod = factory.createMethod(factory.objectType, initProto, constructorName);
this.classConstructorName = factory.createString(Constants.CLASS_INITIALIZER_NAME);
this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
-
- this.deserializeLambdaMethodName = factory.createString(DESERIALIZE_LAMBDA_METHOD_NAME);
- this.deserializeLambdaMethodProto = factory.createProto(
- factory.objectType, factory.createType(SERIALIZED_LAMBDA_TYPE_DESCR));
}
/**
@@ -136,7 +127,8 @@
}
/** Remove lambda deserialization methods. */
- public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
+ public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
+ boolean anyRemoved = false;
for (DexProgramClass clazz : classes) {
// Search for a lambda deserialization method and remove it if found.
DexEncodedMethod[] directMethods = clazz.directMethods();
@@ -145,8 +137,7 @@
for (int i = 0; i < methodCount; i++) {
DexEncodedMethod encoded = directMethods[i];
DexMethod method = encoded.method;
- if (method.name == deserializeLambdaMethodName &&
- method.proto == deserializeLambdaMethodProto) {
+ if (method.isLambdaDeserializeMethod(appInfo.dexItemFactory)) {
assert encoded.accessFlags.isStatic();
assert encoded.accessFlags.isSynthetic();
@@ -155,12 +146,15 @@
System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
clazz.setDirectMethods(newMethods);
+ anyRemoved = true;
+
// We assume there is only one such method in the class.
break;
}
}
}
}
+ return anyRemoved;
}
/**
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 e3257d5..a07410c 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
@@ -1889,7 +1889,7 @@
String name = null;
if (invokedMethod == appInfo.dexItemFactory.classMethods.getName) {
if (code.options.enableMinification
- && !converter.rootSet.noObfuscation.contains(holder)) {
+ && !converter.rootSet.noObfuscation.contains(holderType)) {
deferred = new DexItemBasedValueString(
holderType, new ClassNameComputationInfo(NAME));
} else {
@@ -1899,7 +1899,7 @@
// TODO(b/119426668): desugar Type#getTypeName
} else if (invokedMethod == appInfo.dexItemFactory.classMethods.getCanonicalName) {
if (code.options.enableMinification
- && !converter.rootSet.noObfuscation.contains(holder)) {
+ && !converter.rootSet.noObfuscation.contains(holderType)) {
deferred = new DexItemBasedValueString(
holderType, new ClassNameComputationInfo(CANONICAL_NAME));
} else {
@@ -1907,7 +1907,7 @@
}
} else if (invokedMethod == appInfo.dexItemFactory.classMethods.getSimpleName) {
if (code.options.enableMinification
- && !converter.rootSet.noObfuscation.contains(holder)) {
+ && !converter.rootSet.noObfuscation.contains(holderType)) {
deferred = new DexItemBasedValueString(
holderType, new ClassNameComputationInfo(SIMPLE_NAME));
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 41faa04..434bb04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -6,13 +6,14 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -56,12 +57,16 @@
this.appInfo = appInfo;
}
- private ProguardMemberRuleLookup lookupMemberRule(DexItem item) {
- ProguardMemberRule rule = appInfo.noSideEffects.get(item);
+ private ProguardMemberRuleLookup lookupMemberRule(DexDefinition definition) {
+ if (definition == null) {
+ return null;
+ }
+ DexReference reference = definition.toReference();
+ ProguardMemberRule rule = appInfo.noSideEffects.get(reference);
if (rule != null) {
return new ProguardMemberRuleLookup(RuleType.ASSUME_NO_SIDE_EFFECTS, rule);
}
- rule = appInfo.assumedValues.get(item);
+ rule = appInfo.assumedValues.get(reference);
if (rule != null) {
return new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 9cdb95c..bcc0af3 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -244,7 +244,7 @@
return classNaming.originalName + " " + memberNaming.signature.toString();
}
- public Signature originalSignatureOf(DexMethod method) {
+ public MethodSignature originalSignatureOf(DexMethod method) {
String decoded = descriptorToJavaType(method.holder.descriptor.toString());
MethodSignature memberSignature = getRenamedMethodSignature(method);
ClassNaming classNaming = getClassNaming(decoded);
@@ -255,7 +255,7 @@
if (memberNaming == null) {
return memberSignature;
}
- return memberNaming.signature;
+ return (MethodSignature) memberNaming.signature;
}
public FieldSignature originalSignatureOf(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index b02801c..e53f959 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -87,12 +87,10 @@
this.classDictionary = options.getProguardConfiguration().getClassObfuscationDictionary();
this.keepInnerClassStructure = options.getProguardConfiguration().getKeepAttributes().signature;
this.noObfuscationTypes =
- DexReference.filterDexType(
- DexDefinition.mapToReference(rootSet.noObfuscation.stream()))
+ DexReference.filterDexType(rootSet.noObfuscation.stream())
.collect(Collectors.toSet());
this.keepPackageName =
- DexReference.filterDexType(
- DexDefinition.mapToReference(rootSet.keepPackageName.stream()))
+ DexReference.filterDexType(rootSet.keepPackageName.stream())
.collect(Collectors.toSet());
// Initialize top-level naming state.
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index b589aa4..bbb3391 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -72,8 +72,8 @@
DexEncodedField encodedField,
NamingState<DexType, ?> state,
boolean isLibrary) {
- if (isLibrary || rootSet.noObfuscation.contains(encodedField)) {
- DexField field = encodedField.field;
+ DexField field = encodedField.field;
+ if (isLibrary || rootSet.noObfuscation.contains(field)) {
state.reserveName(field.name, field.type);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index df11ba9..5f990ac 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -479,11 +479,12 @@
return state;
}
- private void reserveNamesForMethod(DexEncodedMethod method,
- boolean keepAll, NamingState<DexProto, ?> state) {
+ private void reserveNamesForMethod(
+ DexEncodedMethod encodedMethod, boolean keepAll, NamingState<DexProto, ?> state) {
+ DexMethod method = encodedMethod.method;
if (keepAll || rootSet.noObfuscation.contains(method)) {
- state.reserveName(method.method.name, method.method.proto);
- globalState.reserveName(method.method.name, method.method.proto);
+ state.reserveName(method.name, method.proto);
+ globalState.reserveName(method.name, method.proto);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 6232fb3..d5add45 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -214,7 +214,7 @@
applyClassMappingOnTheFly(originalField.clazz),
applyClassMappingOnTheFly(originalField.type),
appInfo.dexItemFactory.createString(appliedSignature.name));
- lenseBuilder.map(originalField, appliedField);
+ lenseBuilder.move(originalField, appliedField);
}
private void applyMethodMapping(DexMethod originalMethod, MemberNaming memberNaming) {
@@ -224,7 +224,7 @@
applyClassMappingOnTheFly(originalMethod.holder),
applyClassMappingOnTheFly(originalMethod.proto),
appInfo.dexItemFactory.createString(appliedSignature.name));
- lenseBuilder.map(originalMethod, appliedMethod);
+ lenseBuilder.move(originalMethod, appliedMethod);
}
private DexType applyClassMappingOnTheFly(DexType from) {
@@ -336,7 +336,7 @@
if (newHolderType != appliedMethod.holder || newProto != appliedMethod.proto) {
newMethod = appInfo.dexItemFactory.createMethod(
substituteType(newHolderType, encodedMethod), newProto, appliedMethod.name);
- lenseBuilder.map(encodedMethod.method, newMethod);
+ lenseBuilder.move(encodedMethod.method, newMethod);
} else {
newMethod = appliedMethod;
}
@@ -359,7 +359,7 @@
if (newHolderType != appliedField.clazz || newFieldType != appliedField.type) {
newField = appInfo.dexItemFactory.createField(
substituteType(newHolderType, null), newFieldType, appliedField.name);
- lenseBuilder.map(encodedField.field, newField);
+ lenseBuilder.move(encodedField.field, newField);
} else {
newField = appliedField;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index debba04..59caf25 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -3,87 +3,89 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.Sets;
import java.util.Arrays;
-import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
public class VisibilityBridgeRemover {
- private final AppInfoWithSubtyping appInfo;
- private final DexApplication application;
- private final Set<DexEncodedMethod> unneededVisibilityBridges = Sets.newIdentityHashSet();
- public VisibilityBridgeRemover(AppInfoWithSubtyping appInfo, DexApplication application) {
- this.appInfo = appInfo;
- this.application = application;
+ private final AppView<? extends AppInfoWithLiveness> appView;
+
+ public VisibilityBridgeRemover(AppView<? extends AppInfoWithLiveness> appView) {
+ this.appView = appView;
}
- private void identifyBridgeMethod(DexEncodedMethod method) {
+ private void removeUnneededVisibilityBridgesFromClass(DexProgramClass clazz) {
+ clazz.setDirectMethods(removeUnneededVisibilityBridges(clazz.directMethods()));
+ clazz.setVirtualMethods(removeUnneededVisibilityBridges(clazz.virtualMethods()));
+ }
+
+ private DexEncodedMethod[] removeUnneededVisibilityBridges(DexEncodedMethod[] methods) {
+ Set<DexEncodedMethod> methodsToBeRemoved = null;
+ for (DexEncodedMethod method : methods) {
+ if (isUnneededVisibilityBridge(method)) {
+ if (methodsToBeRemoved == null) {
+ methodsToBeRemoved = Sets.newIdentityHashSet();
+ }
+ methodsToBeRemoved.add(method);
+ }
+ }
+ if (methodsToBeRemoved != null) {
+ Set<DexEncodedMethod> finalMethodsToBeRemoved = methodsToBeRemoved;
+ return Arrays.stream(methods)
+ .filter(method -> !finalMethodsToBeRemoved.contains(method))
+ .toArray(DexEncodedMethod[]::new);
+ }
+ return methods;
+ }
+
+ private boolean isUnneededVisibilityBridge(DexEncodedMethod method) {
+ if (appView.appInfo().isPinned(method.method)) {
+ return false;
+ }
MethodAccessFlags accessFlags = method.accessFlags;
- if (accessFlags.isBridge() && !accessFlags.isAbstract()) {
- InvokeSingleTargetExtractor targetExtractor =
- new InvokeSingleTargetExtractor(appInfo.dexItemFactory);
- method.getCode().registerCodeReferences(targetExtractor);
- DexMethod target = targetExtractor.getTarget();
- InvokeKind kind = targetExtractor.getKind();
- // javac-generated visibility forward bridge method has same descriptor (name, signature and
- // return type).
- if (target != null && target.hasSameProtoAndName(method.method)) {
- assert !accessFlags.isPrivate() && !accessFlags.isConstructor();
- if (kind == InvokeKind.SUPER) {
- // This is a visibility forward, so check for the direct target.
- DexEncodedMethod targetMethod =
- appInfo.resolveMethod(target.getHolder(), target).asSingleTarget();
- if (targetMethod != null && targetMethod.accessFlags.isPublic()) {
- if (Log.ENABLED) {
- Log.info(getClass(), "Removing visibility forwarding %s -> %s", method.method,
- targetMethod.method);
- }
- unneededVisibilityBridges.add(method);
+ if (!accessFlags.isBridge() || accessFlags.isAbstract()) {
+ return false;
+ }
+ InvokeSingleTargetExtractor targetExtractor =
+ new InvokeSingleTargetExtractor(appView.dexItemFactory());
+ method.getCode().registerCodeReferences(targetExtractor);
+ DexMethod target = targetExtractor.getTarget();
+ InvokeKind kind = targetExtractor.getKind();
+ // javac-generated visibility forward bridge method has same descriptor (name, signature and
+ // return type).
+ if (target != null && target.hasSameProtoAndName(method.method)) {
+ assert !accessFlags.isPrivate() && !accessFlags.isConstructor();
+ if (kind == InvokeKind.SUPER) {
+ // This is a visibility forward, so check for the direct target.
+ DexEncodedMethod targetMethod =
+ appView.appInfo().resolveMethod(target.getHolder(), target).asSingleTarget();
+ if (targetMethod != null && targetMethod.accessFlags.isPublic()) {
+ if (Log.ENABLED) {
+ Log.info(
+ getClass(),
+ "Removing visibility forwarding %s -> %s",
+ method.method,
+ targetMethod.method);
}
+ return true;
}
}
}
+ return false;
}
- private void removeUnneededVisibilityBridges() {
- Set<DexType> classes = unneededVisibilityBridges.stream()
- .map(method -> method.method.getHolder())
- .collect(Collectors.toSet());
- for (DexType type : classes) {
- DexClass clazz = appInfo.definitionFor(type);
- clazz.setVirtualMethods(removeMethods(clazz.virtualMethods(), unneededVisibilityBridges));
+ public void run() {
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ removeUnneededVisibilityBridgesFromClass(clazz);
}
}
-
- private DexEncodedMethod[] removeMethods(DexEncodedMethod[] methods,
- Set<DexEncodedMethod> removals) {
- assert methods != null;
- List<DexEncodedMethod> newMethods = Arrays.stream(methods)
- .filter(method -> !removals.contains(method))
- .collect(Collectors.toList());
- assert newMethods.size() < methods.length;
- return newMethods.toArray(new DexEncodedMethod[newMethods.size()]);
- }
-
- public DexApplication run() {
- for (DexClass clazz : appInfo.classes()) {
- clazz.forEachMethod(this::identifyBridgeMethod);
- }
- if (!unneededVisibilityBridges.isEmpty()) {
- removeUnneededVisibilityBridges();
- }
- return application;
- }
-
}
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index b28a1a7..07a62a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
@@ -19,7 +20,7 @@
public class DiscardedChecker {
- private final Set<DexDefinition> checkDiscarded;
+ private final Set<DexReference> checkDiscarded;
private final List<DexProgramClass> classes;
private boolean fail = false;
private final InternalOptions options;
@@ -55,7 +56,7 @@
}
private void checkItem(DexDefinition item) {
- if (checkDiscarded.contains(item)) {
+ if (checkDiscarded.contains(item.toReference())) {
options.reporter.info(
new StringDiagnostic("Item " + item.toSourceString() + " was not discarded."));
fail = true;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 0761a71..55ceee8 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
@@ -63,7 +64,6 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
@@ -193,6 +193,10 @@
*/
private final Set<DexMethod> methodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
/**
+ * Set of direct lambda methods that are the immediate target of an invoke-dynamic.
+ */
+ private final Set<DexMethod> lambdaMethodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
+ /**
* Set of virtual methods that are the immediate target of an invoke-direct.
* */
private final Set<DexMethod> virtualMethodsTargetedByInvokeDirect = Sets.newIdentityHashSet();
@@ -237,15 +241,15 @@
private final Set<DexMethod> virtualTargetsMarkedAsReachable = Sets.newIdentityHashSet();
/**
- * A set of dexitems we have reported missing to dedupe warnings.
+ * A set of references we have reported missing to dedupe warnings.
*/
private final Set<DexReference> reportedMissing = Sets.newIdentityHashSet();
/**
- * A set of items that we are keeping due to keep rules. This may differ from the rootSet due to
- * dependent keep rules.
+ * A set of references that we are keeping due to keep rules. This may differ from the root set
+ * due to dependent keep rules.
*/
- private final Set<DexDefinition> pinnedItems = Sets.newIdentityHashSet();
+ private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet();
/**
* A map from classes to annotations that need to be processed should the classes ever become
@@ -297,22 +301,24 @@
this.options = options;
}
- private void enqueueRootItems(Map<DexDefinition, Set<ProguardKeepRule>> items) {
+ private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRule>> items) {
items.entrySet().forEach(this::enqueueRootItem);
}
- private void enqueueRootItem(Entry<DexDefinition, Set<ProguardKeepRule>> root) {
- enqueueRootItem(root.getKey(), root.getValue());
- }
-
- private void enqueueRootItem(DexDefinition item, ProguardKeepRule rule) {
- enqueueRootItem(item, KeepReason.dueToKeepRule(rule));
+ private void enqueueRootItem(Entry<DexReference, Set<ProguardKeepRule>> root) {
+ DexDefinition item = appInfo.definitionFor(root.getKey());
+ if (item != null) {
+ enqueueRootItem(item, root.getValue());
+ } else {
+ // TODO(b/123923324): Verify that root items are present.
+ // assert false : "Expected root item `" + root.getKey().toSourceString() + "` to be present";
+ }
}
private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRule> rules) {
assert !rules.isEmpty();
if (keptGraphConsumer != null) {
- GraphNode node = getGraphNode(item);
+ GraphNode node = getGraphNode(item.toReference());
for (ProguardKeepRule rule : rules) {
registerEdge(node, KeepReason.dueToKeepRule(rule));
}
@@ -322,7 +328,7 @@
private void enqueueRootItem(DexDefinition item, KeepReason reason) {
if (keptGraphConsumer != null) {
- registerEdge(getGraphNode(item), reason);
+ registerEdge(getGraphNode(item.toReference()), reason);
}
internalEnqueueRootItem(item, reason);
}
@@ -352,7 +358,7 @@
} else {
throw new IllegalArgumentException(item.toString());
}
- pinnedItems.add(item);
+ pinnedItems.add(item.toReference());
}
private void enqueueFirstNonSerializableClassInitializer(DexClass clazz, KeepReason reason) {
@@ -368,16 +374,17 @@
}
private void enqueueHolderIfDependentNonStaticMember(
- DexClass holder, Map<DexDefinition, Set<ProguardKeepRule>> dependentItems) {
+ DexClass holder, Map<DexReference, Set<ProguardKeepRule>> dependentItems) {
// Check if any dependent members are not static, and in that case enqueue the class as well.
// Having a dependent rule like -keepclassmembers with non static items indicates that class
// instances will be present even if tracing do not find any instantiation. See b/115867670.
- for (Entry<DexDefinition, Set<ProguardKeepRule>> entry : dependentItems.entrySet()) {
- DexDefinition dependentItem = entry.getKey();
- if (dependentItem.isDexClass()) {
+ for (Entry<DexReference, Set<ProguardKeepRule>> entry : dependentItems.entrySet()) {
+ DexReference dependentItem = entry.getKey();
+ if (dependentItem.isDexType()) {
continue;
}
- if (!dependentItem.isStaticMember()) {
+ DexDefinition dependentDefinition = appInfo.definitionFor(dependentItem);
+ if (!dependentDefinition.isStaticMember()) {
enqueueRootItem(holder, entry.getValue());
// Enough to enqueue the known holder once.
break;
@@ -648,6 +655,10 @@
assert implHandle != null;
DexMethod method = implHandle.asMethod();
+ if (descriptor.delegatesToLambdaImplMethod()) {
+ lambdaMethodsTargetedByInvokeDynamic.add(method);
+ }
+
if (!methodsTargetedByInvokeDynamic.add(method)) {
return;
}
@@ -810,7 +821,7 @@
annotations.forEach(this::handleAnnotationOfLiveType);
}
- Map<DexDefinition, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
+ Map<DexReference, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
enqueueHolderIfDependentNonStaticMember(holder, dependentItems);
// Add all dependent members to the workqueue.
enqueueRootItems(dependentItems);
@@ -1325,7 +1336,7 @@
// TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
// marking of not renaming is in the root set.
enqueueRootItem(valuesMethod, reason);
- rootSet.noObfuscation.add(valuesMethod);
+ rootSet.noObfuscation.add(valuesMethod.toReference());
}
}
@@ -1465,17 +1476,14 @@
targetedMethods.getItems(),
executorService);
ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
+ rootSet.addConsequentRootSet(consequentRootSet);
enqueueRootItems(consequentRootSet.noShrinking);
- rootSet.neverInline.addAll(consequentRootSet.neverInline);
- rootSet.neverClassInline.addAll(consequentRootSet.neverClassInline);
- rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
- rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
- rootSet.addDependentItems(consequentRootSet.dependentNoShrinking);
// Check if any newly dependent members are not static, and in that case find the holder
// and enqueue it as well. This is -if version of workaround for b/115867670.
consequentRootSet.dependentNoShrinking.forEach((precondition, dependentItems) -> {
- if (precondition.isDexClass()) {
- enqueueHolderIfDependentNonStaticMember(precondition.asDexClass(), dependentItems);
+ if (precondition.isDexType()) {
+ DexClass preconditionHolder = appInfo.definitionFor(precondition.asDexType());
+ enqueueHolderIfDependentNonStaticMember(preconditionHolder, dependentItems);
}
// Add all dependent members to the workqueue.
enqueueRootItems(dependentItems);
@@ -1520,9 +1528,18 @@
} finally {
timing.end();
}
+ unpinLambdaMethods();
return new AppInfoWithLiveness(appInfo, this);
}
+ private void unpinLambdaMethods() {
+ for (DexMethod method : lambdaMethodsTargetedByInvokeDynamic) {
+ pinnedItems.remove(method);
+ rootSet.prune(method);
+ }
+ lambdaMethodsTargetedByInvokeDynamic.clear();
+ }
+
private void markMethodAsKept(DexEncodedMethod target, KeepReason reason) {
DexClass holder = appInfo.definitionFor(target.method.holder);
// If this method no longer has a corresponding class then we have shaken it away before.
@@ -1948,11 +1965,11 @@
/**
* All items with assumenosideeffects rule.
*/
- public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
+ public final Map<DexReference, ProguardMemberRule> noSideEffects;
/**
* All items with assumevalues rule.
*/
- public final Map<DexDefinition, ProguardMemberRule> assumedValues;
+ public final Map<DexReference, ProguardMemberRule> assumedValues;
/**
* All methods that should be inlined if possible due to a configuration directive.
*/
@@ -1965,9 +1982,13 @@
* All methods that *must* never be inlined due to a configuration directive (testing only).
*/
public final Set<DexMethod> neverInline;
- /** All methods that may not have any parameters with a constant value removed. */
+ /**
+ * All methods that may not have any parameters with a constant value removed.
+ */
public final Set<DexMethod> keepConstantArguments;
- /** All methods that may not have any unused arguments removed. */
+ /**
+ * All methods that may not have any unused arguments removed.
+ */
public final Set<DexMethod> keepUnusedArguments;
/**
* All types that *must* never be inlined due to a configuration directive (testing only).
@@ -2028,8 +2049,7 @@
instanceFieldReads.keySet(), staticFieldReads.keySet());
this.fieldsWritten = enqueuer.mergeFieldAccesses(
instanceFieldWrites.keySet(), staticFieldWrites.keySet());
- this.pinnedItems =
- DexDefinition.mapToReference(enqueuer.pinnedItems.stream()).collect(Collectors.toSet());
+ this.pinnedItems = enqueuer.pinnedItems;
this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes);
this.interfaceInvokes = joinInvokedMethods(enqueuer.interfaceInvokes);
this.superInvokes = joinInvokedMethods(enqueuer.superInvokes, TargetWithContext::getTarget);
@@ -2143,16 +2163,14 @@
this.callSites = previous.callSites;
this.brokenSuperInvokes = lense.rewriteMethodsConservatively(previous.brokenSuperInvokes);
this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
- assert lense.assertDefinitionsNotModified(previous.noSideEffects.keySet());
- this.noSideEffects = previous.noSideEffects;
- assert lense.assertDefinitionsNotModified(previous.assumedValues.keySet());
- this.assumedValues = previous.assumedValues;
+ this.noSideEffects = rewriteReferenceKeys(previous.noSideEffects, lense::lookupReference);
+ this.assumedValues = rewriteReferenceKeys(previous.assumedValues, lense::lookupReference);
assert lense.assertDefinitionsNotModified(
previous.alwaysInline.stream()
.map(this::definitionFor)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
- this.alwaysInline = previous.alwaysInline;
+ this.alwaysInline = lense.rewriteMethodsWithRenamedSignature(previous.alwaysInline);
this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
this.keepConstantArguments =
@@ -2165,7 +2183,7 @@
.filter(Objects::nonNull)
.collect(Collectors.toList()));
this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
- this.neverMerge = previous.neverMerge;
+ this.neverMerge = rewriteItems(previous.neverMerge, lense::lookupType);
this.identifierNameStrings =
lense.rewriteReferencesConservatively(previous.identifierNameStrings);
// Switchmap classes should never be affected by renaming.
@@ -2174,8 +2192,8 @@
.map(this::definitionFor)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
- this.switchMaps = previous.switchMaps;
- this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
+ this.switchMaps = rewriteReferenceKeys(previous.switchMaps, lense::lookupField);
+ this.ordinalsMaps = rewriteReferenceKeys(previous.ordinalsMaps, lense::lookupType);
// Sanity check sets after rewriting.
assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
@@ -2318,14 +2336,6 @@
return builder.build();
}
- private static <T extends PresortedComparable<T>, S> ImmutableMap<T, S> rewriteKeys(
- Map<T, S> original, Function<T, T> rewrite) {
- ImmutableMap.Builder<T, S> builder = new ImmutableMap.Builder<>();
- for (T item : original.keySet()) {
- builder.put(rewrite.apply(item), original.get(item));
- }
- return builder.build();
- }
private static <T extends PresortedComparable<T>, S>
Map<T, Set<S>> rewriteKeysWhileMergingValues(
@@ -2820,15 +2830,15 @@
return reason.getSourceNode(this);
}
- public GraphNode getGraphNode(DexDefinition item) {
- if (item instanceof DexClass) {
- return getClassGraphNode(((DexClass) item).type);
+ public GraphNode getGraphNode(DexReference reference) {
+ if (reference.isDexType()) {
+ return getClassGraphNode(reference.asDexType());
}
- if (item instanceof DexEncodedMethod) {
- return getMethodGraphNode(((DexEncodedMethod) item).method);
+ if (reference.isDexMethod()) {
+ return getMethodGraphNode(reference.asDexMethod());
}
- if (item instanceof DexEncodedField) {
- return getFieldGraphNode(((DexEncodedField) item).field);
+ if (reference.isDexField()) {
+ return getFieldGraphNode(reference.asDexField());
}
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 86b29a8..f3ac326 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -273,6 +273,19 @@
return false;
}
+ public boolean isSpecific() {
+ switch (getRuleType()) {
+ case ALL:
+ // fall through
+ case ALL_FIELDS:
+ // fall through
+ case ALL_METHODS:
+ return false;
+ default:
+ return Iterables.size(getWildcards()) == 0;
+ }
+ }
+
Iterable<ProguardWildcard> getWildcards() {
return Iterables.concat(
ProguardTypeMatcher.getWildcardsOrEmpty(annotation),
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 2a8ce8f..f94954b 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.GraphLense.rewriteMutableReferenceKeys;
+import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
+
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -22,11 +25,11 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.base.Equivalence.Wrapper;
@@ -53,6 +56,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -61,14 +65,14 @@
private final AppView<? extends AppInfo> appView;
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
- private final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking = new IdentityHashMap<>();
- private final Set<DexDefinition> noOptimization = Sets.newIdentityHashSet();
- private final Set<DexDefinition> noObfuscation = Sets.newIdentityHashSet();
- private final LinkedHashMap<DexDefinition, DexDefinition> reasonAsked = new LinkedHashMap<>();
- private final Set<DexDefinition> keepPackageName = Sets.newIdentityHashSet();
+ private final Map<DexReference, Set<ProguardKeepRule>> noShrinking = new IdentityHashMap<>();
+ private final Set<DexReference> noOptimization = Sets.newIdentityHashSet();
+ private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
+ private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
+ private final Set<DexReference> keepPackageName = Sets.newIdentityHashSet();
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
- private final Set<DexDefinition> checkDiscarded = Sets.newIdentityHashSet();
+ private final Set<DexReference> checkDiscarded = Sets.newIdentityHashSet();
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
@@ -76,10 +80,10 @@
private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
- private final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking =
+ private final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking =
new IdentityHashMap<>();
- private final Map<DexDefinition, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
- private final Map<DexDefinition, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
+ private final Map<DexReference, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
+ private final Map<DexReference, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
private final InternalOptions options;
@@ -863,11 +867,11 @@
}
// Keep the type if the item is also kept.
dependentNoShrinking
- .computeIfAbsent(item, x -> new IdentityHashMap<>())
- .computeIfAbsent(definition, k -> new HashSet<>())
+ .computeIfAbsent(item.toReference(), x -> new IdentityHashMap<>())
+ .computeIfAbsent(type, k -> new HashSet<>())
.add(context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
- noObfuscation.add(definition);
+ noObfuscation.add(type);
}
private void includeDescriptorClasses(DexDefinition item, ProguardKeepRule context) {
@@ -891,9 +895,28 @@
ProguardMemberRule rule,
DexDefinition precondition) {
if (context instanceof ProguardKeepRule) {
- if (item.isDexEncodedMethod() && item.asDexEncodedMethod().accessFlags.isSynthetic()) {
- // Don't keep synthetic methods (see b/120971047 for additional details).
- return;
+ if (item.isDexEncodedMethod()) {
+ DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
+ if (encodedMethod.method.isLambdaDeserializeMethod(appView.dexItemFactory())) {
+ // Don't keep lambda deserialization methods.
+ return;
+ }
+ // If desugaring is enabled, private and static interface methods will be moved to a
+ // companion class. So we don't need to add them to the root set in the beginning.
+ if (options.isInterfaceMethodDesugaringEnabled()
+ && encodedMethod.hasCode()
+ && (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) {
+ DexClass holder = appView.appInfo().definitionFor(encodedMethod.method.getHolder());
+ if (holder != null && holder.isInterface()) {
+ if (rule.isSpecific()) {
+ options.reporter.warning(
+ new StringDiagnostic(
+ "The rule `" + rule + "` is ignored because the targeting interface method `"
+ + encodedMethod.method.toSourceString() + "` will be desugared."));
+ }
+ return;
+ }
+ }
}
ProguardKeepRule keepRule = (ProguardKeepRule) context;
@@ -901,32 +924,32 @@
if (!modifiers.allowsShrinking) {
if (precondition != null) {
dependentNoShrinking
- .computeIfAbsent(precondition, x -> new IdentityHashMap<>())
- .computeIfAbsent(item, i -> new HashSet<>())
+ .computeIfAbsent(precondition.toReference(), x -> new IdentityHashMap<>())
+ .computeIfAbsent(item.toReference(), i -> new HashSet<>())
.add(keepRule);
} else {
- noShrinking.computeIfAbsent(item, i -> new HashSet<>()).add(keepRule);
+ noShrinking.computeIfAbsent(item.toReference(), i -> new HashSet<>()).add(keepRule);
}
}
if (!modifiers.allowsOptimization) {
- noOptimization.add(item);
+ noOptimization.add(item.toReference());
}
if (!modifiers.allowsObfuscation) {
- noObfuscation.add(item);
+ noObfuscation.add(item.toReference());
}
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
}
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
- noSideEffects.put(item, rule);
+ noSideEffects.put(item.toReference(), rule);
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
- reasonAsked.computeIfAbsent(item, i -> i);
+ reasonAsked.computeIfAbsent(item.toReference(), i -> i);
} else if (context instanceof ProguardKeepPackageNamesRule) {
- keepPackageName.add(item);
+ keepPackageName.add(item.toReference());
} else if (context instanceof ProguardAssumeValuesRule) {
- assumedValues.put(item, rule);
+ assumedValues.put(item.toReference(), rule);
} else if (context instanceof ProguardCheckDiscardRule) {
- checkDiscarded.add(item);
+ checkDiscarded.add(item.toReference());
} else if (context instanceof InlineRule) {
if (item.isDexEncodedMethod()) {
switch (((InlineRule) context).getType()) {
@@ -982,12 +1005,12 @@
public static class RootSet {
- public final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking;
- public final Set<DexDefinition> noOptimization;
- public final Set<DexDefinition> noObfuscation;
- public final ImmutableList<DexDefinition> reasonAsked;
- public final Set<DexDefinition> keepPackageName;
- public final Set<DexDefinition> checkDiscarded;
+ public final Map<DexReference, Set<ProguardKeepRule>> noShrinking;
+ public final Set<DexReference> noOptimization;
+ public final Set<DexReference> noObfuscation;
+ public final ImmutableList<DexReference> reasonAsked;
+ public final Set<DexReference> keepPackageName;
+ public final Set<DexReference> checkDiscarded;
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
public final Set<DexMethod> neverInline;
@@ -995,20 +1018,20 @@
public final Set<DexMethod> keepUnusedArguments;
public final Set<DexType> neverClassInline;
public final Set<DexType> neverMerge;
- public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
- public final Map<DexDefinition, ProguardMemberRule> assumedValues;
- private final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>>
+ public final Map<DexReference, ProguardMemberRule> noSideEffects;
+ public final Map<DexReference, ProguardMemberRule> assumedValues;
+ private final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>>
dependentNoShrinking;
public final Set<DexReference> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
private RootSet(
- Map<DexDefinition, Set<ProguardKeepRule>> noShrinking,
- Set<DexDefinition> noOptimization,
- Set<DexDefinition> noObfuscation,
- ImmutableList<DexDefinition> reasonAsked,
- Set<DexDefinition> keepPackageName,
- Set<DexDefinition> checkDiscarded,
+ Map<DexReference, Set<ProguardKeepRule>> noShrinking,
+ Set<DexReference> noOptimization,
+ Set<DexReference> noObfuscation,
+ ImmutableList<DexReference> reasonAsked,
+ Set<DexReference> keepPackageName,
+ Set<DexReference> checkDiscarded,
Set<DexMethod> alwaysInline,
Set<DexMethod> forceInline,
Set<DexMethod> neverInline,
@@ -1016,12 +1039,12 @@
Set<DexMethod> keepUnusedArguments,
Set<DexType> neverClassInline,
Set<DexType> neverMerge,
- Map<DexDefinition, ProguardMemberRule> noSideEffects,
- Map<DexDefinition, ProguardMemberRule> assumedValues,
- Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking,
+ Map<DexReference, ProguardMemberRule> noSideEffects,
+ Map<DexReference, ProguardMemberRule> assumedValues,
+ Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking,
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules) {
- this.noShrinking = Collections.unmodifiableMap(noShrinking);
+ this.noShrinking = noShrinking;
this.noOptimization = noOptimization;
this.noObfuscation = noObfuscation;
this.reasonAsked = reasonAsked;
@@ -1034,40 +1057,121 @@
this.keepUnusedArguments = keepUnusedArguments;
this.neverClassInline = neverClassInline;
this.neverMerge = Collections.unmodifiableSet(neverMerge);
- this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
- this.assumedValues = Collections.unmodifiableMap(assumedValues);
+ this.noSideEffects = noSideEffects;
+ this.assumedValues = assumedValues;
this.dependentNoShrinking = dependentNoShrinking;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
}
+ private RootSet(RootSet previous, GraphLense lense) {
+ this.noShrinking = rewriteMutableReferenceKeys(previous.noShrinking, lense::lookupReference);
+ this.noOptimization = lense.rewriteMutableReferencesConservatively(previous.noOptimization);
+ this.noObfuscation = lense.rewriteMutableReferencesConservatively(previous.noObfuscation);
+ this.reasonAsked = lense.rewriteReferencesConservatively(previous.reasonAsked);
+ this.keepPackageName = lense.rewriteReferencesConservatively(previous.keepPackageName);
+ this.checkDiscarded = lense.rewriteReferencesConservatively(previous.checkDiscarded);
+ this.alwaysInline = lense.rewriteMethodsConservatively(previous.alwaysInline);
+ this.forceInline = lense.rewriteMethodsConservatively(previous.forceInline);
+ this.neverInline = lense.rewriteMutableMethodsConservatively(previous.neverInline);
+ this.keepConstantArguments =
+ lense.rewriteMutableMethodsConservatively(previous.keepConstantArguments);
+ this.keepUnusedArguments =
+ lense.rewriteMutableMethodsConservatively(previous.keepUnusedArguments);
+ this.neverClassInline = lense.rewriteMutableTypesConservatively(previous.neverClassInline);
+ this.neverMerge = lense.rewriteTypesConservatively(previous.neverMerge);
+ this.noSideEffects =
+ rewriteMutableReferenceKeys(previous.noSideEffects, lense::lookupReference);
+ this.assumedValues =
+ rewriteMutableReferenceKeys(previous.assumedValues, lense::lookupReference);
+ this.dependentNoShrinking =
+ rewriteDependentReferenceKeys(previous.dependentNoShrinking, lense::lookupReference);
+ this.identifierNameStrings =
+ lense.rewriteReferencesConservatively(previous.identifierNameStrings);
+ this.ifRules = Collections.unmodifiableSet(previous.ifRules);
+ }
+
+ public RootSet rewrittenWithLense(GraphLense lense) {
+ return new RootSet(this, lense);
+ }
+
+ private static <T extends DexReference, S> Map<T, Map<T, S>> rewriteDependentReferenceKeys(
+ Map<T, Map<T, S>> original, Function<T, T> rewrite) {
+ Map<T, Map<T, S>> result = new IdentityHashMap<>();
+ for (T item : original.keySet()) {
+ result.put(rewrite.apply(item), rewriteReferenceKeys(original.get(item), rewrite));
+ }
+ return result;
+ }
+
+ void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
+ neverInline.addAll(consequentRootSet.neverInline);
+ neverClassInline.addAll(consequentRootSet.neverClassInline);
+ noOptimization.addAll(consequentRootSet.noOptimization);
+ noObfuscation.addAll(consequentRootSet.noObfuscation);
+ addDependentItems(consequentRootSet.dependentNoShrinking);
+ }
+
// Add dependent items that depend on -if rules.
- void addDependentItems(
- Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentItems) {
+ private void addDependentItems(
+ Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentItems) {
dependentItems.forEach(
- (def, dependence) ->
+ (reference, dependence) ->
dependentNoShrinking
- .computeIfAbsent(def, x -> new IdentityHashMap<>())
+ .computeIfAbsent(reference, x -> new IdentityHashMap<>())
.putAll(dependence));
}
- Map<DexDefinition, Set<ProguardKeepRule>> getDependentItems(DexDefinition item) {
- return Collections
- .unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
+ Map<DexReference, Set<ProguardKeepRule>> getDependentItems(DexDefinition item) {
+ return Collections.unmodifiableMap(
+ dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
+ }
+
+ public void copy(DexReference original, DexReference rewritten) {
+ if (noShrinking.containsKey(original)) {
+ noShrinking.put(rewritten, noShrinking.get(original));
+ }
+ if (noOptimization.contains(original)) {
+ noOptimization.add(rewritten);
+ }
+ if (noObfuscation.contains(original)) {
+ noObfuscation.add(rewritten);
+ }
+ if (noSideEffects.containsKey(original)) {
+ noSideEffects.put(rewritten, noSideEffects.get(original));
+ }
+ if (assumedValues.containsKey(original)) {
+ assumedValues.put(rewritten, assumedValues.get(original));
+ }
+ }
+
+ public void prune(DexReference reference) {
+ noShrinking.remove(reference);
+ noOptimization.remove(reference);
+ noObfuscation.remove(reference);
+ noSideEffects.remove(reference);
+ assumedValues.remove(reference);
+ }
+
+ public void move(DexReference original, DexReference rewritten) {
+ copy(original, rewritten);
+ prune(original);
}
public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
- for (DexDefinition definition : noShrinking.keySet()) {
- if (definition.isDexEncodedField()) {
- DexEncodedField field = definition.asDexEncodedField();
- if (field.isStatic() || isKeptDirectlyOrIndirectly(field.field.clazz, appInfo)) {
+ for (DexReference reference : noShrinking.keySet()) {
+ if (reference.isDexField()) {
+ DexField field = reference.asDexField();
+ DexEncodedField encodedField = appInfo.definitionFor(field);
+ if (encodedField != null
+ && (encodedField.isStatic() || isKeptDirectlyOrIndirectly(field.clazz, appInfo))) {
// TODO(b/121354886): Enable asserts for reads and writes.
- /*assert appInfo.fieldsRead.contains(field.field)
- : "Expected kept field `" + field.field.toSourceString() + "` to be read";
- assert appInfo.fieldsWritten.contains(field.field)
- : "Expected kept field `" + field.field.toSourceString() + "` to be written";*/
- assert appInfo.liveFields.contains(field.field)
- : "Expected kept field `" + field.field.toSourceString() + "` to be live";
+ /*assert appInfo.fieldsRead.contains(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be read";
+ assert appInfo.fieldsWritten.contains(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be written";*/
+ assert appInfo.liveFields.contains(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be live";
}
}
}
@@ -1075,17 +1179,17 @@
}
public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
- for (DexDefinition definition : noShrinking.keySet()) {
- if (definition.isDexEncodedMethod()) {
- DexEncodedMethod method = definition.asDexEncodedMethod();
- assert appInfo.targetedMethods.contains(method.method)
- : "Expected kept method `" + method.method.toSourceString() + "` to be targeted";
-
- if (!method.accessFlags.isAbstract()
- && isKeptDirectlyOrIndirectly(method.method.holder, appInfo)) {
- assert appInfo.liveMethods.contains(method.method)
+ for (DexReference reference : noShrinking.keySet()) {
+ if (reference.isDexMethod()) {
+ DexMethod method = reference.asDexMethod();
+ assert appInfo.targetedMethods.contains(method)
+ : "Expected kept method `" + method.toSourceString() + "` to be targeted";
+ DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
+ if (!encodedMethod.accessFlags.isAbstract()
+ && isKeptDirectlyOrIndirectly(method.holder, appInfo)) {
+ assert appInfo.liveMethods.contains(method)
: "Expected non-abstract kept method `"
- + method.method.toSourceString()
+ + method.toSourceString()
+ "` to be live";
}
}
@@ -1094,73 +1198,63 @@
}
public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
- for (DexDefinition definition : noShrinking.keySet()) {
- if (definition.isDexClass()) {
- DexClass clazz = definition.asDexClass();
- assert appInfo.liveTypes.contains(clazz.type)
- : "Expected kept type `" + clazz.type.toSourceString() + "` to be live";
+ for (DexReference reference : noShrinking.keySet()) {
+ if (reference.isDexType()) {
+ DexType type = reference.asDexType();
+ assert appInfo.liveTypes.contains(type)
+ : "Expected kept type `" + type.toSourceString() + "` to be live";
}
}
return true;
}
private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
+ if (noShrinking.containsKey(type)) {
+ return true;
+ }
DexClass clazz = appInfo.definitionFor(type);
if (clazz == null) {
return false;
}
- if (noShrinking.containsKey(clazz)) {
- return true;
- }
if (clazz.superType != null) {
return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
}
return false;
}
- public boolean verifyKeptItemsAreKept(
- DexApplication application, AppInfo appInfo, InternalOptions options) {
- if (options.getProguardConfiguration().hasApplyMappingFile()) {
- // TODO(b/121295633): Root set is obsolete after mapping has been applied.
- return true;
- }
-
- boolean isInterfaceMethodDesugaringEnabled =
- options.enableDesugaring
- && options.interfaceMethodDesugaring == OffOrAuto.Auto
- && !options.canUseDefaultAndStaticInterfaceMethods();
-
+ public boolean verifyKeptItemsAreKept(DexApplication application, AppInfo appInfo) {
Set<DexReference> pinnedItems =
appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
// Create a mapping from each required type to the set of required members on that type.
- Map<DexType, Set<DexDefinition>> requiredDefinitionsPerType = new IdentityHashMap<>();
- for (DexDefinition definition : noShrinking.keySet()) {
+ Map<DexType, Set<DexReference>> requiredReferencesPerType = new IdentityHashMap<>();
+ for (DexReference reference : noShrinking.keySet()) {
// Check that `pinnedItems` is a super set of the root set.
- assert pinnedItems == null || pinnedItems.contains(definition.toReference());
- if (definition.isDexClass()) {
- DexType type = definition.toReference().asDexType();
- requiredDefinitionsPerType.putIfAbsent(type, Sets.newIdentityHashSet());
+ assert pinnedItems == null || pinnedItems.contains(reference)
+ : "Expected reference `" + reference.toSourceString() + "` to be pinned";
+ if (reference.isDexType()) {
+ DexType type = reference.asDexType();
+ requiredReferencesPerType.putIfAbsent(type, Sets.newIdentityHashSet());
} else {
- assert definition.isDexEncodedField() || definition.isDexEncodedMethod();
- Descriptor<?, ?> descriptor = definition.toReference().asDescriptor();
- requiredDefinitionsPerType
+ assert reference.isDexField() || reference.isDexMethod();
+ Descriptor<?, ?> descriptor = reference.asDescriptor();
+ requiredReferencesPerType
.computeIfAbsent(descriptor.getHolder(), key -> Sets.newIdentityHashSet())
- .add(definition);
+ .add(reference);
}
}
// Run through each class in the program and check that it has members it must have.
for (DexProgramClass clazz : application.classes()) {
- Set<DexDefinition> requiredDefinitions =
- requiredDefinitionsPerType.getOrDefault(clazz.type, ImmutableSet.of());
+ Set<DexReference> requiredReferences =
+ requiredReferencesPerType.getOrDefault(clazz.type, ImmutableSet.of());
Set<DexField> fields = null;
Set<DexMethod> methods = null;
- for (DexDefinition requiredDefinition : requiredDefinitions) {
- if (requiredDefinition.isDexEncodedField()) {
- DexEncodedField requiredField = requiredDefinition.asDexEncodedField();
+ for (DexReference requiredReference : requiredReferences) {
+ if (requiredReference.isDexField()) {
+ DexField requiredField = requiredReference.asDexField();
if (fields == null) {
// Create a Set of the fields to avoid quadratic behavior.
fields =
@@ -1168,16 +1262,12 @@
.map(DexEncodedField::getKey)
.collect(Collectors.toSet());
}
- assert fields.contains(requiredField.field);
- } else if (requiredDefinition.isDexEncodedMethod()) {
- DexEncodedMethod requiredMethod = requiredDefinition.asDexEncodedMethod();
- if (isInterfaceMethodDesugaringEnabled) {
- if (clazz.isInterface() && requiredMethod.hasCode()) {
- // TODO(b/121240523): Ideally, these default interface methods should not be in the
- // root set.
- continue;
- }
- }
+ assert fields.contains(requiredField)
+ : "Expected field `"
+ + requiredField.toSourceString()
+ + "` from the root set to be present";
+ } else if (requiredReference.isDexMethod()) {
+ DexMethod requiredMethod = requiredReference.asDexMethod();
if (methods == null) {
// Create a Set of the methods to avoid quadratic behavior.
methods =
@@ -1185,21 +1275,24 @@
.map(DexEncodedMethod::getKey)
.collect(Collectors.toSet());
}
- assert methods.contains(requiredMethod.method);
+ assert methods.contains(requiredMethod)
+ : "Expected method `"
+ + requiredMethod.toSourceString()
+ + "` from the root set to be present";
} else {
assert false;
}
}
- requiredDefinitionsPerType.remove(clazz.type);
+ requiredReferencesPerType.remove(clazz.type);
}
// If the map is non-empty, then a type in the root set was not in the application.
- if (!requiredDefinitionsPerType.isEmpty()) {
- DexType type = requiredDefinitionsPerType.keySet().iterator().next();
+ if (!requiredReferencesPerType.isEmpty()) {
+ DexType type = requiredReferencesPerType.keySet().iterator().next();
DexClass clazz = application.definitionFor(type);
assert clazz == null || clazz.isProgramClass()
: "Unexpected library type in root set: `" + type + "`";
- assert requiredDefinitionsPerType.isEmpty()
+ assert requiredReferencesPerType.isEmpty()
: "Expected type `" + type.toSourceString() + "` to be present";
}
@@ -1225,7 +1318,7 @@
builder.append("\n\nNo Shrinking:");
noShrinking.keySet().stream()
- .sorted(Comparator.comparing(DexDefinition::toSourceString))
+ .sorted(Comparator.comparing(DexReference::toSourceString))
.forEach(a -> builder
.append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a)));
builder.append("\n");
@@ -1237,18 +1330,18 @@
static class ConsequentRootSet {
final Set<DexMethod> neverInline;
final Set<DexType> neverClassInline;
- final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking;
- final Set<DexDefinition> noOptimization;
- final Set<DexDefinition> noObfuscation;
- final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking;
+ final Map<DexReference, Set<ProguardKeepRule>> noShrinking;
+ final Set<DexReference> noOptimization;
+ final Set<DexReference> noObfuscation;
+ final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking;
private ConsequentRootSet(
Set<DexMethod> neverInline,
Set<DexType> neverClassInline,
- Map<DexDefinition, Set<ProguardKeepRule>> noShrinking,
- Set<DexDefinition> noOptimization,
- Set<DexDefinition> noObfuscation,
- Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking) {
+ Map<DexReference, Set<ProguardKeepRule>> noShrinking,
+ Set<DexReference> noOptimization,
+ Set<DexReference> noObfuscation,
+ Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking) {
this.neverInline = Collections.unmodifiableSet(neverInline);
this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
this.noShrinking = Collections.unmodifiableMap(noShrinking);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 7edd536..145f24b 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -272,9 +271,7 @@
// TODO(christofferqa): Remove the invariant that the graph lense should not modify any
// methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
extractPinnedItems(appInfo.alwaysInline, AbortReason.ALWAYS_INLINE);
- extractPinnedItems(
- DexDefinition.mapToReference(appInfo.noSideEffects.keySet().stream())::iterator,
- AbortReason.NO_SIDE_EFFECTS);
+ extractPinnedItems(appInfo.noSideEffects.keySet(), AbortReason.NO_SIDE_EFFECTS);
for (DexProgramClass clazz : classes) {
for (DexEncodedMethod method : clazz.methods()) {
@@ -620,7 +617,7 @@
}
private boolean verifyGraphLense(GraphLense graphLense) {
- assert graphLense.assertDefinitionsNotModified(appInfo.noSideEffects.keySet());
+ assert graphLense.assertReferencesNotModified(appInfo.noSideEffects.keySet());
// Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
// getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
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 49573db..28d1628 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -279,6 +279,19 @@
return mainDexClasses;
}
+ /** Returns a copy of this AndroidApp that does not have a main dex list. */
+ public AndroidApp withoutMainDexList() {
+ return new AndroidApp(
+ programResourceProviders,
+ programResourcesMainDescriptor,
+ classpathResourceProviders,
+ libraryResourceProviders,
+ archiveProvidersToClose,
+ proguardMapOutputData,
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
/**
* Write the dex program resources and proguard resource to @code{output}.
*/
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 045aa39..cad6397 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -579,6 +579,16 @@
return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.N);
}
+ public boolean isInterfaceMethodDesugaringEnabled() {
+ // This condition is to filter out tests that never set program consumer.
+ if (!hasConsumer()) {
+ return false;
+ }
+ return enableDesugaring
+ && interfaceMethodDesugaring == OffOrAuto.Auto
+ && !canUseDefaultAndStaticInterfaceMethods();
+ }
+
public boolean canUseMultidex() {
assert isGeneratingDex();
return intermediate || hasMinApi(AndroidApiLevel.L);
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index a9c02ff..ecb0a92 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -55,6 +55,11 @@
return state.getDiagnosticsMessages();
}
+ public R8TestCompileResult inspectDiagnosticMessages(Consumer<TestDiagnosticMessages> consumer) {
+ consumer.accept(state.getDiagnosticsMessages());
+ return self();
+ }
+
@Override
public CodeInspector inspector() throws IOException, ExecutionException {
return new CodeInspector(app, proguardMap);
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
index aeeb0ec..092d06e 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -71,12 +71,13 @@
.compile()
.outputJar();
} else {
- jar = testFolder.newFolder().toPath().resolve("out.zip");
+ jar = testFolder.newFolder().toPath().resolve("out.jar");
R8.run(
R8Command.builder()
- .setMode(CompilationMode.RELEASE)
- .addLibraryFiles(runtimeJar(Backend.CF))
+ .setMode(mode)
.addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
+ .addProguardConfigurationFiles(MAIN_KEEP)
+ .addLibraryFiles(runtimeJar(Backend.CF))
.setOutput(jar, OutputMode.ClassFile)
.build());
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
new file mode 100644
index 0000000..700e2d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -0,0 +1,66 @@
+// 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.kotlin;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ProcessKotlinReflectionLibTest extends KotlinTestBase {
+ private final Backend backend;
+
+ public ProcessKotlinReflectionLibTest(Backend backend, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.backend = backend;
+ }
+
+ @Parameterized.Parameters(name = "Backend: {0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(Backend.values(), KotlinTargetVersion.values());
+ }
+
+ private void test(String... rules) throws Exception {
+ testForR8(backend)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .addKeepRules(rules)
+ .compile();
+ }
+
+ @Test
+ public void testAsIs() throws Exception {
+ test("-dontshrink", "-dontoptimize", "-dontobfuscate");
+ }
+
+ @Test
+ public void testDontShrinkAndDontOptimize() throws Exception {
+ test("-dontshrink", "-dontoptimize");
+ }
+
+ @Test
+ public void testDontShrinkAndDontObfuscate() throws Exception {
+ test("-dontshrink", "-dontobfuscate");
+ }
+
+ @Test
+ public void testDontShrink() throws Exception {
+ test("-dontshrink");
+ }
+
+ @Test
+ public void testDontOptimize() throws Exception {
+ test("-dontoptimize");
+ }
+
+ @Test
+ public void testDontObfuscate() throws Exception {
+ test("-dontobfuscate");
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
new file mode 100644
index 0000000..f796046
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
@@ -0,0 +1,64 @@
+// 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.kotlin;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ProcessKotlinStdlibTest extends KotlinTestBase {
+ private final Backend backend;
+
+ public ProcessKotlinStdlibTest(Backend backend, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.backend = backend;
+ }
+
+ @Parameterized.Parameters(name = "Backend: {0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(Backend.values(), KotlinTargetVersion.values());
+ }
+
+ private void test(String... rules) throws Exception {
+ testForR8(backend)
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepRules(rules)
+ .compile();
+ }
+
+ @Test
+ public void testAsIs() throws Exception {
+ test("-dontshrink", "-dontoptimize", "-dontobfuscate");
+ }
+
+ @Test
+ public void testDontShrinkAndDontOptimize() throws Exception {
+ test("-dontshrink", "-dontoptimize");
+ }
+
+ @Test
+ public void testDontShrinkAndDontObfuscate() throws Exception {
+ test("-dontshrink", "-dontobfuscate");
+ }
+
+ @Test
+ public void testDontShrink() throws Exception {
+ test("-dontshrink");
+ }
+
+ @Test
+ public void testDontOptimize() throws Exception {
+ test("-dontoptimize");
+ }
+
+ @Test
+ public void testDontObfuscate() throws Exception {
+ test("-dontobfuscate");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
new file mode 100644
index 0000000..ab37cb0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
@@ -0,0 +1,102 @@
+// 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.shaking.desugar;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+@NeverMerge
+interface I {
+ static void foo() {
+ System.out.println("static::foo");
+ }
+
+ default void bar() {
+ foo();
+ System.out.println("default::bar");
+ }
+}
+
+class C implements I {
+}
+
+class KeepRuleWarningTestRunner {
+ public static void main(String[] args) {
+ C obj = new C();
+ obj.bar();
+ }
+}
+
+public class KeepRuleWarningTest extends TestBase {
+ private static final Class<?> MAIN = KeepRuleWarningTestRunner.class;
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("static::foo", "default::bar");
+
+ @Test
+ public void test_allMethods() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { <methods>; }")
+ .compile()
+ .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void test_asterisk() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { *(); }")
+ .compile()
+ .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void test_stillNotSpecific() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { *** f*(); }")
+ .compile()
+ .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void test_specific() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { static void foo(); }")
+ .compile()
+ .inspectDiagnosticMessages(m -> {
+ m.assertWarningsCount(1)
+ .assertWarningMessageThatMatches(containsString("static void foo()"))
+ .assertWarningMessageThatMatches(containsString("is ignored"))
+ .assertWarningMessageThatMatches(containsString("will be desugared"));
+ })
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
new file mode 100644
index 0000000..5038729
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -0,0 +1,73 @@
+// 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.shaking.desugar.interfacemethods;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Method;
+import org.junit.Test;
+
+@NeverMerge
+interface I {
+ default void m() {
+ System.out.println("I::m");
+ }
+}
+
+class C implements I {
+}
+
+class BridgeInliningTestRunner {
+ public static void main(String[] args) throws Exception {
+ C obj = new C();
+ for (Method m : obj.getClass().getDeclaredMethods()) {
+ m.invoke(obj, null);
+ }
+ }
+}
+
+public class BridgeInliningTest extends TestBase {
+ private static final Class<?> MAIN = BridgeInliningTestRunner.class;
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("I::m");
+
+ @Test
+ public void test() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { m(); }")
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector codeInspector) {
+ ClassSubject c = codeInspector.clazz(C.class);
+ assertThat(c, isPresent());
+ MethodSubject m = c.uniqueMethodWithName("m");
+ assertThat(m, isPresent());
+ assertTrue(m.getMethod().hasCode());
+ // TODO(b/123778921): why are I and C not merged without merge annotations?
+ //assertTrue(
+ // m.iterateInstructions(i -> i.isConstString("I::m", JumboStringMode.ALLOW)).hasNext());
+
+ // TODO(b/123778921): No companion class is in the output.
+ //codeInspector.forAllClasses(classSubject -> {
+ // assertFalse(classSubject.getOriginalDescriptor().contains("$-CC"));
+ //});
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/synthetic/KotlinCollectionDump.java b/src/test/java/com/android/tools/r8/shaking/synthetic/KotlinCollectionDump.java
new file mode 100644
index 0000000..d104272
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/synthetic/KotlinCollectionDump.java
@@ -0,0 +1,113 @@
+// 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.shaking.synthetic;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class Base {
+// public static void foo() {
+// System.out.println("Base::foo");
+// }
+// static void bar() {
+// Sub.foo();
+// }
+// }
+//
+// then make bar _synthetic_ as `kotlinc` does.
+class Base implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "Base", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Arrays.kt", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(5, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Base::foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(6, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 0);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC | ACC_SYNTHETIC, "bar", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(9, label0);
+ methodVisitor.visitMethodInsn(INVOKESTATIC, "Sub", "foo", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(10, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(0, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+// class Sub extends Base {}
+class Sub implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "Sub", null, "Base", null);
+
+ classWriter.visitSource("Arrays.kt", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(13, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "Base", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/shaking/synthetic/StaticCallInSyntheticMethodAsmTest.java b/src/test/java/com/android/tools/r8/shaking/synthetic/StaticCallInSyntheticMethodAsmTest.java
new file mode 100644
index 0000000..b08be31
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/synthetic/StaticCallInSyntheticMethodAsmTest.java
@@ -0,0 +1,37 @@
+// 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.shaking.synthetic;
+
+import com.android.tools.r8.AsmTestBase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StaticCallInSyntheticMethodAsmTest extends AsmTestBase {
+ private final Backend backend;
+
+ @Parameterized.Parameters(name = "backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public StaticCallInSyntheticMethodAsmTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ // class Base {
+ // static void foo() { ... }
+ // static synthetic void bar() { Sub.foo(); }
+ // }
+ // class Sub extends Base {}
+ @Test
+ public void test() throws Exception {
+ testForR8(backend)
+ .addProgramClassFileData(Base.dump(), Sub.dump())
+ .addKeepRules("-dontshrink")
+ .compile();
+ }
+
+}