Reland "Hygienic lambda desugaring."
Bug: 158159959
Change-Id: Id9e58b1ab66d51494f66f59ae4241fced1dae420
Fixes: 176211449
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 4def5d0..b762557 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -271,6 +271,9 @@
if (result != null) {
appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
appView.pruneItems(result.prunedItems);
+ if (result.lens != null) {
+ appView.setGraphLens(result.lens);
+ }
}
new CfApplicationWriter(
appView,
@@ -315,6 +318,9 @@
if (result != null) {
appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
appView.pruneItems(result.prunedItems);
+ if (result.lens != null) {
+ appView.setGraphLens(result.lens);
+ }
}
new ApplicationWriter(
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index f610d83..e611c5a 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -133,6 +133,9 @@
if (result != null) {
appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
appView.pruneItems(result.prunedItems);
+ if (result.lens != null) {
+ appView.setGraphLens(result.lens);
+ }
}
NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9e7a967..125b84d 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -838,12 +838,21 @@
appView.getSyntheticItems().computeFinalSynthetics(appView);
if (result != null) {
if (appView.appInfo().hasLiveness()) {
- appViewWithLiveness.setAppInfo(
- appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
+ if (result.lens == null) {
+ appViewWithLiveness.setAppInfo(
+ appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
+ } else {
+ appViewWithLiveness.rewriteWithLensAndApplication(
+ result.lens, result.commit.getApplication().asDirect());
+ }
+ appViewWithLiveness.pruneItems(result.prunedItems);
} else {
appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
+ appView.pruneItems(result.prunedItems);
+ if (result.lens != null) {
+ appView.setGraphLens(result.lens);
+ }
}
- appViewWithLiveness.pruneItems(result.prunedItems);
}
// Perform minification.
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 977dafd..ccfd713 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -332,6 +332,9 @@
@Override
public final void acceptHashing(HashingVisitor visitor) {
// Rather than traverse the full instruction, the compare ID will likely give a reasonable hash.
+ // TODO(b/158159959): This will likely lead to a lot of distinct synthetics hashing to the same
+ // hash as many have the same instruction pattern such as an invoke of the impl method or a
+ // field access.
visitor.visitInt(getCompareToId());
}
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index ef6b7cd..bbe4a90 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -55,7 +55,7 @@
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DexVersion;
import com.android.tools.r8.utils.InternalOptions;
@@ -323,7 +323,7 @@
for (DexType type : mapping.getTypes()) {
if (type.isClassType()) {
assert DexString.isValidSimpleName(apiLevel, type.getName());
- assert SyntheticItems.verifyNotInternalSynthetic(type);
+ assert SyntheticNaming.verifyNotInternalSynthetic(type);
}
}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 917f0de..7da0bc5 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -28,7 +28,7 @@
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -667,7 +667,7 @@
@Override
public boolean addType(DexType type) {
- assert SyntheticItems.verifyNotInternalSynthetic(type);
+ assert SyntheticNaming.verifyNotInternalSynthetic(type);
return types.add(type);
}
@@ -790,7 +790,7 @@
@Override
public boolean addType(DexType type) {
- assert SyntheticItems.verifyNotInternalSynthetic(type);
+ assert SyntheticNaming.verifyNotInternalSynthetic(type);
return maybeInsert(type, types, base.types);
}
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 cfacdbb..302dff4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -123,11 +123,6 @@
}
}
- public Collection<DexProgramClass> synthesizedClasses() {
- assert checkIfObsolete();
- return syntheticItems.getPendingSyntheticClasses();
- }
-
public Collection<DexProgramClass> classes() {
assert checkIfObsolete();
return app.classes();
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index eb83898..4d45d55 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -247,6 +247,9 @@
public void acceptHashing(HashingVisitor visitor) {
// Rather than hash the entire content, hash the sizes and each instruction "type" which
// should provide a fast yet reasonably distinct key.
+ // TODO(b/158159959): This will likely lead to a lot of distinct synthetics hashing to the same
+ // hash as many have the same instruction pattern such as an invoke of the impl method or a
+ // field access.
visitor.visitInt(instructions.size());
visitor.visitInt(tryCatchRanges.size());
visitor.visitInt(localVariables.size());
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index bcb7974..3483fbb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.structural.StructuralItem;
@@ -399,13 +401,18 @@
}
public static DexAnnotation createAnnotationSynthesizedClass(
- DexType synthesizingContext, DexItemFactory dexItemFactory) {
- DexValueType value = new DexValueType(synthesizingContext);
- DexAnnotationElement element = new DexAnnotationElement(dexItemFactory.valueString, value);
+ SyntheticKind kind, DexType synthesizingContext, DexItemFactory dexItemFactory) {
+ DexAnnotationElement kindElement =
+ new DexAnnotationElement(
+ dexItemFactory.kindString,
+ new DexValueString(dexItemFactory.createString(kind.descriptor)));
+ DexAnnotationElement typeElement =
+ new DexAnnotationElement(dexItemFactory.valueString, new DexValueType(synthesizingContext));
return new DexAnnotation(
VISIBILITY_BUILD,
new DexEncodedAnnotation(
- dexItemFactory.annotationSynthesizedClass, new DexAnnotationElement[] {element}));
+ dexItemFactory.annotationSynthesizedClass,
+ new DexAnnotationElement[] {kindElement, typeElement}));
}
public static boolean hasSynthesizedClassAnnotation(
@@ -413,7 +420,7 @@
return getSynthesizedClassAnnotationContextType(annotations, factory) != null;
}
- public static DexType getSynthesizedClassAnnotationContextType(
+ public static Pair<SyntheticKind, DexType> getSynthesizedClassAnnotationContextType(
DexAnnotationSet annotations, DexItemFactory factory) {
if (annotations.size() != 1) {
return null;
@@ -422,17 +429,31 @@
if (annotation.annotation.type != factory.annotationSynthesizedClass) {
return null;
}
- if (annotation.annotation.elements.length != 1) {
+ if (annotation.annotation.elements.length != 2) {
return null;
}
- DexAnnotationElement element = annotation.annotation.elements[0];
- if (element.name != factory.valueString) {
+ assert factory.kindString.isLessThan(factory.valueString);
+ DexAnnotationElement kindElement = annotation.annotation.elements[0];
+ if (kindElement.name != factory.kindString) {
return null;
}
- if (!element.value.isDexValueType()) {
+ if (!kindElement.value.isDexValueString()) {
return null;
}
- return element.value.asDexValueType().getValue();
+ SyntheticKind kind =
+ SyntheticNaming.SyntheticKind.fromDescriptor(
+ kindElement.value.asDexValueString().getValue().toString());
+ if (kind == null) {
+ return null;
+ }
+ DexAnnotationElement valueElement = annotation.annotation.elements[1];
+ if (valueElement.name != factory.valueString) {
+ return null;
+ }
+ if (!valueElement.value.isDexValueType()) {
+ return null;
+ }
+ return new Pair<>(kind, valueElement.value.asDexValueType().getValue());
}
public static DexAnnotation createAnnotationSynthesizedClassMap(
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 91203c8..2236b6c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -76,14 +76,12 @@
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.structural.CompareToVisitor;
-import com.android.tools.r8.utils.structural.CompareToVisitorWithTypeEquivalence;
import com.android.tools.r8.utils.structural.HashingVisitor;
-import com.android.tools.r8.utils.structural.HashingVisitorWithTypeEquivalence;
import com.android.tools.r8.utils.structural.Ordered;
-import com.android.tools.r8.utils.structural.RepresentativeMap;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
import com.google.common.collect.ImmutableList;
-import com.google.common.hash.Hasher;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.ArrayList;
@@ -95,7 +93,8 @@
import java.util.function.IntPredicate;
import org.objectweb.asm.Opcodes;
-public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod> {
+public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod>
+ implements StructuralItem<DexEncodedMethod> {
public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
@@ -338,6 +337,16 @@
return deprecated;
}
+ @Override
+ public DexEncodedMethod self() {
+ return this;
+ }
+
+ @Override
+ public StructuralMapping<DexEncodedMethod> getStructuralMapping() {
+ return DexEncodedMethod::syntheticSpecify;
+ }
+
// Visitor specifying the structure of the method with respect to its "synthetic" content.
// TODO(b/171867022): Generalize this so that it determines any method in full.
private static void syntheticSpecify(StructuralSpecification<DexEncodedMethod, ?> spec) {
@@ -356,28 +365,12 @@
DexEncodedMethod::hashCodeObject);
}
- public void hashSyntheticContent(Hasher hasher, RepresentativeMap map) {
- HashingVisitorWithTypeEquivalence.run(this, hasher, map, DexEncodedMethod::syntheticSpecify);
- }
-
- public boolean isSyntheticContentEqual(DexEncodedMethod other) {
- return syntheticCompareTo(other) == 0;
- }
-
- public int syntheticCompareTo(DexEncodedMethod other) {
- // Consider the holder types to be equivalent, using the holder of this method as the
- // representative.
- RepresentativeMap map = t -> t == other.getHolderType() ? getHolderType() : t;
- return CompareToVisitorWithTypeEquivalence.run(
- this, other, map, DexEncodedMethod::syntheticSpecify);
- }
-
private static int compareCodeObject(Code code1, Code code2, CompareToVisitor visitor) {
if (code1.isCfCode() && code2.isCfCode()) {
return code1.asCfCode().acceptCompareTo(code2.asCfCode(), visitor);
}
if (code1.isDexCode() && code2.isDexCode()) {
- return visitor.visit(code1.asDexCode(), code2.asDexCode(), DexCode::compareTo);
+ return code1.asDexCode().acceptCompareTo(code2.asDexCode(), visitor);
}
throw new Unreachable(
"Unexpected attempt to compare incompatible synthetic objects: " + code1 + " and " + code2);
@@ -387,9 +380,7 @@
if (code.isCfCode()) {
code.asCfCode().acceptHashing(visitor);
} else {
- // TODO(b/158159959): Implement a more precise hashing on code objects.
- assert code.isDexCode();
- visitor.visitInt(code.hashCode());
+ code.asDexCode().acceptHashing(visitor);
}
}
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 385fb58..f148019 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -299,6 +299,7 @@
public final DexString throwableArrayDescriptor = createString("[Ljava/lang/Throwable;");
public final DexString valueString = createString("value");
+ public final DexString kindString = createString("kind");
public final DexType booleanType = createStaticallyKnownType(booleanDescriptor);
public final DexType byteType = createStaticallyKnownType(byteDescriptor);
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 2a6ebf5..7d69899 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -21,6 +21,9 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.structural.Ordered;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
@@ -37,7 +40,7 @@
import java.util.function.Supplier;
public class DexProgramClass extends DexClass
- implements ProgramDefinition, Supplier<DexProgramClass> {
+ implements ProgramDefinition, Supplier<DexProgramClass>, StructuralItem<DexProgramClass> {
@FunctionalInterface
public interface ChecksumSupplier {
@@ -144,6 +147,36 @@
synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
}
+ @Override
+ public DexProgramClass self() {
+ return this;
+ }
+
+ @Override
+ public StructuralMapping<DexProgramClass> getStructuralMapping() {
+ return DexProgramClass::specify;
+ }
+
+ private static void specify(StructuralSpecification<DexProgramClass, ?> spec) {
+ spec.withItem(c -> c.type)
+ .withItem(c -> c.superType)
+ .withItem(c -> c.interfaces)
+ .withItem(c -> c.accessFlags)
+ .withNullableItem(c -> c.sourceFile)
+ .withNullableItem(c -> c.initialClassFileVersion)
+ .withBool(c -> c.deprecated)
+ .withNullableItem(DexClass::getNestHostClassAttribute)
+ .withItemCollection(DexClass::getNestMembersClassAttributes)
+ .withItem(DexDefinition::annotations)
+ // TODO(b/158159959): Make signatures structural.
+ .withAssert(c -> c.classSignature == ClassSignature.noSignature())
+ .withItemArray(c -> c.staticFields)
+ .withItemArray(c -> c.instanceFields)
+ .withItemCollection(DexClass::allMethodsSorted)
+ // TODO(b/168584485): Synthesized-from is being removed (empty for new synthetics).
+ .withAssert(c -> c.synthesizedFrom.isEmpty());
+ }
+
public void forEachProgramField(Consumer<? super ProgramField> consumer) {
forEachField(field -> consumer.accept(new ProgramField(this, field)));
}
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 376abd6..cab5904 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -9,7 +9,6 @@
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.InterfaceMethodRewriter.EMULATE_LIBRARY_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 static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
@@ -21,7 +20,7 @@
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -42,7 +41,7 @@
// Bundletool is merging classes that may originate from a build with an old version of R8.
// Allow merging of classes that use names from older versions of R8.
private static List<String> OLD_SYNTHESIZED_NAMES =
- ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility");
+ ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility", "-$$Lambda$");
public final DexString descriptor;
private String toStringCache = null;
@@ -303,22 +302,15 @@
}
// TODO(b/158159959): Remove usage of name-based identification.
- public boolean isD8R8SynthesizedLambdaClassType() {
- String name = toSourceString();
- return name.contains(LAMBDA_CLASS_NAME_PREFIX);
- }
-
- // TODO(b/158159959): Remove usage of name-based identification.
public boolean isD8R8SynthesizedClassType() {
String name = toSourceString();
// The synthesized classes listed here must always be unique to a program context and thus
// never duplicated for distinct inputs.
- return
- // Hygienic suffix.
- name.contains(COMPANION_CLASS_NAME_SUFFIX)
+ return false
+ // Hygienic suffix.
+ || name.contains(COMPANION_CLASS_NAME_SUFFIX)
// New and hygienic synthesis infrastructure.
- || name.contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
- || name.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+ || SyntheticNaming.isSyntheticName(name)
// Only generated in core lib.
|| name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
|| name.contains(TYPE_WRAPPER_SUFFIX)
@@ -338,7 +330,6 @@
// newer releases can be used to merge previous builds.
return name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX) // Shared among enums.
|| name.contains(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)
- || name.contains(LAMBDA_CLASS_NAME_PREFIX) // Could collide.
|| name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
|| name.contains(DISPATCH_CLASS_NAME_SUFFIX) // Shared on reference.
|| name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 0e3d784..2328408 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -5,7 +5,6 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.horizontalclassmerging.ClassMerger.CLASS_ID_FIELD_NAME;
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_INSTANCE_FIELD_NAME;
import com.android.tools.r8.errors.Unreachable;
@@ -620,7 +619,7 @@
// that they can be mapped back to the original program.
DexField originalField = getOriginalFieldSignature(field.getReference());
assert originalFields.contains(originalField)
- || isD8R8SynthesizedField(originalField, dexItemFactory)
+ || isD8R8SynthesizedField(originalField, appView)
: "Unable to map field `"
+ field.getReference().toSourceString()
+ "` back to original program";
@@ -638,16 +637,16 @@
return true;
}
- private boolean isD8R8SynthesizedField(DexField field, DexItemFactory dexItemFactory) {
+ private boolean isD8R8SynthesizedField(DexField field, AppView<?> appView) {
// TODO(b/167947782): Should be a general check to see if the field is D8/R8 synthesized
// instead of relying on field names.
- if (field.match(dexItemFactory.objectMembers.clinitField)) {
+ if (field.match(appView.dexItemFactory().objectMembers.clinitField)) {
return true;
}
if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) {
return true;
}
- if (field.getHolderType().toSourceString().contains(LAMBDA_CLASS_NAME_PREFIX)
+ if (appView.getSyntheticItems().isSyntheticClass(field.getHolderType())
&& field.getName().toSourceString().equals(LAMBDA_INSTANCE_FIELD_NAME)) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
index e007f4e..c9969a6 100644
--- a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
@@ -5,12 +5,19 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
import org.objectweb.asm.ClassWriter;
-public class NestHostClassAttribute {
+public class NestHostClassAttribute implements StructuralItem<NestHostClassAttribute> {
private final DexType nestHost;
+ private static void specify(StructuralSpecification<NestHostClassAttribute, ?> spec) {
+ spec.withItem(a -> a.nestHost);
+ }
+
public NestHostClassAttribute(DexType nestHost) {
this.nestHost = nestHost;
}
@@ -27,4 +34,14 @@
assert nestHost != null;
writer.visitNestHost(lens.lookupInternalName(nestHost));
}
+
+ @Override
+ public NestHostClassAttribute self() {
+ return this;
+ }
+
+ @Override
+ public StructuralMapping<NestHostClassAttribute> getStructuralMapping() {
+ return NestHostClassAttribute::specify;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
index f9d1a35..0d7c19d 100644
--- a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
@@ -5,14 +5,21 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
import java.util.Collections;
import java.util.List;
import org.objectweb.asm.ClassWriter;
-public class NestMemberClassAttribute {
+public class NestMemberClassAttribute implements StructuralItem<NestMemberClassAttribute> {
private final DexType nestMember;
+ private static void specify(StructuralSpecification<NestMemberClassAttribute, ?> spec) {
+ spec.withItem(a -> a.nestMember);
+ }
+
public NestMemberClassAttribute(DexType nestMember) {
this.nestMember = nestMember;
}
@@ -29,4 +36,14 @@
assert nestMember != null;
writer.visitNestMember(lens.lookupInternalName(nestMember));
}
+
+ @Override
+ public NestMemberClassAttribute self() {
+ return this;
+ }
+
+ @Override
+ public StructuralMapping<NestMemberClassAttribute> getStructuralMapping() {
+ return NestMemberClassAttribute::specify;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index a10db7147..c1d5981 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -15,7 +15,7 @@
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
- private Map<DexType, DexProgramClass> newProgramClasses = null;
+ private final Map<DexType, DexProgramClass> programClassCache = new IdentityHashMap<>();
private final Map<DexType, DexProgramClass> synthesizedFromClasses = new IdentityHashMap<>();
private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
@@ -53,13 +53,13 @@
}
/** Fixup a collection of classes. */
- public Collection<DexProgramClass> fixupClasses(Collection<DexProgramClass> classes) {
- assert newProgramClasses == null;
- newProgramClasses = new IdentityHashMap<>();
+ public List<DexProgramClass> fixupClasses(Collection<DexProgramClass> classes) {
+ List<DexProgramClass> newProgramClasses = new ArrayList<>();
for (DexProgramClass clazz : classes) {
- newProgramClasses.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
+ newProgramClasses.add(
+ programClassCache.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz)));
}
- return newProgramClasses.values();
+ return newProgramClasses;
}
// Should remain private as the correctness of the fixup requires the lazy 'newProgramClasses'.
@@ -70,7 +70,7 @@
clazz.getOriginKind(),
clazz.getOrigin(),
clazz.getAccessFlags(),
- fixupType(clazz.superType),
+ clazz.superType == null ? null : fixupType(clazz.superType),
fixupTypeList(clazz.interfaces),
clazz.getSourceFile(),
fixupNestHost(clazz.getNestHostClassAttribute()),
@@ -250,7 +250,6 @@
// Should remain private as its correctness relies on the setup of 'newProgramClasses'.
private Collection<DexProgramClass> fixupSynthesizedFrom(
Collection<DexProgramClass> synthesizedFrom) {
- assert newProgramClasses != null;
if (synthesizedFrom.isEmpty()) {
return synthesizedFrom;
}
@@ -261,7 +260,7 @@
// is no longer in the application?
Map<DexType, DexProgramClass> classes =
appView.appInfo().definitionForWithoutExistenceAssert(clazz.getType()) != null
- ? newProgramClasses
+ ? programClassCache
: synthesizedFromClasses;
DexProgramClass newClass =
classes.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
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 e2b0e47..bc20e17 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
@@ -409,11 +409,10 @@
}
}
- private void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
- throws ExecutionException {
+ private void synthesizeLambdaClasses(ExecutorService executorService) throws ExecutionException {
if (lambdaRewriter != null) {
assert !appView.enableWholeProgramOptimizations();
- lambdaRewriter.finalizeLambdaDesugaringForD8(builder, this, executorService);
+ lambdaRewriter.finalizeLambdaDesugaringForD8(this, executorService);
}
}
@@ -435,6 +434,7 @@
Flavor includeAllResources,
ExecutorService executorService)
throws ExecutionException {
+ assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
if (interfaceMethodRewriter != null) {
interfaceMethodRewriter.desugarInterfaceMethods(
builder, includeAllResources, executorService);
@@ -522,7 +522,19 @@
builder.setHighestSortingString(highestSortingString);
desugarNestBasedAccess(builder, executor);
- synthesizeLambdaClasses(builder, executor);
+
+ // Synthesize lambda classes and commit to the app in full.
+ synthesizeLambdaClasses(executor);
+ if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+ appView.setAppInfo(
+ new AppInfo(
+ appView.appInfo().getSyntheticItems().commit(builder.build()),
+ appView.appInfo().getMainDexClasses()));
+ application = appView.appInfo().app();
+ builder = application.builder();
+ builder.setHighestSortingString(highestSortingString);
+ }
+
desugarInterfaceMethods(builder, ExcludeDexResources, executor);
synthesizeTwrCloseResourceUtilityClass(builder, executor);
processSynthesizedJava8UtilityClasses(executor);
@@ -533,10 +545,10 @@
timing.end();
- DexApplication app = builder.build();
+ application = builder.build();
appView.setAppInfo(
new AppInfo(
- appView.appInfo().getSyntheticItems().commit(app),
+ appView.appInfo().getSyntheticItems().commit(application),
appView.appInfo().getMainDexClasses()));
}
@@ -794,6 +806,14 @@
appView.clearCodeRewritings();
}
+ // Commit synthetics before creating a builder (otherwise the builder will not include the
+ // synthetics.)
+ if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+ appView.setAppInfo(
+ appView
+ .appInfo()
+ .rebuildWithLiveness(appView.getSyntheticItems().commit(appView.appInfo().app())));
+ }
// Build a new application with jumbo string info.
Builder<?> builder = appView.appInfo().app().builder();
builder.setHighestSortingString(highestSortingString);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 40b18c9..14d10c5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.ir.desugar.backports.NumericMethodRewrites;
import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
+import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
@@ -1406,6 +1407,7 @@
return appInfo
.getSyntheticItems()
.createMethod(
+ SyntheticNaming.SyntheticKind.BACKPORT,
context,
appInfo.dexItemFactory(),
builder ->
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 891beb3..5a00a4b 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
@@ -57,6 +57,7 @@
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
@@ -398,6 +399,7 @@
appView
.getSyntheticItems()
.createMethod(
+ SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
context.getHolder(),
factory,
syntheticMethodBuilder ->
@@ -1139,6 +1141,7 @@
Builder<?> builder, Flavor flavour, Consumer<ProgramMethod> newSynthesizedMethodConsumer) {
ClassProcessor processor = new ClassProcessor(appView, this, newSynthesizedMethodConsumer);
// First we compute all desugaring *without* introducing forwarding methods.
+ assert appView.getSyntheticItems().verifyNonLegacySyntheticsAreCommitted();
for (DexProgramClass clazz : builder.getProgramClasses()) {
if (shouldProcess(clazz, flavour, false)) {
if (appView.isAlreadyLibraryDesugared(clazz)) {
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 a50581e..966cfec 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
@@ -8,7 +8,6 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -20,10 +19,8 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
@@ -37,18 +34,11 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
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.synthesis.SyntheticClassBuilder;
import com.android.tools.r8.utils.OptionalBool;
-import com.google.common.base.Suppliers;
-import com.google.common.primitives.Longs;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
-import java.util.zip.CRC32;
/**
* Represents lambda class generated for a lambda descriptor in context of lambda instantiation
@@ -77,31 +67,27 @@
final DexMethod classConstructor;
public final DexField lambdaField;
public final Target target;
- public final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
- private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<>(1);
- private final Supplier<DexProgramClass> lazyDexClass =
- Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
+
+ // Considered final but is set after due to circularity in allocation.
+ private DexProgramClass clazz = null;
LambdaClass(
+ SyntheticClassBuilder builder,
AppView<?> appView,
LambdaRewriter rewriter,
ProgramMethod accessedFrom,
- DexType lambdaClassType,
LambdaDescriptor descriptor) {
assert rewriter != null;
- assert lambdaClassType != null;
assert descriptor != null;
-
+ this.type = builder.getType();
this.appView = appView;
this.rewriter = rewriter;
- this.type = lambdaClassType;
this.descriptor = descriptor;
- DexItemFactory factory = appView.dexItemFactory();
+ DexItemFactory factory = builder.getFactory();
DexProto constructorProto = factory.createProto(
factory.voidType, descriptor.captures.values);
- this.constructor =
- factory.createMethod(lambdaClassType, constructorProto, factory.constructorMethodName);
+ this.constructor = factory.createMethod(type, constructorProto, factory.constructorMethodName);
this.target = createTarget(accessedFrom);
@@ -109,98 +95,32 @@
this.classConstructor =
!stateless
? null
- : factory.createMethod(
- lambdaClassType, constructorProto, factory.classConstructorMethodName);
+ : factory.createMethod(type, constructorProto, factory.classConstructorMethodName);
this.lambdaField =
- !stateless
- ? null
- : factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
+ !stateless ? null : factory.createField(type, type, rewriter.instanceFieldName);
+
+ // Synthesize the program class one all fields are set.
+ synthesizeLambdaClass(builder);
}
- // Generate unique lambda class type for lambda descriptor and instantiation point context.
- public static DexType createLambdaClassType(
- AppView<?> appView, ProgramMethod accessedFrom, LambdaDescriptor match) {
- StringBuilder lambdaClassDescriptor = new StringBuilder("L");
-
- // We always create lambda class in the same package where it is referenced.
- String packageDescriptor = accessedFrom.getHolderType().getPackageDescriptor();
- if (!packageDescriptor.isEmpty()) {
- lambdaClassDescriptor.append(packageDescriptor).append('/');
- }
-
- // Lambda class name prefix
- lambdaClassDescriptor.append(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX);
-
- // If the lambda class should match 1:1 the class it is accessed from, we
- // just add the name of this type to make lambda class name unique.
- // It also helps link the class lambda originated from in some cases.
- if (match.delegatesToLambdaImplMethod() || match.needsAccessor(accessedFrom)) {
- lambdaClassDescriptor.append(accessedFrom.getHolderType().getName()).append('$');
- }
-
- // Add unique lambda descriptor id
- lambdaClassDescriptor.append(match.uniqueId).append(';');
- return appView.dexItemFactory().createType(lambdaClassDescriptor.toString());
- }
-
- public final DexProgramClass getOrCreateLambdaClass() {
- return lazyDexClass.get();
- }
-
- private DexProgramClass synthesizeLambdaClass() {
- DexMethod mainMethod =
- appView.dexItemFactory().createMethod(type, descriptor.erasedProto, descriptor.name);
-
- DexProgramClass clazz =
- new DexProgramClass(
- type,
- null,
- new SynthesizedOrigin("lambda desugaring", getClass()),
- // Make the synthesized class public, as it might end up being accessed from a different
- // classloader (package private access is not allowed across classloaders, b/72538146).
- ClassAccessFlags.fromDexAccessFlags(
- Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
- appView.dexItemFactory().objectType,
- buildInterfaces(),
- appView.dexItemFactory().createString("lambda"),
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- ClassSignature.noSignature(),
- DexAnnotationSet.empty(),
- synthesizeStaticFields(),
- synthesizeInstanceFields(),
- synthesizeDirectMethods(),
- synthesizeVirtualMethods(mainMethod),
- appView.dexItemFactory().getSkipNameValidationForTesting(),
- LambdaClass::computeChecksumForSynthesizedClass);
- appView.appInfo().addSynthesizedClass(clazz, false);
-
- // The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
- // ModificationException we must use synchronization.
- synchronized (synthesizedFrom) {
- synthesizedFrom.forEach(clazz::addSynthesizedFrom);
- }
+ public final DexProgramClass getLambdaProgramClass() {
+ assert clazz != null;
return clazz;
}
- private static long computeChecksumForSynthesizedClass(DexProgramClass clazz) {
- // Checksum of synthesized classes are compute based off the depending input. This might
- // create false positives (ie: unchanged lambda class detected as changed even thought only
- // an unrelated part from a synthesizedFrom class is changed).
+ void setClass(DexProgramClass clazz) {
+ assert this.clazz == null;
+ assert clazz != null;
+ assert type == clazz.type;
+ this.clazz = clazz;
+ }
- // Ideally, we should use some hashcode of the dex program class that is deterministic across
- // compiles.
- Collection<DexProgramClass> synthesizedFrom = clazz.getSynthesizedFrom();
- ByteBuffer buffer = ByteBuffer.allocate(synthesizedFrom.size() * Longs.BYTES);
- for (DexProgramClass from : synthesizedFrom) {
- buffer.putLong(from.getChecksum());
- }
- CRC32 crc = new CRC32();
- byte[] array = buffer.array();
- crc.update(array, 0, array.length);
- return crc.getValue();
+ private void synthesizeLambdaClass(SyntheticClassBuilder builder) {
+ builder.setInterfaces(descriptor.interfaces);
+ synthesizeStaticFields(builder);
+ synthesizeInstanceFields(builder);
+ synthesizeDirectMethods(builder);
+ synthesizeVirtualMethods(builder);
}
final DexField getCaptureField(int index) {
@@ -216,24 +136,15 @@
return descriptor.isStateless();
}
- void addSynthesizedFrom(DexProgramClass clazz) {
- assert clazz != null;
- synchronized (synthesizedFrom) {
- if (synthesizedFrom.add(clazz)) {
- // The lambda class may already have been synthesized, and we therefore need to update the
- // synthesized lambda class as well.
- getOrCreateLambdaClass().addSynthesizedFrom(clazz);
- }
- }
- }
-
// Synthesize virtual methods.
- private DexEncodedMethod[] synthesizeVirtualMethods(DexMethod mainMethod) {
- DexEncodedMethod[] methods = new DexEncodedMethod[1 + descriptor.bridges.size()];
- int index = 0;
+ private void synthesizeVirtualMethods(SyntheticClassBuilder builder) {
+ DexMethod mainMethod =
+ appView.dexItemFactory().createMethod(type, descriptor.erasedProto, descriptor.name);
+
+ List<DexEncodedMethod> methods = new ArrayList<>(1 + descriptor.bridges.size());
// Synthesize main method.
- methods[index++] =
+ methods.add(
new DexEncodedMethod(
mainMethod,
MethodAccessFlags.fromSharedAccessFlags(
@@ -242,13 +153,13 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
LambdaMainMethodSourceCode.build(this, mainMethod),
- true);
+ true));
// Synthesize bridge methods.
for (DexProto bridgeProto : descriptor.bridges) {
DexMethod bridgeMethod =
appView.dexItemFactory().createMethod(type, bridgeProto, descriptor.name);
- methods[index++] =
+ methods.add(
new DexEncodedMethod(
bridgeMethod,
MethodAccessFlags.fromSharedAccessFlags(
@@ -261,18 +172,18 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
LambdaBridgeMethodSourceCode.build(this, bridgeMethod, mainMethod),
- true);
+ true));
}
- return methods;
+ builder.setVirtualMethods(methods);
}
// Synthesize direct methods.
- private DexEncodedMethod[] synthesizeDirectMethods() {
+ private void synthesizeDirectMethods(SyntheticClassBuilder builder) {
boolean stateless = isStateless();
- DexEncodedMethod[] methods = new DexEncodedMethod[stateless ? 2 : 1];
+ List<DexEncodedMethod> methods = new ArrayList<>(stateless ? 2 : 1);
// Constructor.
- methods[0] =
+ methods.add(
new DexEncodedMethod(
constructor,
MethodAccessFlags.fromSharedAccessFlags(
@@ -283,11 +194,11 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
LambdaConstructorSourceCode.build(this),
- true);
+ true));
// Class constructor for stateless lambda classes.
if (stateless) {
- methods[1] =
+ methods.add(
new DexEncodedMethod(
classConstructor,
MethodAccessFlags.fromSharedAccessFlags(
@@ -296,61 +207,50 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
LambdaClassConstructorSourceCode.build(this),
- true);
- feedback.classInitializerMayBePostponed(methods[1]);
+ true));
+ feedback.classInitializerMayBePostponed(methods.get(1));
}
- return methods;
+ builder.setDirectMethods(methods);
}
// Synthesize instance fields to represent captured values.
- private DexEncodedField[] synthesizeInstanceFields() {
+ private void synthesizeInstanceFields(SyntheticClassBuilder builder) {
DexType[] fieldTypes = descriptor.captures.values;
int fieldCount = fieldTypes.length;
- DexEncodedField[] fields = new DexEncodedField[fieldCount];
+ List<DexEncodedField> fields = new ArrayList<>(fieldCount);
for (int i = 0; i < fieldCount; i++) {
FieldAccessFlags accessFlags =
FieldAccessFlags.fromSharedAccessFlags(
Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC);
- fields[i] =
+ fields.add(
new DexEncodedField(
getCaptureField(i),
accessFlags,
FieldTypeSignature.noSignature(),
DexAnnotationSet.empty(),
- null);
+ null));
}
- return fields;
+ builder.setInstanceFields(fields);
}
// Synthesize static fields to represent singleton instance.
- private DexEncodedField[] synthesizeStaticFields() {
- if (!isStateless()) {
- return DexEncodedField.EMPTY_ARRAY;
+ private void synthesizeStaticFields(SyntheticClassBuilder builder) {
+ if (isStateless()) {
+ // Create instance field for stateless lambda.
+ assert this.lambdaField != null;
+ builder.setStaticFields(
+ Collections.singletonList(
+ new DexEncodedField(
+ this.lambdaField,
+ FieldAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC
+ | Constants.ACC_FINAL
+ | Constants.ACC_SYNTHETIC
+ | Constants.ACC_STATIC),
+ FieldTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ DexValueNull.NULL)));
}
-
- // Create instance field for stateless lambda.
- assert this.lambdaField != null;
- DexEncodedField[] fields = new DexEncodedField[1];
- fields[0] =
- new DexEncodedField(
- this.lambdaField,
- FieldAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC
- | Constants.ACC_FINAL
- | Constants.ACC_SYNTHETIC
- | Constants.ACC_STATIC),
- FieldTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- DexValueNull.NULL);
- return fields;
- }
-
- // Build a list of implemented interfaces.
- private DexTypeList buildInterfaces() {
- List<DexType> interfaces = descriptor.interfaces;
- return interfaces.isEmpty()
- ? DexTypeList.empty()
- : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
}
// Creates a delegation target for this particular lambda class. Note that we
@@ -643,11 +543,15 @@
newMethod.getCode(), callTarget.getArity(), appView);
return newMethod;
});
-
- assert replacement != null
- : "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName();
-
- return new ProgramMethod(implMethodHolder, replacement);
+ if (replacement != null) {
+ return new ProgramMethod(implMethodHolder, replacement);
+ }
+ // The method might already have been moved by another invoke-dynamic targeting it.
+ // If so, it must be defined on the holder.
+ ProgramMethod modified = implMethodHolder.lookupProgramMethod(callTarget);
+ assert modified != null;
+ assert modified.getDefinition().isNonPrivateVirtualMethod();
+ return modified;
}
}
@@ -713,11 +617,27 @@
rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
return newMethod;
});
- return new ProgramMethod(implMethodHolder, replacement);
+ if (replacement != null) {
+ return new ProgramMethod(implMethodHolder, replacement);
+ }
+ // The method might already have been moved by another invoke-dynamic targeting it.
+ // If so, it must be defined on the holder.
+ ProgramMethod modified = implMethodHolder.lookupProgramMethod(callTarget);
+ assert modified != null;
+ assert modified.getDefinition().isNonPrivateVirtualMethod();
+ return modified;
}
private ProgramMethod createSyntheticAccessor(
DexMethod implMethod, DexProgramClass implMethodHolder) {
+ // The accessor might already have been created by another invoke-dynamic targeting it.
+ ProgramMethod existing = implMethodHolder.lookupProgramMethod(callTarget);
+ if (existing != null) {
+ assert existing.getAccessFlags().isSynthetic();
+ assert existing.getAccessFlags().isPublic();
+ assert existing.getDefinition().isVirtualMethod();
+ return existing;
+ }
MethodAccessFlags accessorFlags =
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false);
@@ -762,6 +682,15 @@
DexProgramClass accessorClass = appView.definitionForProgramType(callTarget.holder);
assert accessorClass != null;
+ // The accessor might already have been created by another invoke-dynamic targeting it.
+ ProgramMethod existing = accessorClass.lookupProgramMethod(callTarget);
+ if (existing != null) {
+ assert existing.getAccessFlags().isSynthetic();
+ assert existing.getAccessFlags().isPublic();
+ assert existing.getAccessFlags().isStatic();
+ return existing;
+ }
+
// Always make the method public to provide access when r8 minification is allowed to move
// the lambda class accessing this method to another package (-allowaccessmodification).
MethodAccessFlags accessorFlags =
@@ -779,11 +708,7 @@
AccessorMethodSourceCode.build(LambdaClass.this, callTarget),
true);
- // We may arrive here concurrently so we need must update the methods of the class atomically.
- synchronized (accessorClass) {
- accessorClass.addDirectMethod(accessorEncodedMethod);
- }
-
+ accessorClass.addDirectMethod(accessorEncodedMethod);
return new ProgramMethod(accessorClass, accessorEncodedMethod);
}
}
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 f464fdd..ee459b4 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
@@ -16,7 +16,6 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -28,19 +27,10 @@
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeCustom;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -49,9 +39,8 @@
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.IdentityHashMap;
+import java.util.Collections;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -69,7 +58,6 @@
public class LambdaRewriter {
// 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$";
public static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
@@ -81,16 +69,10 @@
private final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
private final Set<DexMethod> forcefullyMovedMethods = Sets.newIdentityHashSet();
- // Maps call sites seen so far to inferred lambda descriptor. It is intended
- // to help avoid re-matching call sites we already seen. Note that same call
- // site may match one or several lambda classes.
- //
- // NOTE: synchronize concurrent access on `knownCallSites`.
- private final Map<DexCallSite, LambdaDescriptor> knownCallSites = new IdentityHashMap<>();
// Maps lambda class type into lambda class representation. Since lambda class
// type uniquely defines lambda class, effectively canonicalizes lambda classes.
// NOTE: synchronize concurrent access on `knownLambdaClasses`.
- private final Map<DexType, LambdaClass> knownLambdaClasses = new IdentityHashMap<>();
+ private final List<LambdaClass> knownLambdaClasses = new ArrayList<>();
public LambdaRewriter(AppView<?> appView) {
this.appView = appView;
@@ -138,7 +120,7 @@
if (descriptor == null) {
return null;
}
- return getOrCreateLambdaClass(descriptor, method);
+ return createLambdaClass(descriptor, method);
});
}
@@ -209,16 +191,10 @@
}
/** Generates lambda classes and adds them to the builder. */
- public void finalizeLambdaDesugaringForD8(
- Builder<?> builder, IRConverter converter, ExecutorService executorService)
+ public void finalizeLambdaDesugaringForD8(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
synthesizeAccessibilityBridgesForLambdaClassesD8(
- knownLambdaClasses.values(), converter, executorService);
- for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
- DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
- appView.appInfo().addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
- builder.addSynthesizedClass(synthesizedClass);
- }
+ knownLambdaClasses, converter, executorService);
fixup();
optimizeSynthesizedClasses(converter, executorService);
}
@@ -226,183 +202,35 @@
private void optimizeSynthesizedClasses(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
converter.optimizeSynthesizedClasses(
- knownLambdaClasses.values().stream()
- .map(LambdaClass::getOrCreateLambdaClass)
+ knownLambdaClasses.stream()
+ .map(LambdaClass::getLambdaProgramClass)
.collect(ImmutableSet.toImmutableSet()),
executorService);
}
- // Matches invoke-custom instruction operands to infer lambda descriptor
- // corresponding to this lambda invocation point.
- //
- // Returns the lambda descriptor or `MATCH_FAILED`.
- private LambdaDescriptor inferLambdaDescriptor(DexCallSite callSite, ProgramMethod context) {
- // We check the map before and after inferring lambda descriptor to minimize time
- // spent in synchronized block. As a result we may throw away calculated descriptor
- // in rare case when another thread has same call site processed concurrently,
- // but this is a low price to pay comparing to making whole method synchronous.
- LambdaDescriptor descriptor = getKnown(knownCallSites, callSite);
- return descriptor != null
- ? descriptor
- : putIfAbsent(
- knownCallSites,
- callSite,
- LambdaDescriptor.infer(callSite, appView.appInfoForDesugaring(), context));
- }
-
- // Returns a lambda class corresponding to the lambda descriptor and context,
- // creates the class if it does not yet exist.
- public LambdaClass getOrCreateLambdaClass(
- LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
- DexType lambdaClassType = LambdaClass.createLambdaClassType(appView, accessedFrom, descriptor);
- // We check the map twice to to minimize time spent in synchronized block.
- LambdaClass lambdaClass = getKnown(knownLambdaClasses, lambdaClassType);
- if (lambdaClass == null) {
- lambdaClass =
- putIfAbsent(
- knownLambdaClasses,
- lambdaClassType,
- new LambdaClass(appView, this, accessedFrom, lambdaClassType, descriptor));
- if (appView.options().isDesugaredLibraryCompilation()) {
- DexType rewrittenType =
- appView.rewritePrefix.rewrittenType(accessedFrom.getHolderType(), appView);
- if (rewrittenType == null) {
- rewrittenType =
- appView
- .options()
- .desugaredLibraryConfiguration
- .getEmulateLibraryInterface()
- .get(accessedFrom.getHolderType());
- }
- if (rewrittenType != null) {
- addRewritingPrefix(accessedFrom, rewrittenType, lambdaClassType);
- }
- }
- }
- lambdaClass.addSynthesizedFrom(accessedFrom.getHolder());
- if (appView.appInfo().getMainDexClasses().contains(accessedFrom.getHolder())) {
- lambdaClass.addToMainDexList.set(true);
+ // Creates a lambda class corresponding to the lambda descriptor and context.
+ public LambdaClass createLambdaClass(LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
+ Box<LambdaClass> box = new Box<>();
+ DexProgramClass clazz =
+ appView
+ .getSyntheticItems()
+ .createClass(
+ SyntheticNaming.SyntheticKind.LAMBDA,
+ accessedFrom.getHolder(),
+ appView.dexItemFactory(),
+ builder ->
+ box.set(new LambdaClass(builder, appView, this, accessedFrom, descriptor)));
+ // Immediately set the actual program class on the lambda.
+ LambdaClass lambdaClass = box.get();
+ lambdaClass.setClass(clazz);
+ synchronized (knownLambdaClasses) {
+ knownLambdaClasses.add(lambdaClass);
}
return lambdaClass;
}
- private LambdaClass getKnownLambdaClass(LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
- DexType lambdaClassType = LambdaClass.createLambdaClassType(appView, accessedFrom, descriptor);
- return getKnown(knownLambdaClasses, lambdaClassType);
- }
-
- private void addRewritingPrefix(
- ProgramMethod context, DexType rewritten, DexType lambdaClassType) {
- String javaName = lambdaClassType.toString();
- String typeString = context.getHolderType().toString();
- String actualPrefix = typeString.substring(0, typeString.lastIndexOf('.'));
- String rewrittenString = rewritten.toString();
- String actualRewrittenPrefix = rewrittenString.substring(0, rewrittenString.lastIndexOf('.'));
- assert javaName.startsWith(actualPrefix);
- appView.rewritePrefix.rewriteType(
- lambdaClassType,
- appView
- .dexItemFactory()
- .createType(
- DescriptorUtils.javaTypeToDescriptor(
- actualRewrittenPrefix + javaName.substring(actualPrefix.length()))));
- }
-
- private static <K, V> V getKnown(Map<K, V> map, K key) {
- synchronized (map) {
- return map.get(key);
- }
- }
-
- private static <K, V> V putIfAbsent(Map<K, V> map, K key, V value) {
- synchronized (map) {
- V known = map.get(key);
- if (known != null) {
- return known;
- }
- map.put(key, value);
- return value;
- }
- }
-
- // Patches invoke-custom instruction to create or get an instance
- // of the generated lambda class.
- private void patchInstruction(
- InvokeCustom invoke,
- LambdaClass lambdaClass,
- IRCode code,
- ListIterator<BasicBlock> blocks,
- InstructionListIterator instructions,
- Set<Value> affectedValues) {
- assert lambdaClass != null;
- assert instructions != null;
-
- // The value representing new lambda instance: we reuse the
- // value from the original invoke-custom instruction, and thus
- // all its usages.
- Value lambdaInstanceValue = invoke.outValue();
- if (lambdaInstanceValue == null) {
- // The out value might be empty in case it was optimized out.
- lambdaInstanceValue =
- code.createValue(
- TypeElement.fromDexType(lambdaClass.type, Nullability.maybeNull(), appView));
- } else {
- affectedValues.add(lambdaInstanceValue);
- }
-
- // For stateless lambdas we replace InvokeCustom instruction with StaticGet
- // reading the value of INSTANCE field created for singleton lambda class.
- if (lambdaClass.isStateless()) {
- instructions.replaceCurrentInstruction(
- new StaticGet(lambdaInstanceValue, lambdaClass.lambdaField));
- // Note that since we replace one throwing operation with another we don't need
- // to have any special handling for catch handlers.
- return;
- }
-
- // For stateful lambdas we always create a new instance since we need to pass
- // captured values to the constructor.
- //
- // We replace InvokeCustom instruction with a new NewInstance instruction
- // instantiating lambda followed by InvokeDirect instruction calling a
- // constructor on it.
- //
- // original:
- // Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
- //
- // result:
- // NewInstance rResult <- LambdaClass
- // Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
- lambdaInstanceValue.setType(
- lambdaInstanceValue.getType().asReferenceType().asDefinitelyNotNull());
- NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
- instructions.replaceCurrentInstruction(newInstance);
-
- List<Value> arguments = new ArrayList<>();
- arguments.add(lambdaInstanceValue);
- arguments.addAll(invoke.arguments()); // Optional captures.
- InvokeDirect constructorCall =
- new InvokeDirect(lambdaClass.constructor, null /* no return value */, arguments);
- instructions.add(constructorCall);
- constructorCall.setPosition(newInstance.getPosition());
-
- // If we don't have catch handlers we are done.
- if (!constructorCall.getBlock().hasCatchHandlers()) {
- return;
- }
-
- // Move the iterator back to position it between the two instructions, split
- // the block between the two instructions, and copy the catch handlers.
- instructions.previous();
- assert instructions.peekNext().isInvokeDirect();
- BasicBlock currentBlock = newInstance.getBlock();
- BasicBlock nextBlock = instructions.split(code, blocks);
- assert !instructions.hasNext();
- nextBlock.copyCatchHandlers(code, blocks, currentBlock, appView.options());
- }
-
- public Map<DexType, LambdaClass> getKnownLambdaClasses() {
- return knownLambdaClasses;
+ public Collection<LambdaClass> getKnownLambdaClasses() {
+ return Collections.unmodifiableList(knownLambdaClasses);
}
public NestedGraphLens fixup() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index 1b55f13..a972b39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.InternalOptions;
public class UtilityMethodsForCodeOptimizations {
@@ -31,6 +32,7 @@
SyntheticItems syntheticItems = appView.getSyntheticItems();
ProgramMethod syntheticMethod =
syntheticItems.createMethod(
+ SyntheticNaming.SyntheticKind.TO_STRING_IF_NOT_NULL,
context,
dexItemFactory,
builder ->
@@ -60,6 +62,7 @@
SyntheticItems syntheticItems = appView.getSyntheticItems();
ProgramMethod syntheticMethod =
syntheticItems.createMethod(
+ SyntheticNaming.SyntheticKind.THROW_CCE_IF_NOT_NULL,
context,
dexItemFactory,
builder ->
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index b5dfee6..9868a64 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -1090,10 +1090,18 @@
} else if (classInitializerSideEffect.canBePostponed()) {
feedback.classInitializerMayBePostponed(method);
} else {
- assert !context.getHolderType().isD8R8SynthesizedLambdaClassType()
- || options.debug
- || appView.appInfo().hasPinnedInstanceInitializer(context.getHolderType())
- || appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()
+ assert options.debug
+ || appView
+ .getSyntheticItems()
+ .verifySyntheticLambdaProperty(
+ context.getHolder(),
+ lambdaClass ->
+ appView.appInfo().hasPinnedInstanceInitializer(lambdaClass.getType())
+ || appView
+ .options()
+ .horizontalClassMergerOptions()
+ .isJavaLambdaMergingEnabled(),
+ nonLambdaClass -> true)
: "Unexpected observable side effects from lambda `" + context.toSourceString() + "`";
}
return;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index ef724ec..cf6a117 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -40,7 +40,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AsmUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -124,7 +124,7 @@
marker.isRelocator() ? Optional.empty() : Optional.of(marker.toString());
LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
for (DexProgramClass clazz : application.classes()) {
- assert SyntheticItems.verifyNotInternalSynthetic(clazz.getType());
+ assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
try {
writeClass(clazz, consumer, rewriter, markerString);
} catch (ClassTooLargeException e) {
@@ -194,6 +194,7 @@
for (int i = 0; i < clazz.interfaces.values.length; i++) {
interfaces[i] = namingLens.lookupInternalName(clazz.interfaces.values[i]);
}
+ assert SyntheticNaming.verifyNotInternalSynthetic(name);
writer.visit(version.raw(), access, name, signature, superName, interfaces);
writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations);
ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index b374e58..73037aa 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -269,7 +269,7 @@
this.switchMaps = switchMaps;
this.lockCandidates = lockCandidates;
this.initClassReferences = initClassReferences;
- verify();
+ assert verify();
}
private AppInfoWithLiveness(AppInfoWithLiveness previous, CommittedItems committedItems) {
@@ -366,10 +366,11 @@
previous.initClassReferences);
}
- private void verify() {
+ private boolean verify() {
assert keepInfo.verifyPinnedTypesAreLive(liveTypes);
assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(
liveTypes, getMissingClasses(), this);
+ return true;
}
private static KeepInfoCollection extendPinnedItems(
@@ -454,7 +455,7 @@
this.lockCandidates = previous.lockCandidates;
this.initClassReferences = previous.initClassReferences;
previous.markObsolete();
- verify();
+ assert verify();
}
public static AppInfoWithLivenessModifier modifier() {
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 c15fdb8..90416fd 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -921,7 +921,7 @@
assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
CfCode code = contextMethod.getCode().asCfCode();
if (code != null) {
- LambdaClass lambdaClass = lambdaRewriter.getOrCreateLambdaClass(descriptor, context);
+ LambdaClass lambdaClass = lambdaRewriter.createLambdaClass(descriptor, context);
lambdaClasses.put(lambdaClass.type, new Pair<>(lambdaClass, context));
lambdaCallSites
.computeIfAbsent(context, k -> new IdentityHashMap<>())
@@ -3038,13 +3038,9 @@
return empty;
}
- void addInstantiatedClass(
- DexProgramClass clazz, ProgramMethod context, boolean isMainDexClass) {
+ void addInstantiatedClass(DexProgramClass clazz, ProgramMethod context) {
assert !syntheticInstantiations.containsKey(clazz.type);
syntheticInstantiations.put(clazz.type, new Pair<>(clazz, context));
- if (isMainDexClass) {
- mainDexTypes.add(clazz);
- }
}
void addClasspathClass(DexClasspathClass clazz) {
@@ -3066,10 +3062,6 @@
void amendApplication(Builder appBuilder) {
assert !isEmpty();
- for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
- syntheticInstantiations.values()) {
- appBuilder.addProgramClass(clazzAndContext.getFirst());
- }
appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
}
@@ -3193,8 +3185,8 @@
// Add all desugared classes to the application, main-dex list, and mark them instantiated.
LambdaClass lambdaClass = lambdaClassAndContext.getFirst();
ProgramMethod context = lambdaClassAndContext.getSecond();
- DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
- additions.addInstantiatedClass(programClass, context, lambdaClass.addToMainDexList.get());
+ DexProgramClass programClass = lambdaClass.getLambdaProgramClass();
+ additions.addInstantiatedClass(programClass, context);
// Mark the instance constructor targeted and live.
DexEncodedMethod constructor = programClass.lookupDirectMethod(lambdaClass.constructor);
KeepReason reason = KeepReason.instantiatedIn(context);
@@ -3349,10 +3341,9 @@
lambdaRewriter
.getKnownLambdaClasses()
.forEach(
- (type, lambda) -> {
- DexProgramClass synthesizedClass = lambda.getOrCreateLambdaClass();
+ lambda -> {
+ DexProgramClass synthesizedClass = lambda.getLambdaProgramClass();
assert synthesizedClass != null;
- assert synthesizedClass == appInfo().definitionForWithoutExistenceAssert(type);
assert liveTypes.contains(synthesizedClass);
if (synthesizedClass == null) {
return;
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
index 424fd8d..e60dfa3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -7,8 +7,6 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.function.Function;
@@ -28,23 +26,19 @@
// Immutable package accessible fields to allow SyntheticItems creation.
final DexApplication application;
final int nextSyntheticId;
- final ImmutableSet<DexType> legacySyntheticTypes;
- final ImmutableMap<DexType, SyntheticReference> syntheticItems;
+ final CommittedSyntheticsCollection committed;
final ImmutableList<DexType> committedTypes;
CommittedItems(
int nextSyntheticId,
DexApplication application,
- ImmutableSet<DexType> legacySyntheticTypes,
- ImmutableMap<DexType, SyntheticReference> syntheticItems,
+ CommittedSyntheticsCollection committed,
ImmutableList<DexType> committedTypes) {
- assert verifyTypesAreInApp(application, legacySyntheticTypes);
- assert verifyTypesAreInApp(application, syntheticItems.keySet());
this.nextSyntheticId = nextSyntheticId;
this.application = application;
- this.legacySyntheticTypes = legacySyntheticTypes;
- this.syntheticItems = syntheticItems;
+ this.committed = committed;
this.committedTypes = committedTypes;
+ committed.verifyTypesAreInApp(application);
}
// Conversion to a mutable synthetic items collection. Should only be used in AppInfo creation.
@@ -62,7 +56,7 @@
@Deprecated
public Collection<DexType> getLegacySyntheticTypes() {
- return legacySyntheticTypes;
+ return committed.getLegacyTypes();
}
@Override
@@ -70,11 +64,4 @@
// All synthetic types are committed to the application so lookup is just the base lookup.
return baseDefinitionFor.apply(type);
}
-
- private static boolean verifyTypesAreInApp(DexApplication app, Collection<DexType> types) {
- for (DexType type : types) {
- assert app.programDefinitionFor(type) != null : "Missing synthetic: " + type;
- }
- return true;
- }
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
new file mode 100644
index 0000000..582a39a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -0,0 +1,243 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * Immutable collection of committed items.
+ *
+ * <p>This structure is to make it easier to pass the items from SyntheticItems to CommittedItems
+ * and back while also providing a builder for updating the committed synthetics.
+ */
+class CommittedSyntheticsCollection {
+
+ static class Builder {
+ private final CommittedSyntheticsCollection parent;
+ private ImmutableMap.Builder<DexType, SyntheticClassReference> newNonLegacyClasses = null;
+ private ImmutableMap.Builder<DexType, SyntheticMethodReference> newNonLegacyMethods = null;
+ private ImmutableSet.Builder<DexType> newLegacyClasses = null;
+
+ public Builder(CommittedSyntheticsCollection parent) {
+ this.parent = parent;
+ }
+
+ public Builder addItem(SyntheticDefinition<?, ?> definition) {
+ definition.toReference().apply(this::addNonLegacyMethod, this::addNonLegacyClass);
+ return this;
+ }
+
+ public Builder addNonLegacyClass(SyntheticClassDefinition definition) {
+ return addNonLegacyClass(definition.toReference());
+ }
+
+ public Builder addNonLegacyClass(SyntheticClassReference reference) {
+ if (newNonLegacyClasses == null) {
+ newNonLegacyClasses = ImmutableMap.builder();
+ }
+ newNonLegacyClasses.put(reference.getHolder(), reference);
+ return this;
+ }
+
+ public Builder addNonLegacyMethod(SyntheticMethodDefinition definition) {
+ return addNonLegacyMethod(definition.toReference());
+ }
+
+ public Builder addNonLegacyMethod(SyntheticMethodReference reference) {
+ if (newNonLegacyMethods == null) {
+ newNonLegacyMethods = ImmutableMap.builder();
+ }
+ newNonLegacyMethods.put(reference.getHolder(), reference);
+ return this;
+ }
+
+ public Builder addLegacyClasses(Collection<DexProgramClass> classes) {
+ if (newLegacyClasses == null) {
+ newLegacyClasses = ImmutableSet.builder();
+ }
+ classes.forEach(c -> newLegacyClasses.add(c.getType()));
+ return this;
+ }
+
+ public Builder addLegacyClass(DexType type) {
+ if (newLegacyClasses == null) {
+ newLegacyClasses = ImmutableSet.builder();
+ }
+ newLegacyClasses.add(type);
+ return this;
+ }
+
+ public CommittedSyntheticsCollection build() {
+ if (newNonLegacyClasses == null && newNonLegacyMethods == null && newLegacyClasses == null) {
+ return parent;
+ }
+ ImmutableMap<DexType, SyntheticClassReference> allNonLegacyClasses =
+ newNonLegacyClasses == null
+ ? parent.nonLegacyClasses
+ : newNonLegacyClasses.putAll(parent.nonLegacyClasses).build();
+ ImmutableMap<DexType, SyntheticMethodReference> allNonLegacyMethods =
+ newNonLegacyMethods == null
+ ? parent.nonLegacyMethods
+ : newNonLegacyMethods.putAll(parent.nonLegacyMethods).build();
+ ImmutableSet<DexType> allLegacyClasses =
+ newLegacyClasses == null
+ ? parent.legacyTypes
+ : newLegacyClasses.addAll(parent.legacyTypes).build();
+ return new CommittedSyntheticsCollection(
+ allLegacyClasses, allNonLegacyMethods, allNonLegacyClasses);
+ }
+ }
+
+ private static final CommittedSyntheticsCollection EMPTY =
+ new CommittedSyntheticsCollection(ImmutableSet.of(), ImmutableMap.of(), ImmutableMap.of());
+
+ /**
+ * Immutable set of synthetic types in the application (eg, committed).
+ *
+ * <p>TODO(b/158159959): Remove legacy support.
+ */
+ private final ImmutableSet<DexType> legacyTypes;
+
+ /** Mapping from synthetic type to its synthetic method item description. */
+ private final ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods;
+
+ /** Mapping from synthetic type to its synthetic class item description. */
+ private final ImmutableMap<DexType, SyntheticClassReference> nonLegacyClasses;
+
+ public CommittedSyntheticsCollection(
+ ImmutableSet<DexType> legacyTypes,
+ ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods,
+ ImmutableMap<DexType, SyntheticClassReference> nonLegacyClasses) {
+ this.legacyTypes = legacyTypes;
+ this.nonLegacyMethods = nonLegacyMethods;
+ this.nonLegacyClasses = nonLegacyClasses;
+ assert legacyTypes.size() + nonLegacyMethods.size() + nonLegacyClasses.size()
+ == Sets.union(Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()), legacyTypes)
+ .size();
+ }
+
+ public static CommittedSyntheticsCollection empty() {
+ return EMPTY;
+ }
+
+ Builder builder() {
+ return new Builder(this);
+ }
+
+ boolean isEmpty() {
+ return legacyTypes.isEmpty() && nonLegacyMethods.isEmpty() && nonLegacyClasses.isEmpty();
+ }
+
+ boolean containsType(DexType type) {
+ return containsLegacyType(type) || containsNonLegacyType(type);
+ }
+
+ public boolean containsLegacyType(DexType type) {
+ return legacyTypes.contains(type);
+ }
+
+ public boolean containsNonLegacyType(DexType type) {
+ return nonLegacyMethods.containsKey(type) || nonLegacyClasses.containsKey(type);
+ }
+
+ public ImmutableSet<DexType> getLegacyTypes() {
+ return legacyTypes;
+ }
+
+ public ImmutableMap<DexType, SyntheticMethodReference> getNonLegacyMethods() {
+ return nonLegacyMethods;
+ }
+
+ public ImmutableMap<DexType, SyntheticClassReference> getNonLegacyClasses() {
+ return nonLegacyClasses;
+ }
+
+ public SyntheticReference<?, ?> getNonLegacyItem(DexType type) {
+ SyntheticMethodReference reference = nonLegacyMethods.get(type);
+ if (reference != null) {
+ return reference;
+ }
+ return nonLegacyClasses.get(type);
+ }
+
+ public void forEachNonLegacyItem(Consumer<SyntheticReference<?, ?>> fn) {
+ nonLegacyMethods.forEach((t, r) -> fn.accept(r));
+ nonLegacyClasses.forEach((t, r) -> fn.accept(r));
+ }
+
+ CommittedSyntheticsCollection pruneItems(PrunedItems prunedItems) {
+ Set<DexType> removed = prunedItems.getNoLongerSyntheticItems();
+ if (removed.isEmpty()) {
+ return this;
+ }
+ Builder builder = CommittedSyntheticsCollection.empty().builder();
+ boolean changed = false;
+ for (DexType type : legacyTypes) {
+ if (removed.contains(type)) {
+ changed = true;
+ } else {
+ builder.addLegacyClass(type);
+ }
+ }
+ for (SyntheticMethodReference reference : nonLegacyMethods.values()) {
+ if (removed.contains(reference.getHolder())) {
+ changed = true;
+ } else {
+ builder.addNonLegacyMethod(reference);
+ }
+ }
+ for (SyntheticClassReference reference : nonLegacyClasses.values()) {
+ if (removed.contains(reference.getHolder())) {
+ changed = true;
+ } else {
+ builder.addNonLegacyClass(reference);
+ }
+ }
+ return changed ? builder.build() : this;
+ }
+
+ CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) {
+ return new CommittedSyntheticsCollection(
+ lens.rewriteTypes(legacyTypes),
+ rewriteItems(nonLegacyMethods, lens),
+ rewriteItems(nonLegacyClasses, lens));
+ }
+
+ private static <R extends SyntheticReference<R, ?>> ImmutableMap<DexType, R> rewriteItems(
+ Map<DexType, R> items, NonIdentityGraphLens lens) {
+ ImmutableMap.Builder<DexType, R> rewrittenItems = ImmutableMap.builder();
+ for (R reference : items.values()) {
+ R rewritten = reference.rewrite(lens);
+ if (rewritten != null) {
+ rewrittenItems.put(rewritten.getHolder(), rewritten);
+ }
+ }
+ return rewrittenItems.build();
+ }
+
+ boolean verifyTypesAreInApp(DexApplication application) {
+ assert verifyTypesAreInApp(application, legacyTypes);
+ assert verifyTypesAreInApp(application, nonLegacyMethods.keySet());
+ assert verifyTypesAreInApp(application, nonLegacyClasses.keySet());
+ return true;
+ }
+
+ private static boolean verifyTypesAreInApp(DexApplication app, Collection<DexType> types) {
+ for (DexType type : types) {
+ assert app.programDefinitionFor(type) != null : "Missing synthetic: " + type;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index f5d4b66..2e371d0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
import java.util.Comparator;
import java.util.Set;
@@ -53,6 +54,20 @@
return new SynthesizingContext(synthesizingContextType, clazz.type, clazz.origin);
}
+ static SynthesizingContext fromSyntheticContextChange(
+ DexType syntheticType, SynthesizingContext oldContext, DexItemFactory factory) {
+ String descriptor = syntheticType.toDescriptorString();
+ int i = descriptor.indexOf(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
+ if (i <= 0) {
+ assert false : "Unexpected synthetic without internal separator: " + syntheticType;
+ return null;
+ }
+ DexType newContext = factory.createType(descriptor.substring(0, i) + ";");
+ return newContext == oldContext.getSynthesizingContextType()
+ ? oldContext
+ : new SynthesizingContext(newContext, newContext, oldContext.inputContextOrigin);
+ }
+
private SynthesizingContext(
DexType synthesizingContextType, DexType inputContextType, Origin inputContextOrigin) {
this.synthesizingContextType = synthesizingContextType;
@@ -75,14 +90,6 @@
return inputContextOrigin;
}
- DexType createHygienicType(String syntheticId, DexItemFactory factory) {
- // If the context is a synthetic input, then use its annotated context as the hygienic context.
- String contextDesc = synthesizingContextType.toDescriptorString();
- String prefix = contextDesc.substring(0, contextDesc.length() - 1);
- String suffix = SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR + syntheticId + ";";
- return factory.createType(prefix + suffix);
- }
-
SynthesizingContext rewrite(NonIdentityGraphLens lens) {
DexType rewrittenInputeContextType = lens.lookupType(inputContextType);
DexType rewrittenSynthesizingContextType = lens.lookupType(synthesizingContextType);
@@ -127,15 +134,17 @@
void addIfDerivedFromMainDexClass(
DexProgramClass externalSyntheticClass,
MainDexClasses mainDexClasses,
- Set<DexType> allMainDexTypes,
- Set<DexType> derivedMainDexTypesToIgnore) {
+ Set<DexType> allMainDexTypes) {
// The input context type (not the annotated context) determines if the derived class is to be
// in main dex.
// TODO(b/168584485): Once resolved allMainDexTypes == mainDexClasses.
if (allMainDexTypes.contains(inputContextType)) {
mainDexClasses.add(externalSyntheticClass);
- // Mark the type as to be ignored when computing main-dex placement for legacy types.
- derivedMainDexTypesToIgnore.add(inputContextType);
}
}
+
+ @Override
+ public String toString() {
+ return "SynthesizingContext{" + getSynthesizingContextType() + "}";
+ }
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 4f289c7..9d2460b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -33,8 +33,10 @@
private DexType superType;
private DexTypeList interfaces = DexTypeList.empty();
-
- private int nextMethodId = 0;
+ private List<DexEncodedField> staticFields = new ArrayList<>();
+ private List<DexEncodedField> instanceFields = new ArrayList<>();
+ private List<DexEncodedMethod> directMethods = new ArrayList<>();
+ private List<DexEncodedMethod> virtualMethods = new ArrayList<>();
private List<SyntheticMethodBuilder> methods = new ArrayList<>();
SyntheticClassBuilder(DexType type, SynthesizingContext context, DexItemFactory factory) {
@@ -52,12 +54,40 @@
return type;
}
- private String getNextMethodName() {
- return SyntheticItems.INTERNAL_SYNTHETIC_METHOD_PREFIX + nextMethodId++;
+ public SyntheticClassBuilder setInterfaces(List<DexType> interfaces) {
+ this.interfaces =
+ interfaces.isEmpty()
+ ? DexTypeList.empty()
+ : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
+ return this;
+ }
+
+ public SyntheticClassBuilder setStaticFields(List<DexEncodedField> fields) {
+ staticFields.clear();
+ staticFields.addAll(fields);
+ return this;
+ }
+
+ public SyntheticClassBuilder setInstanceFields(List<DexEncodedField> fields) {
+ instanceFields.clear();
+ instanceFields.addAll(fields);
+ return this;
+ }
+
+ public SyntheticClassBuilder setDirectMethods(Iterable<DexEncodedMethod> methods) {
+ directMethods.clear();
+ methods.forEach(directMethods::add);
+ return this;
+ }
+
+ public SyntheticClassBuilder setVirtualMethods(Iterable<DexEncodedMethod> methods) {
+ virtualMethods.clear();
+ methods.forEach(virtualMethods::add);
+ return this;
}
public SyntheticClassBuilder addMethod(Consumer<SyntheticMethodBuilder> fn) {
- SyntheticMethodBuilder method = new SyntheticMethodBuilder(this, getNextMethodName());
+ SyntheticMethodBuilder method = new SyntheticMethodBuilder(this);
fn.accept(method);
methods.add(method);
return this;
@@ -65,35 +95,27 @@
DexProgramClass build() {
ClassAccessFlags accessFlags =
- ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+ ClassAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_FINAL | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
Kind originKind = null;
DexString sourceFile = null;
NestHostClassAttribute nestHost = null;
List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
EnclosingMethodAttribute enclosingMembers = null;
List<InnerClassAttribute> innerClasses = Collections.emptyList();
- DexEncodedField[] staticFields = DexEncodedField.EMPTY_ARRAY;
- DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY;
- DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
- DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
- assert !methods.isEmpty();
- List<DexEncodedMethod> directs = new ArrayList<>(methods.size());
- List<DexEncodedMethod> virtuals = new ArrayList<>(methods.size());
for (SyntheticMethodBuilder builder : methods) {
DexEncodedMethod method = builder.build();
if (method.isNonPrivateVirtualMethod()) {
- virtuals.add(method);
+ virtualMethods.add(method);
} else {
- directs.add(method);
+ directMethods.add(method);
}
}
- if (!directs.isEmpty()) {
- directMethods = directs.toArray(new DexEncodedMethod[directs.size()]);
- }
- if (!virtuals.isEmpty()) {
- virtualMethods = virtuals.toArray(new DexEncodedMethod[virtuals.size()]);
- }
- long checksum = 7 * (long) directs.hashCode() + 11 * (long) virtuals.hashCode();
+ long checksum =
+ 7 * (long) directMethods.hashCode()
+ + 11 * (long) virtualMethods.hashCode()
+ + 13 * (long) staticFields.hashCode()
+ + 17 * (long) instanceFields.hashCode();
return new DexProgramClass(
type,
originKind,
@@ -108,10 +130,10 @@
innerClasses,
ClassSignature.noSignature(),
DexAnnotationSet.empty(),
- staticFields,
- instanceFields,
- directMethods,
- virtualMethods,
+ staticFields.toArray(new DexEncodedField[staticFields.size()]),
+ instanceFields.toArray(new DexEncodedField[instanceFields.size()]),
+ directMethods.toArray(new DexEncodedMethod[directMethods.size()]),
+ virtualMethods.toArray(new DexEncodedMethod[virtualMethods.size()]),
factory.getSkipNameValidationForTesting(),
c -> checksum);
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassDefinition.java
new file mode 100644
index 0000000..4b0b2cf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassDefinition.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.structural.RepresentativeMap;
+import com.google.common.hash.Hasher;
+
+/**
+ * Definition of a synthetic class item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SyntheticClassDefinition
+ extends SyntheticDefinition<SyntheticClassReference, SyntheticClassDefinition> {
+
+ private final DexProgramClass clazz;
+
+ SyntheticClassDefinition(SyntheticKind kind, SynthesizingContext context, DexProgramClass clazz) {
+ super(kind, context);
+ this.clazz = clazz;
+ }
+
+ public DexProgramClass getProgramClass() {
+ return clazz;
+ }
+
+ @Override
+ SyntheticClassReference toReference() {
+ return new SyntheticClassReference(getKind(), getContext(), clazz.getType());
+ }
+
+ @Override
+ DexProgramClass getHolder() {
+ return clazz;
+ }
+
+ @Override
+ public boolean isValid() {
+ return clazz.isPublic() && clazz.isFinal() && clazz.accessFlags.isSynthetic();
+ }
+
+ @Override
+ void internalComputeHash(Hasher hasher, RepresentativeMap map) {
+ clazz.hashWithTypeEquivalence(hasher, map);
+ }
+
+ @Override
+ int internalCompareTo(SyntheticClassDefinition o, RepresentativeMap map) {
+ return clazz.compareWithTypeEquivalenceTo(o.clazz, map);
+ }
+
+ @Override
+ public String toString() {
+ return "SyntheticClass{ clazz = "
+ + clazz.type.toSourceString()
+ + ", kind = "
+ + getKind()
+ + ", context = "
+ + getContext()
+ + " }";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
new file mode 100644
index 0000000..9c79ab9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Reference to a synthetic class item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SyntheticClassReference
+ extends SyntheticReference<SyntheticClassReference, SyntheticClassDefinition> {
+ final DexType type;
+
+ SyntheticClassReference(SyntheticKind kind, SynthesizingContext context, DexType type) {
+ super(kind, context);
+ this.type = type;
+ }
+
+ @Override
+ DexType getHolder() {
+ return type;
+ }
+
+ @Override
+ SyntheticClassDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+ DexClass clazz = definitions.apply(type);
+ if (clazz == null) {
+ return null;
+ }
+ assert clazz.isProgramClass();
+ return new SyntheticClassDefinition(getKind(), getContext(), clazz.asProgramClass());
+ }
+
+ @Override
+ SyntheticClassReference rewrite(NonIdentityGraphLens lens) {
+ DexType rewritten = lens.lookupType(type);
+ // If the reference has been non-trivially rewritten the compiler has changed it and it can no
+ // longer be considered a synthetic. The context may or may not have changed.
+ if (type != rewritten && !lens.isSimpleRenaming(type, rewritten)) {
+ // If the referenced item is rewritten, it should be moved to another holder as the
+ // synthetic holder is no longer part of the synthetic collection.
+ assert SyntheticNaming.verifyNotInternalSynthetic(rewritten);
+ return null;
+ }
+ SynthesizingContext context = getContext().rewrite(lens);
+ if (context == getContext() && rewritten == type) {
+ return this;
+ }
+ // Ensure that if a synthetic moves its context moves consistently.
+ if (type != rewritten) {
+ context =
+ SynthesizingContext.fromSyntheticContextChange(rewritten, context, lens.dexItemFactory());
+ if (context == null) {
+ return null;
+ }
+ }
+ return new SyntheticClassReference(getKind(), context, rewritten);
+ }
+
+ @Override
+ void apply(
+ Consumer<SyntheticMethodReference> onMethod, Consumer<SyntheticClassReference> onClass) {
+ onClass.accept(this);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 01f8825..526f4fc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -4,30 +4,71 @@
package com.android.tools.r8.synthesis;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.structural.RepresentativeMap;
import com.google.common.hash.HashCode;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
/**
* Base type for the definition of a synthetic item.
*
* <p>This class is internal to the synthetic items collection, thus package-protected.
*/
-abstract class SyntheticDefinition {
+abstract class SyntheticDefinition<
+ R extends SyntheticReference<R, D>, D extends SyntheticDefinition<R, D>> {
+
+ private final SyntheticKind kind;
private final SynthesizingContext context;
- SyntheticDefinition(SynthesizingContext context) {
+ SyntheticDefinition(SyntheticKind kind, SynthesizingContext context) {
+ assert kind != null;
+ assert context != null;
+ this.kind = kind;
this.context = context;
}
- abstract SyntheticReference toReference();
+ abstract R toReference();
- SynthesizingContext getContext() {
+ final SyntheticKind getKind() {
+ return kind;
+ }
+
+ final SynthesizingContext getContext() {
return context;
}
abstract DexProgramClass getHolder();
- abstract HashCode computeHash(RepresentativeMap map, boolean intermediate);
+ final HashCode computeHash(RepresentativeMap map, boolean intermediate) {
+ Hasher hasher = Hashing.murmur3_128().newHasher();
+ if (intermediate) {
+ // If in intermediate mode, include the context type as sharing is restricted to within a
+ // single context.
+ getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
+ }
+ internalComputeHash(hasher, map);
+ return hasher.hash();
+ }
- abstract boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate);
+ abstract void internalComputeHash(Hasher hasher, RepresentativeMap map);
+
+ final boolean isEquivalentTo(D other, boolean includeContext) {
+ return compareTo(other, includeContext) == 0;
+ }
+
+ int compareTo(D other, boolean includeContext) {
+ if (includeContext) {
+ int order = getContext().compareTo(other.getContext());
+ if (order != 0) {
+ return order;
+ }
+ }
+ RepresentativeMap map = t -> t == other.getHolder().getType() ? getHolder().getType() : t;
+ return internalCompareTo(other, map);
+ }
+
+ abstract int internalCompareTo(D other, RepresentativeMap map);
+
+ public abstract boolean isValid();
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index c18b29d..67391a2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -6,22 +6,31 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.Builder;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
import com.android.tools.r8.utils.structural.RepresentativeMap;
import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;
@@ -32,37 +41,146 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
-import java.util.function.Predicate;
+import java.util.function.Function;
public class SyntheticFinalization {
public static class Result {
public final CommittedItems commit;
+ public final NonIdentityGraphLens lens;
public final PrunedItems prunedItems;
- public Result(CommittedItems commit, PrunedItems prunedItems) {
+ public Result(
+ CommittedItems commit, SyntheticFinalizationGraphLens lens, PrunedItems prunedItems) {
this.commit = commit;
+ this.lens = lens;
this.prunedItems = prunedItems;
}
}
- private static class EquivalenceGroup<T extends SyntheticDefinition & Comparable<T>>
- implements Comparable<EquivalenceGroup<T>> {
- private List<T> members;
+ public static class SyntheticFinalizationGraphLens extends NestedGraphLens {
- EquivalenceGroup(T singleton) {
- this(singleton, Collections.singletonList(singleton));
+ private final Map<DexType, DexType> syntheticTypeMap;
+ private final Map<DexMethod, DexMethod> syntheticMethodsMap;
+
+ private SyntheticFinalizationGraphLens(
+ GraphLens previous,
+ Map<DexType, DexType> syntheticClassesMap,
+ Map<DexMethod, DexMethod> syntheticMethodsMap,
+ Map<DexType, DexType> typeMap,
+ BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+ Map<DexMethod, DexMethod> methodMap,
+ BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
+ DexItemFactory factory) {
+ super(typeMap, methodMap, fieldMap, originalMethodSignatures, previous, factory);
+ this.syntheticTypeMap = syntheticClassesMap;
+ this.syntheticMethodsMap = syntheticMethodsMap;
}
- EquivalenceGroup(T representative, List<T> members) {
+ // The mapping is many to one, so the inverse is only defined up to equivalence groups.
+ // Override the access to renamed signatures to first check for synthetic mappings before
+ // using the original item mappings of the
+
+ @Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ if (syntheticTypeMap.containsKey(originalField.holder)) {
+ DexField renamed = fieldMap.get(originalField);
+ if (renamed != null) {
+ return renamed;
+ }
+ }
+ return super.getRenamedFieldSignature(originalField);
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+ if (syntheticTypeMap.containsKey(originalMethod.holder)) {
+ DexMethod renamed = methodMap.get(originalMethod);
+ if (renamed != null) {
+ return renamed;
+ }
+ }
+ DexMethod renamed = syntheticMethodsMap.get(originalMethod);
+ return renamed != null ? renamed : super.getRenamedMethodSignature(originalMethod, applied);
+ }
+ }
+
+ private static class Builder {
+
+ // Forward mapping of internal to external synthetics.
+ Map<DexType, DexType> syntheticClassesMap = new IdentityHashMap<>();
+ Map<DexMethod, DexMethod> syntheticMethodsMap = new IdentityHashMap<>();
+
+ Map<DexType, DexType> typeMap = new IdentityHashMap<>();
+ BidirectionalManyToOneRepresentativeHashMap<DexField, DexField> fieldMap =
+ new BidirectionalManyToOneRepresentativeHashMap<>();
+ Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
+
+ protected final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
+ new BidirectionalOneToOneHashMap<>();
+
+ void moveSyntheticClass(DexType from, DexType to) {
+ assert !syntheticClassesMap.containsKey(from);
+ syntheticClassesMap.put(from, to);
+ typeMap.put(from, to);
+ }
+
+ void moveSyntheticMethod(DexMethod from, DexMethod to) {
+ assert !syntheticMethodsMap.containsKey(from);
+ syntheticMethodsMap.put(from, to);
+ methodMap.put(from, to);
+ }
+
+ void move(DexType from, DexType to) {
+ typeMap.put(from, to);
+ }
+
+ void move(DexField from, DexField to) {
+ fieldMap.put(from, to);
+ }
+
+ void move(DexMethod from, DexMethod to) {
+ methodMap.put(from, to);
+ originalMethodSignatures.put(to, from);
+ }
+
+ SyntheticFinalizationGraphLens build(GraphLens previous, DexItemFactory factory) {
+ assert verifySubMap(syntheticClassesMap, typeMap);
+ if (typeMap.isEmpty() && fieldMap.isEmpty() && methodMap.isEmpty()) {
+ return null;
+ }
+ return new SyntheticFinalizationGraphLens(
+ previous,
+ syntheticClassesMap,
+ syntheticMethodsMap,
+ typeMap,
+ fieldMap,
+ methodMap,
+ originalMethodSignatures,
+ factory);
+ }
+
+ private static <K, V> boolean verifySubMap(Map<K, V> sub, Map<K, V> sup) {
+ for (Entry<K, V> entry : sub.entrySet()) {
+ assert sup.get(entry.getKey()) == entry.getValue();
+ }
+ return true;
+ }
+ }
+
+ public static class EquivalenceGroup<T extends SyntheticDefinition<?, T>> {
+ private final List<T> members;
+
+ public EquivalenceGroup(T representative, List<T> members) {
assert !members.isEmpty();
assert members.get(0) == representative;
this.members = members;
}
- T getRepresentative() {
+ public T getRepresentative() {
return members.get(0);
}
@@ -70,89 +188,103 @@
return members;
}
+ public int compareToIncludingContext(EquivalenceGroup<T> other) {
+ return getRepresentative().compareTo(other.getRepresentative(), true);
+ }
+
+ public int compareTo(EquivalenceGroup<T> other, boolean includeContext) {
+ return getRepresentative().compareTo(other.getRepresentative(), includeContext);
+ }
+
@Override
- public int compareTo(EquivalenceGroup<T> other) {
- return getRepresentative().compareTo(other.getRepresentative());
+ public String toString() {
+ return "EquivalenceGroup{ members = "
+ + members.size()
+ + ", repr = "
+ + getRepresentative()
+ + " }";
}
}
private final InternalOptions options;
- private final ImmutableSet<DexType> legacySyntheticTypes;
- private final ImmutableMap<DexType, SyntheticReference> syntheticItems;
+ private final CommittedSyntheticsCollection synthetics;
- SyntheticFinalization(
- InternalOptions options,
- ImmutableSet<DexType> legacySyntheticTypes,
- ImmutableMap<DexType, SyntheticReference> syntheticItems) {
+ SyntheticFinalization(InternalOptions options, CommittedSyntheticsCollection synthetics) {
this.options = options;
- this.legacySyntheticTypes = legacySyntheticTypes;
- this.syntheticItems = syntheticItems;
+ this.synthetics = synthetics;
}
public Result computeFinalSynthetics(AppView<?> appView) {
assert verifyNoNestedSynthetics();
- DexApplication application = appView.appInfo().app();
+ DexApplication application;
MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
- GraphLens graphLens = appView.graphLens();
-
- Map<DexType, SyntheticMethodDefinition> methodDefinitions =
- lookupSyntheticMethodDefinitions(application);
-
- Collection<List<SyntheticMethodDefinition>> potentialEquivalences =
- computePotentialEquivalences(methodDefinitions, options.intermediate);
-
- Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> equivalences =
- computeActualEquivalences(potentialEquivalences, options.intermediate, options.itemFactory);
-
- Builder lensBuilder = NestedGraphLens.builder();
- List<DexProgramClass> newProgramClasses = new ArrayList<>();
List<DexProgramClass> finalSyntheticClasses = new ArrayList<>();
- Set<DexType> derivedMainDexTypesToIgnore = Sets.newIdentityHashSet();
- buildLensAndProgram(
- appView,
- equivalences,
- syntheticItems::containsKey,
- mainDexClasses,
- lensBuilder,
- newProgramClasses,
- finalSyntheticClasses,
- derivedMainDexTypesToIgnore);
-
- newProgramClasses.addAll(finalSyntheticClasses);
+ Builder lensBuilder = new Builder();
+ {
+ Map<DexType, NumberGenerator> generators = new IdentityHashMap<>();
+ application =
+ buildLensAndProgram(
+ appView,
+ computeEquivalences(appView, synthetics.getNonLegacyMethods().values(), generators),
+ computeEquivalences(appView, synthetics.getNonLegacyClasses().values(), generators),
+ mainDexClasses,
+ lensBuilder,
+ finalSyntheticClasses);
+ }
handleSynthesizedClassMapping(
- finalSyntheticClasses, application, options, mainDexClasses, derivedMainDexTypesToIgnore);
+ finalSyntheticClasses, application, options, mainDexClasses, lensBuilder.typeMap);
- DexApplication app = application.builder().replaceProgramClasses(newProgramClasses).build();
-
- appView.setGraphLens(lensBuilder.build(options.itemFactory, graphLens));
assert appView.appInfo().getMainDexClasses() == mainDexClasses;
Set<DexType> finalSyntheticTypes = Sets.newIdentityHashSet();
finalSyntheticClasses.forEach(clazz -> finalSyntheticTypes.add(clazz.getType()));
Set<DexType> prunedSynthetics = Sets.newIdentityHashSet();
- for (DexType type : syntheticItems.keySet()) {
- if (!finalSyntheticTypes.contains(type)) {
- prunedSynthetics.add(type);
- }
- }
+ synthetics.forEachNonLegacyItem(
+ reference -> {
+ DexType type = reference.getHolder();
+ if (!finalSyntheticTypes.contains(type)) {
+ prunedSynthetics.add(type);
+ }
+ });
return new Result(
new CommittedItems(
SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
- app,
- legacySyntheticTypes,
- ImmutableMap.of(),
+ application,
+ new CommittedSyntheticsCollection(
+ synthetics.getLegacyTypes(), ImmutableMap.of(), ImmutableMap.of()),
ImmutableList.of()),
- PrunedItems.builder().setPrunedApp(app).addRemovedClasses(prunedSynthetics).build());
+ lensBuilder.build(appView.graphLens(), appView.dexItemFactory()),
+ PrunedItems.builder()
+ .setPrunedApp(application)
+ .addRemovedClasses(prunedSynthetics)
+ .build());
+ }
+
+ private <R extends SyntheticReference<R, D>, D extends SyntheticDefinition<R, D>>
+ Map<DexType, EquivalenceGroup<D>> computeEquivalences(
+ AppView<?> appView,
+ ImmutableCollection<R> references,
+ Map<DexType, NumberGenerator> generators) {
+ boolean intermediate = appView.options().intermediate;
+ Map<DexType, D> definitions = lookupDefinitions(appView, references);
+ Collection<List<D>> potentialEquivalences =
+ computePotentialEquivalences(definitions, intermediate, appView.dexItemFactory());
+ return computeActualEquivalences(potentialEquivalences, generators, appView, intermediate);
+ }
+
+ private boolean isNotSyntheticType(DexType type) {
+ return !synthetics.containsNonLegacyType(type);
}
private boolean verifyNoNestedSynthetics() {
// Check that a context is never itself synthetic class.
- for (SyntheticReference item : syntheticItems.values()) {
- assert !syntheticItems.containsKey(item.getContext().getSynthesizingContextType());
- }
+ synthetics.forEachNonLegacyItem(
+ item -> {
+ assert isNotSyntheticType(item.getContext().getSynthesizingContextType());
+ });
return true;
}
@@ -161,7 +293,7 @@
DexApplication application,
InternalOptions options,
MainDexClasses mainDexClasses,
- Set<DexType> derivedMainDexTypesToIgnore) {
+ Map<DexType, DexType> derivedMainDexTypesToIgnore) {
boolean includeSynthesizedClassMappingInOutput = shouldAnnotateSynthetics(options);
if (includeSynthesizedClassMappingInOutput) {
updateSynthesizedClassMapping(application, finalSyntheticClasses);
@@ -177,7 +309,7 @@
DexApplication application, List<DexProgramClass> finalSyntheticClasses) {
ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized =
ArrayListMultimap.create();
- for (DexType type : legacySyntheticTypes) {
+ for (DexType type : synthetics.getLegacyTypes()) {
DexProgramClass clazz = DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
if (clazz != null) {
for (DexProgramClass origin : clazz.getSynthesizedFrom()) {
@@ -214,7 +346,7 @@
private void updateMainDexListWithSynthesizedClassMap(
DexApplication application,
MainDexClasses mainDexClasses,
- Set<DexType> derivedMainDexTypesToIgnore) {
+ Map<DexType, DexType> derivedMainDexTypesToIgnore) {
if (mainDexClasses.isEmpty()) {
return;
}
@@ -228,12 +360,11 @@
DexAnnotation.readAnnotationSynthesizedClassMap(
programClass, application.dexItemFactory);
for (DexType type : derived) {
- if (!derivedMainDexTypesToIgnore.contains(type)) {
- DexProgramClass syntheticClass =
- DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
- if (syntheticClass != null) {
- newMainDexClasses.add(syntheticClass);
- }
+ DexType mappedType = derivedMainDexTypesToIgnore.getOrDefault(type, type);
+ DexProgramClass syntheticClass =
+ DexProgramClass.asProgramClassOrNull(application.definitionFor(mappedType));
+ if (syntheticClass != null) {
+ newMainDexClasses.add(syntheticClass);
}
}
}
@@ -248,24 +379,16 @@
}
}
- private static void buildLensAndProgram(
+ private static DexApplication buildLensAndProgram(
AppView<?> appView,
Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
- Predicate<DexType> isSyntheticType,
+ Map<DexType, EquivalenceGroup<SyntheticClassDefinition>> syntheticClassGroups,
MainDexClasses mainDexClasses,
Builder lensBuilder,
- List<DexProgramClass> normalClasses,
- List<DexProgramClass> newSyntheticClasses,
- Set<DexType> derivedMainDexTypesToIgnore) {
+ List<DexProgramClass> newSyntheticClasses) {
+ DexApplication application = appView.appInfo().app();
DexItemFactory factory = appView.dexItemFactory();
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (!isSyntheticType.test(clazz.type)) {
- assert SyntheticItems.verifyNotInternalSynthetic(clazz.type);
- normalClasses.add(clazz);
- }
- }
-
// TODO(b/168584485): Remove this once class-mapping support is removed.
Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet();
mainDexClasses.forEach(
@@ -280,60 +403,178 @@
}
});
+ Set<DexType> pruned = Sets.newIdentityHashSet();
syntheticMethodGroups.forEach(
(syntheticType, syntheticGroup) -> {
SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
SynthesizingContext context = representative.getContext();
context.registerPrefixRewriting(syntheticType, appView);
- SyntheticClassBuilder builder =
- new SyntheticClassBuilder(syntheticType, context, factory);
- // TODO(b/158159959): Support grouping multiple methods per synthetic class.
- builder.addMethod(
- methodBuilder -> {
- DexEncodedMethod definition = representative.getMethod().getDefinition();
- methodBuilder
- .setAccessFlags(definition.accessFlags)
- .setProto(definition.getProto())
- .setClassFileVersion(
- definition.hasClassFileVersion() ? definition.getClassFileVersion() : null)
- .setCode(m -> definition.getCode());
- });
- DexProgramClass externalSyntheticClass = builder.build();
- if (shouldAnnotateSynthetics(appView.options())) {
- externalSyntheticClass.setAnnotations(
- externalSyntheticClass
- .annotations()
- .getWithAddedOrReplaced(
- DexAnnotation.createAnnotationSynthesizedClass(
- context.getSynthesizingContextType(), factory)));
- }
+ DexProgramClass externalSyntheticClass =
+ createExternalMethodClass(syntheticType, representative, factory);
+ newSyntheticClasses.add(externalSyntheticClass);
+ addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
assert externalSyntheticClass.getMethodCollection().size() == 1;
DexEncodedMethod externalSyntheticMethod =
externalSyntheticClass.methods().iterator().next();
- newSyntheticClasses.add(externalSyntheticClass);
for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
- if (member.getMethod().getReference() != externalSyntheticMethod.method) {
- lensBuilder.map(member.getMethod().getReference(), externalSyntheticMethod.method);
- }
- member
- .getContext()
- .addIfDerivedFromMainDexClass(
- externalSyntheticClass,
- mainDexClasses,
- derivedMainDexTypes,
- derivedMainDexTypesToIgnore);
- // TODO(b/168584485): Remove this once class-mapping support is removed.
- DexProgramClass from =
- DexProgramClass.asProgramClassOrNull(
- appView
- .appInfo()
- .definitionForWithoutExistenceAssert(
- member.getContext().getSynthesizingContextType()));
- if (from != null) {
- externalSyntheticClass.addSynthesizedFrom(from);
+ DexMethod memberReference = member.getMethod().getReference();
+ pruned.add(member.getHolder().getType());
+ if (memberReference != externalSyntheticMethod.method) {
+ lensBuilder.moveSyntheticMethod(memberReference, externalSyntheticMethod.method);
}
}
});
+
+ List<DexProgramClass> deduplicatedClasses = new ArrayList<>();
+ syntheticClassGroups.forEach(
+ (syntheticType, syntheticGroup) -> {
+ SyntheticClassDefinition representative = syntheticGroup.getRepresentative();
+ SynthesizingContext context = representative.getContext();
+ context.registerPrefixRewriting(syntheticType, appView);
+ DexProgramClass externalSyntheticClass = representative.getProgramClass();
+ newSyntheticClasses.add(externalSyntheticClass);
+ addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
+ for (SyntheticClassDefinition member : syntheticGroup.getMembers()) {
+ DexProgramClass memberClass = member.getProgramClass();
+ DexType memberType = memberClass.getType();
+ pruned.add(memberType);
+ if (memberType != syntheticType) {
+ lensBuilder.moveSyntheticClass(memberType, syntheticType);
+ }
+ // The aliasing of the non-representative members needs to be recorded manually.
+ if (member != representative) {
+ deduplicatedClasses.add(memberClass);
+ }
+ }
+ });
+
+ List<DexProgramClass> newProgramClasses = new ArrayList<>(newSyntheticClasses);
+ for (DexProgramClass clazz : application.classes()) {
+ if (!pruned.contains(clazz.type)) {
+ newProgramClasses.add(clazz);
+ }
+ }
+ application = application.builder().replaceProgramClasses(newProgramClasses).build();
+
+ // We can only assert that the method container classes are in here as the classes need
+ // to be rewritten by the tree-fixer.
+ for (DexType key : syntheticMethodGroups.keySet()) {
+ assert application.definitionFor(key) != null;
+ }
+
+ newSyntheticClasses.clear();
+
+ DexApplication.Builder<?> builder = application.builder();
+ TreeFixerBase treeFixer =
+ new TreeFixerBase(appView) {
+ @Override
+ public DexType mapClassType(DexType type) {
+ return lensBuilder.syntheticClassesMap.getOrDefault(type, type);
+ }
+
+ @Override
+ public void recordFieldChange(DexField from, DexField to) {
+ lensBuilder.move(from, to);
+ }
+
+ @Override
+ public void recordMethodChange(DexMethod from, DexMethod to) {
+ lensBuilder.move(from, to);
+ }
+
+ @Override
+ public void recordClassChange(DexType from, DexType to) {
+ lensBuilder.move(from, to);
+ }
+ };
+ treeFixer.fixupClasses(deduplicatedClasses);
+ builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes()));
+ application = builder.build();
+
+ // Add the synthesized from after repackaging which changed class definitions.
+ final DexApplication appForLookup = application;
+ syntheticClassGroups.forEach(
+ (syntheticType, syntheticGroup) -> {
+ DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+ newSyntheticClasses.add(externalSyntheticClass);
+ for (SyntheticClassDefinition member : syntheticGroup.getMembers()) {
+ addMainDexAndSynthesizedFromForMember(
+ member,
+ externalSyntheticClass,
+ mainDexClasses,
+ derivedMainDexTypes,
+ appForLookup::programDefinitionFor);
+ }
+ });
+ syntheticMethodGroups.forEach(
+ (syntheticType, syntheticGroup) -> {
+ DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+ newSyntheticClasses.add(externalSyntheticClass);
+ for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
+ addMainDexAndSynthesizedFromForMember(
+ member,
+ externalSyntheticClass,
+ mainDexClasses,
+ derivedMainDexTypes,
+ appForLookup::programDefinitionFor);
+ }
+ });
+
+ for (DexType key : syntheticMethodGroups.keySet()) {
+ assert application.definitionFor(key) != null;
+ }
+
+ for (DexType key : syntheticClassGroups.keySet()) {
+ assert application.definitionFor(key) != null;
+ }
+
+ return application;
+ }
+
+ private static void addSyntheticMarker(
+ SyntheticKind kind,
+ DexProgramClass externalSyntheticClass,
+ SynthesizingContext context,
+ AppView<?> appView) {
+ if (shouldAnnotateSynthetics(appView.options())) {
+ SyntheticMarker.addMarkerToClass(
+ externalSyntheticClass, kind, context, appView.dexItemFactory());
+ }
+ }
+
+ private static DexProgramClass createExternalMethodClass(
+ DexType syntheticType, SyntheticMethodDefinition representative, DexItemFactory factory) {
+ SyntheticClassBuilder builder =
+ new SyntheticClassBuilder(syntheticType, representative.getContext(), factory);
+ // TODO(b/158159959): Support grouping multiple methods per synthetic class.
+ builder.addMethod(
+ methodBuilder -> {
+ DexEncodedMethod definition = representative.getMethod().getDefinition();
+ methodBuilder
+ .setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)
+ .setAccessFlags(definition.accessFlags)
+ .setProto(definition.getProto())
+ .setClassFileVersion(
+ definition.hasClassFileVersion() ? definition.getClassFileVersion() : null)
+ .setCode(m -> definition.getCode());
+ });
+ return builder.build();
+ }
+
+ private static void addMainDexAndSynthesizedFromForMember(
+ SyntheticDefinition<?, ?> member,
+ DexProgramClass externalSyntheticClass,
+ MainDexClasses mainDexClasses,
+ Set<DexType> derivedMainDexTypes,
+ Function<DexType, DexProgramClass> definitions) {
+ member
+ .getContext()
+ .addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexClasses, derivedMainDexTypes);
+ // TODO(b/168584485): Remove this once class-mapping support is removed.
+ DexProgramClass from = definitions.apply(member.getContext().getSynthesizingContextType());
+ if (from != null) {
+ externalSyntheticClass.addSynthesizedFrom(from);
+ }
}
private static boolean shouldAnnotateSynthetics(InternalOptions options) {
@@ -345,9 +586,12 @@
return options.intermediate && !options.cfToCfDesugar;
}
- private static <T extends SyntheticDefinition & Comparable<T>>
+ private <T extends SyntheticDefinition<?, T>>
Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
- Collection<List<T>> potentialEquivalences, boolean intermediate, DexItemFactory factory) {
+ Collection<List<T>> potentialEquivalences,
+ Map<DexType, NumberGenerator> generators,
+ AppView<?> appView,
+ boolean intermediate) {
Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
potentialEquivalences.forEach(
members -> {
@@ -368,20 +612,24 @@
Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
groupsPerContext.forEach(
(context, groups) -> {
- groups.sort(EquivalenceGroup::compareTo);
+ // Sort the equivalence groups that go into 'context' including the context type of the
+ // representative which is equal to 'context' here (see assert below).
+ groups.sort(EquivalenceGroup::compareToIncludingContext);
for (int i = 0; i < groups.size(); i++) {
EquivalenceGroup<T> group = groups.get(i);
+ assert group.getRepresentative().getContext().getSynthesizingContextType() == context;
// Two equivalence groups in same context type must be distinct otherwise the assignment
// of the synthetic name will be non-deterministic between the two.
assert i == 0 || checkGroupsAreDistinct(groups.get(i - 1), group);
- DexType representativeType = createExternalType(context, i, factory);
+ SyntheticKind kind = group.members.get(0).getKind();
+ DexType representativeType = createExternalType(kind, context, generators, appView);
equivalences.put(representativeType, group);
}
});
return equivalences;
}
- private static <T extends SyntheticDefinition & Comparable<T>> List<List<T>> groupEquivalent(
+ private static <T extends SyntheticDefinition<?, T>> List<List<T>> groupEquivalent(
List<T> potentialEquivalence, boolean intermediate) {
List<List<T>> groups = new ArrayList<>();
// Each other member is in a shared group if it is actually equivalent to the first member.
@@ -403,42 +651,61 @@
return groups;
}
- private static <T extends SyntheticDefinition & Comparable<T>> boolean checkGroupsAreDistinct(
+ private static <T extends SyntheticDefinition<?, T>> boolean checkGroupsAreDistinct(
EquivalenceGroup<T> g1, EquivalenceGroup<T> g2) {
- assert g1.compareTo(g2) != 0;
+ int order = g1.compareToIncludingContext(g2);
+ assert order != 0;
+ assert order != g2.compareToIncludingContext(g1);
return true;
}
- private static <T extends SyntheticDefinition & Comparable<T>> T findDeterministicRepresentative(
+ private static <T extends SyntheticDefinition<?, T>> T findDeterministicRepresentative(
List<T> members) {
// Pick a deterministic member as representative.
T smallest = members.get(0);
for (int i = 1; i < members.size(); i++) {
T next = members.get(i);
- if (next.compareTo(smallest) < 0) {
+ if (next.compareTo(smallest, true) < 0) {
smallest = next;
}
}
return smallest;
}
- private static DexType createExternalType(
- DexType representativeContext, int nextContextId, DexItemFactory factory) {
- return factory.createType(
- DescriptorUtils.getDescriptorFromClassBinaryName(
- representativeContext.getInternalName()
- + SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR
- + nextContextId));
+ private DexType createExternalType(
+ SyntheticKind kind,
+ DexType representativeContext,
+ Map<DexType, NumberGenerator> generators,
+ AppView<?> appView) {
+ NumberGenerator generator =
+ generators.computeIfAbsent(representativeContext, k -> new NumberGenerator());
+ DexType externalType;
+ do {
+ externalType =
+ SyntheticNaming.createExternalType(
+ kind,
+ representativeContext,
+ Integer.toString(generator.next()),
+ appView.dexItemFactory());
+ DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(externalType);
+ if (clazz != null && isNotSyntheticType(clazz.type)) {
+ assert options.testing.allowConflictingSyntheticTypes
+ : "Unexpected creation of an existing external synthetic type: " + clazz;
+ externalType = null;
+ }
+ } while (externalType == null);
+ return externalType;
}
- private static <T extends SyntheticDefinition> Collection<List<T>> computePotentialEquivalences(
- Map<DexType, T> definitions, boolean intermediate) {
+ private static <T extends SyntheticDefinition<?, T>>
+ Collection<List<T>> computePotentialEquivalences(
+ Map<DexType, T> definitions, boolean intermediate, DexItemFactory factory) {
if (definitions.isEmpty()) {
return Collections.emptyList();
}
- Set<DexType> allTypes = definitions.keySet();
- DexType representative = allTypes.iterator().next();
- RepresentativeMap map = t -> allTypes.contains(t) ? representative : t;
+ // Map all synthetic types to the java 'void' type. This is not an actual valid type, so it
+ // cannot collide with any valid java type providing a good hashing key for the synthetics.
+ RepresentativeMap map = t -> definitions.containsKey(t) ? factory.voidType : t;
Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
for (T definition : definitions.values()) {
HashCode hash = definition.computeHash(map, intermediate);
@@ -447,25 +714,24 @@
return equivalences.values();
}
- private Map<DexType, SyntheticMethodDefinition> lookupSyntheticMethodDefinitions(
- DexApplication finalApp) {
- Map<DexType, SyntheticMethodDefinition> methods = new IdentityHashMap<>(syntheticItems.size());
- for (SyntheticReference reference : syntheticItems.values()) {
- SyntheticDefinition definition = reference.lookupDefinition(finalApp::definitionFor);
- if (definition == null || !(definition instanceof SyntheticMethodDefinition)) {
+ private <R extends SyntheticReference<R, D>, D extends SyntheticDefinition<R, D>>
+ Map<DexType, D> lookupDefinitions(AppView<?> appView, Collection<R> references) {
+ Map<DexType, D> definitions = new IdentityHashMap<>(references.size());
+ for (R reference : references) {
+ D definition = reference.lookupDefinition(appView::definitionFor);
+ if (definition == null) {
// We expect pruned definitions to have been removed.
assert false;
continue;
}
- SyntheticMethodDefinition method = (SyntheticMethodDefinition) definition;
- if (SyntheticMethodBuilder.isValidSyntheticMethod(method.getMethod().getDefinition())) {
- methods.put(method.getHolder().getType(), method);
+ if (definition.isValid()) {
+ definitions.put(reference.getHolder(), definition);
} else {
// Failing this check indicates that an optimization has modified the synthetic in a
// disruptive way.
assert false;
}
}
- return methods;
+ return definitions;
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index bc7d42e..179f990 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -6,12 +6,8 @@
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotation;
-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.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
@@ -21,11 +17,8 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.ir.conversion.MethodProcessingId;
import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSet.Builder;
-import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -35,148 +28,112 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
public class SyntheticItems implements SyntheticDefinitionsProvider {
static final int INVALID_ID_AFTER_SYNTHETIC_FINALIZATION = -1;
- /**
- * The internal synthetic class separator is only used for representing synthetic items during
- * compilation. In particular, this separator must never be used to write synthetic classes to the
- * final compilation result.
- */
- public static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$InternalSynthetic";
-
- /**
- * The external synthetic class separator is used when writing classes. It may appear in types
- * during compilation as the output of a compilation may be the input to another.
- */
- public static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$ExternalSynthetic";
-
- /** Method prefix when generating synthetic methods in a class. */
- public static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m";
-
- public static boolean verifyNotInternalSynthetic(DexType type) {
- assert !type.toDescriptorString().contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR);
- return true;
- }
-
/** Globally incremented id for the next internal synthetic class. */
private int nextSyntheticId;
- /**
- * Thread safe collection of synthesized classes that are not yet committed to the application.
- *
- * <p>TODO(b/158159959): Remove legacy support.
- */
- private final Map<DexType, DexProgramClass> legacyPendingClasses = new ConcurrentHashMap<>();
+ /** Collection of pending items. */
+ private static class PendingSynthetics {
+ /**
+ * Thread safe collection of synthesized classes that are not yet committed to the application.
+ *
+ * <p>TODO(b/158159959): Remove legacy support.
+ */
+ private final Map<DexType, DexProgramClass> legacyClasses = new ConcurrentHashMap<>();
- /**
- * Immutable set of synthetic types in the application (eg, committed).
- *
- * <p>TODO(b/158159959): Remove legacy support.
- */
- private final ImmutableSet<DexType> legacySyntheticTypes;
+ /** Thread safe collection of synthetic items not yet committed to the application. */
+ private final ConcurrentHashMap<DexType, SyntheticDefinition<?, ?>> nonLegacyDefinitions =
+ new ConcurrentHashMap<>();
- /** Thread safe collection of synthetic items not yet committed to the application. */
- private final ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions =
- new ConcurrentHashMap<>();
+ boolean isEmpty() {
+ return legacyClasses.isEmpty() && nonLegacyDefinitions.isEmpty();
+ }
- /** Mapping from synthetic type to its synthetic description. */
- private final ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems;
+ boolean containsType(DexType type) {
+ return legacyClasses.containsKey(type) || nonLegacyDefinitions.containsKey(type);
+ }
+
+ boolean verifyNotRewritten(NonIdentityGraphLens lens) {
+ assert legacyClasses.keySet().equals(lens.rewriteTypes(legacyClasses.keySet()));
+ assert nonLegacyDefinitions.keySet().equals(lens.rewriteTypes(nonLegacyDefinitions.keySet()));
+ return true;
+ }
+
+ Collection<DexProgramClass> getAllClasses() {
+ List<DexProgramClass> allPending =
+ new ArrayList<>(nonLegacyDefinitions.size() + legacyClasses.size());
+ for (SyntheticDefinition<?, ?> item : nonLegacyDefinitions.values()) {
+ allPending.add(item.getHolder());
+ }
+ allPending.addAll(legacyClasses.values());
+ return Collections.unmodifiableList(allPending);
+ }
+ }
+
+ private final CommittedSyntheticsCollection committed;
+
+ private final PendingSynthetics pending = new PendingSynthetics();
// Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
public static CommittedItems createInitialSyntheticItems(DexApplication application) {
return new CommittedItems(
- 0, application, ImmutableSet.of(), ImmutableMap.of(), ImmutableList.of());
+ 0, application, CommittedSyntheticsCollection.empty(), ImmutableList.of());
}
// Only for conversion to a mutable synthetic items collection.
SyntheticItems(CommittedItems commit) {
- this(commit.nextSyntheticId, commit.legacySyntheticTypes, commit.syntheticItems);
+ this(commit.nextSyntheticId, commit.committed);
}
- private SyntheticItems(
- int nextSyntheticId,
- ImmutableSet<DexType> legacySyntheticTypes,
- ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems) {
+ private SyntheticItems(int nextSyntheticId, CommittedSyntheticsCollection committed) {
this.nextSyntheticId = nextSyntheticId;
- this.legacySyntheticTypes = legacySyntheticTypes;
- this.nonLecacySyntheticItems = nonLecacySyntheticItems;
- assert Sets.intersection(nonLecacySyntheticItems.keySet(), legacySyntheticTypes).isEmpty();
+ this.committed = committed;
}
public static void collectSyntheticInputs(AppView<AppInfo> appView) {
// Collecting synthetic items must be the very first task after application build.
SyntheticItems synthetics = appView.getSyntheticItems();
assert synthetics.nextSyntheticId == 0;
- assert synthetics.nonLecacySyntheticItems.isEmpty();
- assert !synthetics.hasPendingSyntheticClasses();
+ assert synthetics.committed.isEmpty();
+ assert synthetics.pending.isEmpty();
if (appView.options().intermediate) {
// If the compilation is in intermediate mode the synthetics should just be passed through.
return;
}
- ImmutableMap.Builder<DexType, SyntheticReference> pending = ImmutableMap.builder();
+ CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
// TODO(b/158159959): Consider identifying synthetics in the input reader to speed this up.
for (DexProgramClass clazz : appView.appInfo().classes()) {
- DexType annotatedContextType = isSynthesizedMethodsContainer(clazz, appView.dexItemFactory());
- if (annotatedContextType == null) {
- continue;
+ SyntheticMarker marker =
+ SyntheticMarker.stripMarkerFromClass(clazz, appView.dexItemFactory());
+ if (marker.isSyntheticMethods()) {
+ clazz.forEachProgramMethod(
+ // TODO(b/158159959): Support having multiple methods per class.
+ method -> {
+ builder.addNonLegacyMethod(
+ new SyntheticMethodDefinition(marker.getKind(), marker.getContext(), method));
+ });
+ } else if (marker.isSyntheticClass()) {
+ builder.addNonLegacyClass(
+ new SyntheticClassDefinition(marker.getKind(), marker.getContext(), clazz));
}
- clazz.setAnnotations(DexAnnotationSet.empty());
- SynthesizingContext context =
- SynthesizingContext.fromSyntheticInputClass(clazz, annotatedContextType);
- clazz.forEachProgramMethod(
- // TODO(b/158159959): Support having multiple methods per class.
- method -> {
- method.getDefinition().setAnnotations(DexAnnotationSet.empty());
- pending.put(clazz.type, new SyntheticMethodDefinition(context, method).toReference());
- });
}
- pending.putAll(synthetics.nonLecacySyntheticItems);
- ImmutableMap<DexType, SyntheticReference> nonLegacySyntheticItems = pending.build();
- if (nonLegacySyntheticItems.isEmpty()) {
+ CommittedSyntheticsCollection committed = builder.build();
+ if (committed.isEmpty()) {
return;
}
CommittedItems commit =
new CommittedItems(
- synthetics.nextSyntheticId,
- appView.appInfo().app(),
- synthetics.legacySyntheticTypes,
- nonLegacySyntheticItems,
- ImmutableList.of());
+ synthetics.nextSyntheticId, appView.appInfo().app(), committed, ImmutableList.of());
appView.setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexClasses()));
}
- private static DexType isSynthesizedMethodsContainer(
- DexProgramClass clazz, DexItemFactory factory) {
- ClassAccessFlags flags = clazz.accessFlags;
- if (!flags.isSynthetic() || flags.isAbstract() || flags.isEnum()) {
- return null;
- }
- DexType contextType =
- DexAnnotation.getSynthesizedClassAnnotationContextType(clazz.annotations(), factory);
- if (contextType == null) {
- return null;
- }
- if (clazz.superType != factory.objectType) {
- return null;
- }
- if (!clazz.interfaces.isEmpty()) {
- return null;
- }
- if (clazz.annotations().size() != 1) {
- return null;
- }
- for (DexEncodedMethod method : clazz.methods()) {
- if (!SyntheticMethodBuilder.isValidSyntheticMethod(method)) {
- return null;
- }
- }
- return contextType;
- }
-
// Internal synthetic id creation helpers.
private synchronized String getNextSyntheticId() {
@@ -191,49 +148,52 @@
@Override
public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
- DexProgramClass pending = legacyPendingClasses.get(type);
- if (pending == null) {
- SyntheticDefinition item = pendingDefinitions.get(type);
+ DexProgramClass clazz = pending.legacyClasses.get(type);
+ if (clazz == null) {
+ SyntheticDefinition<?, ?> item = pending.nonLegacyDefinitions.get(type);
if (item != null) {
- pending = item.getHolder();
+ clazz = item.getHolder();
}
}
- if (pending != null) {
+ if (clazz != null) {
assert baseDefinitionFor.apply(type) == null
: "Pending synthetic definition also present in the active program: " + type;
- return pending;
+ return clazz;
}
return baseDefinitionFor.apply(type);
}
+ public boolean verifyNonLegacySyntheticsAreCommitted() {
+ assert pending.nonLegacyDefinitions.isEmpty()
+ : "Uncommitted synthetics: "
+ + pending.nonLegacyDefinitions.keySet().stream()
+ .map(DexType::getName)
+ .collect(Collectors.joining());
+ return true;
+ }
+
public boolean hasPendingSyntheticClasses() {
- return !legacyPendingClasses.isEmpty() || !pendingDefinitions.isEmpty();
+ return !pending.isEmpty();
}
public Collection<DexProgramClass> getPendingSyntheticClasses() {
- List<DexProgramClass> pending =
- new ArrayList<>(pendingDefinitions.size() + legacyPendingClasses.size());
- for (SyntheticDefinition item : pendingDefinitions.values()) {
- pending.add(item.getHolder());
- }
- pending.addAll(legacyPendingClasses.values());
- return Collections.unmodifiableList(pending);
+ return pending.getAllClasses();
}
private boolean isCommittedSynthetic(DexType type) {
- return nonLecacySyntheticItems.containsKey(type) || legacySyntheticTypes.contains(type);
+ return committed.containsType(type);
}
private boolean isLegacyCommittedSynthetic(DexType type) {
- return legacySyntheticTypes.contains(type);
+ return committed.containsLegacyType(type);
}
public boolean isPendingSynthetic(DexType type) {
- return pendingDefinitions.containsKey(type) || legacyPendingClasses.containsKey(type);
+ return pending.containsType(type);
}
public boolean isLegacyPendingSynthetic(DexType type) {
- return legacyPendingClasses.containsKey(type);
+ return pending.legacyClasses.containsKey(type);
}
public boolean isSyntheticClass(DexType type) {
@@ -247,6 +207,27 @@
return isSyntheticClass(clazz.type);
}
+ // The compiler should not inspect the kind of a synthetic, so this provided only as a assertion
+ // utility.
+ public boolean verifySyntheticLambdaProperty(
+ DexProgramClass clazz,
+ Predicate<DexProgramClass> ifIsLambda,
+ Predicate<DexProgramClass> ifNotLambda) {
+ SyntheticReference<?, ?> reference = committed.getNonLegacyItem(clazz.getType());
+ if (reference == null) {
+ SyntheticDefinition<?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
+ if (definition != null) {
+ reference = definition.toReference();
+ }
+ }
+ if (reference != null && reference.getKind() == SyntheticKind.LAMBDA) {
+ assert ifIsLambda.test(clazz);
+ } else {
+ assert ifNotLambda.test(clazz);
+ }
+ return true;
+ }
+
public boolean isLegacySyntheticClass(DexType type) {
return isLegacyCommittedSynthetic(type) || isLegacyPendingSynthetic(type);
}
@@ -256,18 +237,21 @@
}
public Collection<DexProgramClass> getLegacyPendingClasses() {
- return Collections.unmodifiableCollection(legacyPendingClasses.values());
+ return Collections.unmodifiableCollection(pending.legacyClasses.values());
}
private SynthesizingContext getSynthesizingContext(ProgramDefinition context) {
- SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.getContextType());
- if (pendingItemContext != null) {
- return pendingItemContext.getContext();
+ DexType contextType = context.getContextType();
+ SyntheticDefinition<?, ?> existingDefinition = pending.nonLegacyDefinitions.get(contextType);
+ if (existingDefinition != null) {
+ return existingDefinition.getContext();
}
- SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.getContextType());
- return committedItemContext != null
- ? committedItemContext.getContext()
- : SynthesizingContext.fromNonSyntheticInputContext(context);
+ SyntheticReference<?, ?> existingReference = committed.getNonLegacyItem(contextType);
+ if (existingReference != null) {
+ return existingReference.getContext();
+ }
+ // This context is not nested in an existing synthetic context so create a new "leaf" context.
+ return SynthesizingContext.fromNonSyntheticInputContext(context);
}
// Addition and creation of synthetic items.
@@ -276,25 +260,49 @@
public void addLegacySyntheticClass(DexProgramClass clazz) {
assert clazz.type.isD8R8SynthesizedClassType();
assert !isCommittedSynthetic(clazz.type);
- DexProgramClass previous = legacyPendingClasses.put(clazz.type, clazz);
+ assert !pending.nonLegacyDefinitions.containsKey(clazz.type);
+ DexProgramClass previous = pending.legacyClasses.put(clazz.type, clazz);
assert previous == null || previous == clazz;
}
+ public DexProgramClass createClass(
+ SyntheticKind kind,
+ DexProgramClass context,
+ DexItemFactory factory,
+ Consumer<SyntheticClassBuilder> fn) {
+ // Obtain the outer synthesizing context in the case the context itself is synthetic.
+ // This is to ensure a flat input-type -> synthetic-item mapping.
+ SynthesizingContext outerContext = getSynthesizingContext(context);
+ DexType type =
+ SyntheticNaming.createInternalType(kind, outerContext, getNextSyntheticId(), factory);
+ SyntheticClassBuilder classBuilder = new SyntheticClassBuilder(type, outerContext, factory);
+ fn.accept(classBuilder);
+ DexProgramClass clazz = classBuilder.build();
+ addPendingDefinition(new SyntheticClassDefinition(kind, outerContext, clazz));
+ return clazz;
+ }
+
/** Create a single synthetic method item. */
public ProgramMethod createMethod(
- ProgramDefinition context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) {
- return createMethod(context, factory, fn, this::getNextSyntheticId);
+ SyntheticKind kind,
+ ProgramDefinition context,
+ DexItemFactory factory,
+ Consumer<SyntheticMethodBuilder> fn) {
+ return createMethod(kind, context, factory, fn, this::getNextSyntheticId);
}
public ProgramMethod createMethod(
+ SyntheticKind kind,
ProgramDefinition context,
DexItemFactory factory,
Consumer<SyntheticMethodBuilder> fn,
MethodProcessingId methodProcessingId) {
- return createMethod(context, factory, fn, methodProcessingId::getFullyQualifiedIdAndIncrement);
+ return createMethod(
+ kind, context, factory, fn, methodProcessingId::getFullyQualifiedIdAndIncrement);
}
private ProgramMethod createMethod(
+ SyntheticKind kind,
ProgramDefinition context,
DexItemFactory factory,
Consumer<SyntheticMethodBuilder> fn,
@@ -303,16 +311,20 @@
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
SynthesizingContext outerContext = getSynthesizingContext(context);
- DexType type = outerContext.createHygienicType(syntheticIdSupplier.get(), factory);
+ DexType type =
+ SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), factory);
SyntheticClassBuilder classBuilder = new SyntheticClassBuilder(type, outerContext, factory);
- DexProgramClass clazz = classBuilder.addMethod(fn).build();
+ DexProgramClass clazz =
+ classBuilder
+ .addMethod(fn.andThen(m -> m.setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)))
+ .build();
ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next());
- addPendingDefinition(new SyntheticMethodDefinition(outerContext, method));
+ addPendingDefinition(new SyntheticMethodDefinition(kind, outerContext, method));
return method;
}
- private void addPendingDefinition(SyntheticDefinition definition) {
- pendingDefinitions.put(definition.getHolder().getType(), definition);
+ private void addPendingDefinition(SyntheticDefinition<?, ?> definition) {
+ pending.nonLegacyDefinitions.put(definition.getHolder().getType(), definition);
}
// Commit of the synthetic items to a new fully populated application.
@@ -322,125 +334,48 @@
}
public CommittedItems commitPrunedItems(PrunedItems prunedItems) {
- return commit(
- prunedItems.getPrunedApp(),
- prunedItems.getNoLongerSyntheticItems(),
- legacyPendingClasses,
- legacySyntheticTypes,
- pendingDefinitions,
- nonLecacySyntheticItems,
- nextSyntheticId);
+ return commit(prunedItems, pending, committed, nextSyntheticId);
}
public CommittedItems commitRewrittenWithLens(
DexApplication application, NonIdentityGraphLens lens) {
- // Rewrite the previously committed synthetic types.
- ImmutableSet<DexType> rewrittenLegacyTypes = lens.rewriteTypes(this.legacySyntheticTypes);
- ImmutableMap.Builder<DexType, SyntheticReference> rewrittenItems = ImmutableMap.builder();
- for (SyntheticReference reference : nonLecacySyntheticItems.values()) {
- SyntheticReference rewritten = reference.rewrite(lens);
- if (rewritten != null) {
- rewrittenItems.put(rewritten.getHolder(), rewritten);
- }
- }
- // No pending item should need rewriting.
- assert legacyPendingClasses.keySet().equals(lens.rewriteTypes(legacyPendingClasses.keySet()));
- assert pendingDefinitions.keySet().equals(lens.rewriteTypes(pendingDefinitions.keySet()));
+ assert pending.verifyNotRewritten(lens);
return commit(
- application,
- Collections.emptySet(),
- legacyPendingClasses,
- rewrittenLegacyTypes,
- pendingDefinitions,
- rewrittenItems.build(),
- nextSyntheticId);
+ PrunedItems.empty(application), pending, committed.rewriteWithLens(lens), nextSyntheticId);
}
private static CommittedItems commit(
- DexApplication application,
- Set<DexType> removedClasses,
- Map<DexType, DexProgramClass> legacyPendingClasses,
- ImmutableSet<DexType> legacySyntheticTypes,
- ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions,
- ImmutableMap<DexType, SyntheticReference> syntheticItems,
+ PrunedItems prunedItems,
+ PendingSynthetics pending,
+ CommittedSyntheticsCollection committed,
int nextSyntheticId) {
- // Legacy synthetics must already have been committed.
- assert verifyClassesAreInApp(application, legacyPendingClasses.values());
- // Add the set of legacy definitions to the synthetic types.
- ImmutableSet<DexType> mergedLegacyTypes = legacySyntheticTypes;
- if (!legacyPendingClasses.isEmpty() || !removedClasses.isEmpty()) {
- ImmutableSet.Builder<DexType> legacyBuilder = ImmutableSet.builder();
- filteredAdd(legacySyntheticTypes, removedClasses, legacyBuilder);
- filteredAdd(legacyPendingClasses.keySet(), removedClasses, legacyBuilder);
- mergedLegacyTypes = legacyBuilder.build();
- }
- // The set of synthetic items is the union of the previous types plus the pending additions.
- ImmutableMap<DexType, SyntheticReference> mergedItems;
+ DexApplication application = prunedItems.getPrunedApp();
+ Set<DexType> removedClasses = prunedItems.getNoLongerSyntheticItems();
+ CommittedSyntheticsCollection.Builder builder = committed.builder();
+ // Legacy synthetics must already have been committed to the app.
+ assert verifyClassesAreInApp(application, pending.legacyClasses.values());
+ builder.addLegacyClasses(pending.legacyClasses.values());
+ // Compute the synthetic additions and add them to the application.
ImmutableList<DexType> additions;
DexApplication amendedApplication;
- if (pendingDefinitions.isEmpty()) {
- mergedItems = filteredCopy(syntheticItems, removedClasses);
+ if (pending.nonLegacyDefinitions.isEmpty()) {
additions = ImmutableList.of();
amendedApplication = application;
} else {
DexApplication.Builder<?> appBuilder = application.builder();
- ImmutableMap.Builder<DexType, SyntheticReference> itemsBuilder = ImmutableMap.builder();
ImmutableList.Builder<DexType> additionsBuilder = ImmutableList.builder();
- for (SyntheticDefinition definition : pendingDefinitions.values()) {
- if (removedClasses.contains(definition.getHolder().getType())) {
- continue;
+ for (SyntheticDefinition<?, ?> definition : pending.nonLegacyDefinitions.values()) {
+ if (!removedClasses.contains(definition.getHolder().getType())) {
+ additionsBuilder.add(definition.getHolder().getType());
+ appBuilder.addProgramClass(definition.getHolder());
+ builder.addItem(definition);
}
- SyntheticReference reference = definition.toReference();
- itemsBuilder.put(reference.getHolder(), reference);
- additionsBuilder.add(definition.getHolder().getType());
- appBuilder.addProgramClass(definition.getHolder());
}
- filteredAdd(syntheticItems, removedClasses, itemsBuilder);
- mergedItems = itemsBuilder.build();
additions = additionsBuilder.build();
amendedApplication = appBuilder.build();
}
return new CommittedItems(
- nextSyntheticId, amendedApplication, mergedLegacyTypes, mergedItems, additions);
- }
-
- private static void filteredAdd(
- Set<DexType> input, Set<DexType> excludeSet, Builder<DexType> result) {
- if (excludeSet.isEmpty()) {
- result.addAll(input);
- } else {
- for (DexType type : input) {
- if (!excludeSet.contains(type)) {
- result.add(type);
- }
- }
- }
- }
-
- private static ImmutableMap<DexType, SyntheticReference> filteredCopy(
- ImmutableMap<DexType, SyntheticReference> syntheticItems, Set<DexType> removedClasses) {
- if (removedClasses.isEmpty()) {
- return syntheticItems;
- }
- ImmutableMap.Builder<DexType, SyntheticReference> builder = ImmutableMap.builder();
- filteredAdd(syntheticItems, removedClasses, builder);
- return builder.build();
- }
-
- private static void filteredAdd(
- ImmutableMap<DexType, SyntheticReference> syntheticItems,
- Set<DexType> removedClasses,
- ImmutableMap.Builder<DexType, SyntheticReference> builder) {
- if (removedClasses.isEmpty()) {
- builder.putAll(syntheticItems);
- } else {
- syntheticItems.forEach(
- (t, r) -> {
- if (!removedClasses.contains(t)) {
- builder.put(t, r);
- }
- });
- }
+ nextSyntheticId, amendedApplication, builder.build().pruneItems(prunedItems), additions);
}
private static boolean verifyClassesAreInApp(
@@ -455,8 +390,6 @@
public Result computeFinalSynthetics(AppView<?> appView) {
assert !hasPendingSyntheticClasses();
- return new SyntheticFinalization(
- appView.options(), legacySyntheticTypes, nonLecacySyntheticItems)
- .computeFinalSynthetics(appView);
+ return new SyntheticFinalization(appView.options(), committed).computeFinalSynthetics(appView);
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
new file mode 100644
index 0000000..03b7e36
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.Pair;
+
+public class SyntheticMarker {
+
+ public static void addMarkerToClass(
+ DexProgramClass clazz,
+ SyntheticKind kind,
+ SynthesizingContext context,
+ DexItemFactory factory) {
+ clazz.setAnnotations(
+ clazz
+ .annotations()
+ .getWithAddedOrReplaced(
+ DexAnnotation.createAnnotationSynthesizedClass(
+ kind, context.getSynthesizingContextType(), factory)));
+ }
+
+ public static SyntheticMarker stripMarkerFromClass(
+ DexProgramClass clazz, DexItemFactory factory) {
+ SyntheticMarker marker = internalStripMarkerFromClass(clazz, factory);
+ assert marker != NO_MARKER
+ || DexAnnotation.getSynthesizedClassAnnotationContextType(clazz.annotations(), factory)
+ == null;
+ return marker;
+ }
+
+ private static SyntheticMarker internalStripMarkerFromClass(
+ DexProgramClass clazz, DexItemFactory factory) {
+ ClassAccessFlags flags = clazz.accessFlags;
+ if (clazz.superType != factory.objectType) {
+ return NO_MARKER;
+ }
+ if (!flags.isSynthetic() || flags.isAbstract() || flags.isEnum()) {
+ return NO_MARKER;
+ }
+ Pair<SyntheticKind, DexType> info =
+ DexAnnotation.getSynthesizedClassAnnotationContextType(clazz.annotations(), factory);
+ if (info == null) {
+ return NO_MARKER;
+ }
+ assert clazz.annotations().size() == 1;
+ SyntheticKind kind = info.getFirst();
+ DexType context = info.getSecond();
+ if (kind.isSingleSyntheticMethod) {
+ if (!clazz.interfaces.isEmpty()) {
+ return NO_MARKER;
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (!SyntheticMethodBuilder.isValidSyntheticMethod(method)) {
+ return NO_MARKER;
+ }
+ }
+ }
+ clazz.setAnnotations(DexAnnotationSet.empty());
+ return new SyntheticMarker(kind, SynthesizingContext.fromSyntheticInputClass(clazz, context));
+ }
+
+ private static final SyntheticMarker NO_MARKER = new SyntheticMarker(null, null);
+
+ private final SyntheticKind kind;
+ private final SynthesizingContext context;
+
+ public SyntheticMarker(SyntheticKind kind, SynthesizingContext context) {
+ this.kind = kind;
+ this.context = context;
+ }
+
+ public boolean isSyntheticMethods() {
+ return kind != null && kind.isSingleSyntheticMethod;
+ }
+
+ public boolean isSyntheticClass() {
+ return kind != null && !kind.isSingleSyntheticMethod;
+ }
+
+ public SyntheticKind getKind() {
+ return kind;
+ }
+
+ public SynthesizingContext getContext() {
+ return context;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index 9fcc7d9..60e3079 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -20,15 +21,25 @@
}
private final SyntheticClassBuilder parent;
- private final String name;
+ private DexString name = null;
private DexProto proto = null;
private CfVersion classFileVersion;
private SyntheticCodeGenerator codeGenerator = null;
private MethodAccessFlags accessFlags = null;
- SyntheticMethodBuilder(SyntheticClassBuilder parent, String name) {
+ SyntheticMethodBuilder(SyntheticClassBuilder parent) {
this.parent = parent;
+ }
+
+ public SyntheticMethodBuilder setName(String name) {
+ return setName(parent.getFactory().createString(name));
+ }
+
+ public SyntheticMethodBuilder setName(DexString name) {
+ assert name != null;
+ assert this.name == null;
this.name = name;
+ return this;
}
public SyntheticMethodBuilder setProto(DexProto proto) {
@@ -52,6 +63,7 @@
}
DexEncodedMethod build() {
+ assert name != null;
boolean isCompilerSynthesized = true;
DexMethod methodSignature = getMethodSignature();
DexEncodedMethod method =
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index ecc1a81..0ba1127 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -3,27 +3,24 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.synthesis;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.structural.RepresentativeMap;
-import com.google.common.hash.HashCode;
import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
-import java.util.Comparator;
/**
* Definition of a synthetic method item.
*
* <p>This class is internal to the synthetic items collection, thus package-protected.
*/
-class SyntheticMethodDefinition extends SyntheticDefinition
- implements Comparable<SyntheticMethodDefinition> {
+class SyntheticMethodDefinition
+ extends SyntheticDefinition<SyntheticMethodReference, SyntheticMethodDefinition> {
private final ProgramMethod method;
- SyntheticMethodDefinition(SynthesizingContext context, ProgramMethod method) {
- super(context);
+ SyntheticMethodDefinition(SyntheticKind kind, SynthesizingContext context, ProgramMethod method) {
+ super(kind, context);
this.method = method;
}
@@ -32,8 +29,8 @@
}
@Override
- SyntheticReference toReference() {
- return new SyntheticMethodReference(getContext(), method.getReference());
+ SyntheticMethodReference toReference() {
+ return new SyntheticMethodReference(getKind(), getContext(), method.getReference());
}
@Override
@@ -42,39 +39,18 @@
}
@Override
- HashCode computeHash(RepresentativeMap map, boolean intermediate) {
- Hasher hasher = Hashing.sha256().newHasher();
- if (intermediate) {
- // If in intermediate mode, include the context type as sharing is restricted to within a
- // single context.
- hasher.putInt(getContext().getSynthesizingContextType().hashCode());
- }
- method.getDefinition().hashSyntheticContent(hasher, map);
- return hasher.hash();
+ void internalComputeHash(Hasher hasher, RepresentativeMap map) {
+ method.getDefinition().hashWithTypeEquivalence(hasher, map);
}
@Override
- boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate) {
- if (!(other instanceof SyntheticMethodDefinition)) {
- return false;
- }
- if (intermediate
- && getContext().getSynthesizingContextType()
- != other.getContext().getSynthesizingContextType()) {
- // If in intermediate mode, only synthetics within the same context should be considered
- // equal.
- return false;
- }
- SyntheticMethodDefinition o = (SyntheticMethodDefinition) other;
- return method.getDefinition().isSyntheticContentEqual(o.method.getDefinition());
+ int internalCompareTo(SyntheticMethodDefinition other, RepresentativeMap map) {
+ return method.getDefinition().compareWithTypeEquivalenceTo(other.method.getDefinition(), map);
}
- // Since methods are sharable they must define an order from which representatives can be found.
@Override
- public int compareTo(SyntheticMethodDefinition other) {
- return Comparator.comparing(SyntheticMethodDefinition::getContext)
- .thenComparing(m -> m.method.getDefinition(), DexEncodedMethod::syntheticCompareTo)
- .compare(this, other);
+ public boolean isValid() {
+ return SyntheticMethodBuilder.isValidSyntheticMethod(method.getDefinition());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 28913d0..cade86e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -5,10 +5,11 @@
import com.android.tools.r8.graph.DexClass;
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.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -16,37 +17,35 @@
*
* <p>This class is internal to the synthetic items collection, thus package-protected.
*/
-class SyntheticMethodReference extends SyntheticReference {
+class SyntheticMethodReference
+ extends SyntheticReference<SyntheticMethodReference, SyntheticMethodDefinition> {
final DexMethod method;
- SyntheticMethodReference(SynthesizingContext context, DexMethod method) {
- super(context);
+ SyntheticMethodReference(SyntheticKind kind, SynthesizingContext context, DexMethod method) {
+ super(kind, context);
this.method = method;
}
@Override
- DexReference getReference() {
- return method;
- }
-
- @Override
DexType getHolder() {
return method.holder;
}
@Override
- SyntheticDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+ SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
DexClass clazz = definitions.apply(method.holder);
if (clazz == null) {
return null;
}
assert clazz.isProgramClass();
ProgramMethod definition = clazz.asProgramClass().lookupProgramMethod(method);
- return definition != null ? new SyntheticMethodDefinition(getContext(), definition) : null;
+ return definition != null
+ ? new SyntheticMethodDefinition(getKind(), getContext(), definition)
+ : null;
}
@Override
- SyntheticReference rewrite(NonIdentityGraphLens lens) {
+ SyntheticMethodReference rewrite(NonIdentityGraphLens lens) {
DexMethod rewritten = lens.lookupMethod(method);
// If the reference has been non-trivially rewritten the compiler has changed it and it can no
// longer be considered a synthetic. The context may or may not have changed.
@@ -54,12 +53,28 @@
// If the referenced item is rewritten, it should be moved to another holder as the
// synthetic holder is no longer part of the synthetic collection.
assert method.holder != rewritten.holder;
- assert SyntheticItems.verifyNotInternalSynthetic(rewritten.holder);
+ assert SyntheticNaming.verifyNotInternalSynthetic(rewritten.holder);
return null;
}
SynthesizingContext context = getContext().rewrite(lens);
- return context == getContext() && rewritten == method
- ? this
- : new SyntheticMethodReference(context, rewritten);
+ if (context == getContext() && rewritten == method) {
+ return this;
+ }
+ // Ensure that if a synthetic moves its context moves consistently.
+ if (method != rewritten) {
+ context =
+ SynthesizingContext.fromSyntheticContextChange(
+ rewritten.holder, context, lens.dexItemFactory());
+ if (context == null) {
+ return null;
+ }
+ }
+ return new SyntheticMethodReference(getKind(), context, rewritten);
+ }
+
+ @Override
+ void apply(
+ Consumer<SyntheticMethodReference> onMethod, Consumer<SyntheticClassReference> onClass) {
+ onMethod.accept(this);
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
new file mode 100644
index 0000000..644a359
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+public class SyntheticNaming {
+
+ /**
+ * Enumeration of all kinds of synthetic items.
+ *
+ * <p>The synthetic kinds are used to provide hinting about what a synthetic item represents. The
+ * kinds must *not* be used be the compiler and are only meant for "debugging". The compiler and
+ * its test may use the kind information as part of asserting properties of the compiler. The kind
+ * will be put into any non-minified synthetic name and thus the kind "descriptor" must be a
+ * distinct for each kind.
+ */
+ public enum SyntheticKind {
+ // Class synthetics.
+ LAMBDA("Lambda", false),
+ // Method synthetics.
+ BACKPORT("Backport", true),
+ STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
+ TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
+ THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true);
+
+ public final String descriptor;
+ public final boolean isSingleSyntheticMethod;
+
+ SyntheticKind(String descriptor, boolean isSingleSyntheticMethod) {
+ this.descriptor = descriptor;
+ this.isSingleSyntheticMethod = isSingleSyntheticMethod;
+ }
+
+ public static SyntheticKind fromDescriptor(String descriptor) {
+ for (SyntheticKind kind : values()) {
+ if (kind.descriptor.equals(descriptor)) {
+ return kind;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * The internal synthetic class separator is only used for representing synthetic items during
+ * compilation. In particular, this separator must never be used to write synthetic classes to the
+ * final compilation result.
+ */
+ private static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$InternalSynthetic";
+ /**
+ * The external synthetic class separator is used when writing classes. It may appear in types
+ * during compilation as the output of a compilation may be the input to another.
+ */
+ private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$ExternalSynthetic";
+ /** Method prefix when generating synthetic methods in a class. */
+ static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m";
+
+ // TODO(b/158159959): Remove usage of name-based identification.
+ public static boolean isSyntheticName(String typeName) {
+ return typeName.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+ || typeName.contains(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+ }
+
+ static DexType createInternalType(
+ SyntheticKind kind, SynthesizingContext context, String id, DexItemFactory factory) {
+ return createType(
+ INTERNAL_SYNTHETIC_CLASS_SEPARATOR,
+ kind,
+ context.getSynthesizingContextType(),
+ id,
+ factory);
+ }
+
+ static DexType createExternalType(
+ SyntheticKind kind, DexType context, String id, DexItemFactory factory) {
+ return createType(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context, id, factory);
+ }
+
+ private static DexType createType(
+ String separator, SyntheticKind kind, DexType context, String id, DexItemFactory factory) {
+ return factory.createType(createDescriptor(separator, kind, context.getInternalName(), id));
+ }
+
+ private static String createDescriptor(
+ String separator, SyntheticKind kind, String context, String id) {
+ return DescriptorUtils.getDescriptorFromClassBinaryName(
+ context + separator + kind.descriptor + id);
+ }
+
+ public static boolean verifyNotInternalSynthetic(DexType type) {
+ return verifyNotInternalSynthetic(type.toDescriptorString());
+ }
+
+ public static boolean verifyNotInternalSynthetic(ClassReference reference) {
+ return verifyNotInternalSynthetic(reference.getDescriptor());
+ }
+
+ public static boolean verifyNotInternalSynthetic(String typeBinaryNameOrDescriptor) {
+ assert !typeBinaryNameOrDescriptor.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+ return true;
+ }
+
+ // Visible via package protection in SyntheticItemsTestUtils.
+
+ enum Phase {
+ INTERNAL,
+ EXTERNAL
+ }
+
+ static String getPhaseSeparator(Phase phase) {
+ assert phase != null;
+ return phase == Phase.INTERNAL
+ ? INTERNAL_SYNTHETIC_CLASS_SEPARATOR
+ : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
+ }
+
+ static ClassReference makeSyntheticReferenceForTest(
+ ClassReference context, SyntheticKind kind, String id) {
+ return Reference.classFromDescriptor(
+ createDescriptor(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context.getBinaryName(), id));
+ }
+
+ static boolean isSynthetic(ClassReference clazz, Phase phase, SyntheticKind kind) {
+ String typeName = clazz.getTypeName();
+ String separator = getPhaseSeparator(phase);
+ int i = typeName.indexOf(separator);
+ return i >= 0 && checkMatchFrom(kind, typeName, i, separator);
+ }
+
+ private static boolean checkMatchFrom(
+ SyntheticKind kind, String name, int i, String externalSyntheticClassSeparator) {
+ int end = i + externalSyntheticClassSeparator.length() + kind.descriptor.length();
+ if (end >= name.length()) {
+ return false;
+ }
+ String prefix = name.substring(i, end);
+ return prefix.equals(externalSyntheticClassSeparator + kind.descriptor)
+ && isInt(name.substring(end));
+ }
+
+ private static boolean isInt(String str) {
+ if (str.isEmpty()) {
+ return false;
+ }
+ if ('0' == str.charAt(0)) {
+ return str.length() == 1;
+ }
+ for (int i = 0; i < str.length(); i++) {
+ if (!Character.isDigit(str.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
index 6618378..ea33564 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -4,9 +4,10 @@
package com.android.tools.r8.synthesis;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -14,16 +15,24 @@
*
* <p>This class is internal to the synthetic items collection, thus package-protected.
*/
-abstract class SyntheticReference {
+abstract class SyntheticReference<
+ R extends SyntheticReference<R, D>, D extends SyntheticDefinition<R, D>> {
+
+ private final SyntheticKind kind;
private final SynthesizingContext context;
- SyntheticReference(SynthesizingContext context) {
+ SyntheticReference(SyntheticKind kind, SynthesizingContext context) {
+ assert kind != null;
+ assert context != null;
+ this.kind = kind;
this.context = context;
}
- abstract SyntheticDefinition lookupDefinition(Function<DexType, DexClass> definitions);
+ abstract D lookupDefinition(Function<DexType, DexClass> definitions);
- abstract DexReference getReference();
+ final SyntheticKind getKind() {
+ return kind;
+ }
final SynthesizingContext getContext() {
return context;
@@ -31,5 +40,8 @@
abstract DexType getHolder();
- abstract SyntheticReference rewrite(NonIdentityGraphLens lens);
+ abstract R rewrite(NonIdentityGraphLens lens);
+
+ abstract void apply(
+ Consumer<SyntheticMethodReference> onMethod, Consumer<SyntheticClassReference> onClass);
}
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 db914a4..c375074 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1280,6 +1280,7 @@
public boolean enable = true;
public boolean enableConstructorMerging = true;
+ // TODO(b/174809311): Update or remove the option and its tests after new lambdas synthetics.
public boolean enableJavaLambdaMerging = false;
public boolean enableKotlinLambdaMerging = true;
@@ -1473,6 +1474,8 @@
public boolean allowInvalidCfAccessFlags =
System.getProperty("com.android.tools.r8.allowInvalidCfAccessFlags") != null;
+ public boolean allowConflictingSyntheticTypes = false;
+
// Flag to allow processing of resources in D8. A data resource consumer still needs to be
// specified.
public boolean enableD8ResourcesPassThrough = false;
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 40ae9ce..fe6feab 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -40,6 +40,14 @@
return -1;
}
+ public static <S, T> List<T> map(Iterable<S> list, Function<S, T> fn) {
+ List<T> result = new ArrayList<>();
+ for (S element : list) {
+ result.add(fn.apply(element));
+ }
+ return result;
+ }
+
public static <S, T> List<T> map(Collection<S> list, Function<S, T> fn) {
List<T> result = new ArrayList<>(list.size());
for (S element : list) {
diff --git a/src/main/java/com/android/tools/r8/utils/Pair.java b/src/main/java/com/android/tools/r8/utils/Pair.java
index ac58f56..c2f3489 100644
--- a/src/main/java/com/android/tools/r8/utils/Pair.java
+++ b/src/main/java/com/android/tools/r8/utils/Pair.java
@@ -50,4 +50,9 @@
public boolean equals(Object obj) {
throw new Unreachable("Pair does not want to support equality!");
}
+
+ @Override
+ public String toString() {
+ return "Pair{" + first + ", " + second + '}';
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
index 234732c..c721599 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
@@ -18,29 +18,39 @@
/** Base class to share most visiting methods */
public abstract class CompareToVisitorBase extends CompareToVisitor {
+ private static boolean DEBUG = false;
+
+ // Helper to debug insert a breakpoint on order values.
+ public static int debug(int order) {
+ if (DEBUG && order != 0) {
+ return order;
+ }
+ return order;
+ }
+
@Override
public final int visitBool(boolean value1, boolean value2) {
- return Boolean.compare(value1, value2);
+ return debug(Boolean.compare(value1, value2));
}
@Override
public final int visitInt(int value1, int value2) {
- return Integer.compare(value1, value2);
+ return debug(Integer.compare(value1, value2));
}
@Override
public int visitLong(long value1, long value2) {
- return Long.compare(value1, value2);
+ return debug(Long.compare(value1, value2));
}
@Override
public int visitFloat(float value1, float value2) {
- return Float.compare(value1, value2);
+ return debug(Float.compare(value1, value2));
}
@Override
public int visitDouble(double value1, double value2) {
- return Double.compare(value1, value2);
+ return debug(Double.compare(value1, value2));
}
@Override
@@ -53,12 +63,12 @@
if (order == 0) {
order = visitBool(it1.hasNext(), it2.hasNext());
}
- return order;
+ return debug(order);
}
@Override
public int visitDexString(DexString string1, DexString string2) {
- return string1.compareTo(string2);
+ return debug(string1.compareTo(string2));
}
@Override
@@ -74,19 +84,19 @@
order = visitDexMethod(reference1.asDexMethod(), reference2.asDexMethod());
}
}
- return order;
+ return debug(order);
}
@Override
public final <S> int visit(S item1, S item2, Comparator<S> comparator) {
- return comparator.compare(item1, item2);
+ return debug(comparator.compare(item1, item2));
}
@Override
public final <S> int visit(S item1, S item2, StructuralMapping<S> accept) {
ItemSpecification<S> itemVisitor = new ItemSpecification<>(item1, item2, this);
accept.apply(itemVisitor);
- return itemVisitor.order;
+ return debug(itemVisitor.order);
}
private static class ItemSpecification<T>
@@ -198,7 +208,7 @@
}
@Override
- protected <S> ItemSpecification<T> withItemIterator(
+ protected <S> ItemSpecification<T> withCustomItemIterator(
Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
if (order == 0) {
order = parent.visitItemIterator(getter.apply(item1), getter.apply(item2), compare);
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
index f581065..7569929 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
@@ -29,34 +29,35 @@
@Override
public int visitDexType(DexType type1, DexType type2) {
- return namingLens
- .lookupDescriptor(type1)
- .acceptCompareTo(namingLens.lookupDescriptor(type2), this);
+ return debug(
+ namingLens
+ .lookupDescriptor(type1)
+ .acceptCompareTo(namingLens.lookupDescriptor(type2), this));
}
@Override
public int visitDexField(DexField field1, DexField field2) {
int order = field1.holder.acceptCompareTo(field2.holder, this);
if (order != 0) {
- return order;
+ return debug(order);
}
order = namingLens.lookupName(field1).acceptCompareTo(namingLens.lookupName(field2), this);
if (order != 0) {
- return order;
+ return debug(order);
}
- return field1.type.acceptCompareTo(field2.type, this);
+ return debug(field1.type.acceptCompareTo(field2.type, this));
}
@Override
public int visitDexMethod(DexMethod method1, DexMethod method2) {
int order = method1.holder.acceptCompareTo(method2.holder, this);
if (order != 0) {
- return order;
+ return debug(order);
}
order = namingLens.lookupName(method1).acceptCompareTo(namingLens.lookupName(method2), this);
if (order != 0) {
- return order;
+ return debug(order);
}
- return method1.proto.acceptCompareTo(method2.proto, this);
+ return debug(method1.proto.acceptCompareTo(method2.proto, this));
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
index 89bd02b..ccb5c9e 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
@@ -28,6 +28,6 @@
public int visitDexType(DexType type1, DexType type2) {
DexType repr1 = representatives.getRepresentative(type1);
DexType repr2 = representatives.getRepresentative(type2);
- return repr1.getDescriptor().acceptCompareTo(repr2.getDescriptor(), this);
+ return debug(repr1.getDescriptor().acceptCompareTo(repr2.getDescriptor(), this));
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
index 892183d..244d404 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
@@ -105,7 +105,7 @@
}
@Override
- protected <S> HashCodeVisitor<T> withItemIterator(
+ protected <S> HashCodeVisitor<T> withCustomItemIterator(
Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
Iterator<S> it = getter.apply(item);
while (it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
index e63ac6c..6c388fb 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
@@ -170,7 +170,7 @@
}
@Override
- protected <S> ItemSpecification<T> withItemIterator(
+ protected <S> ItemSpecification<T> withCustomItemIterator(
Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
parent.visitItemIterator(getter.apply(item), hasher);
return this;
diff --git a/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java b/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
index a81fc18..69d8796 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
@@ -51,12 +51,12 @@
HashingAccept<S> hasher);
/** Base implementation for visiting an enumeration of items. */
- protected abstract <S> V withItemIterator(
+ protected abstract <S> V withCustomItemIterator(
Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher);
public final <S> V withCustomItemCollection(
Function<T, Collection<S>> getter, StructuralAcceptor<S> acceptor) {
- return withItemIterator(getter.andThen(Collection::iterator), acceptor, acceptor);
+ return withCustomItemIterator(getter.andThen(Collection::iterator), acceptor, acceptor);
}
/**
@@ -79,24 +79,23 @@
predicate, getter, StructuralItem::acceptCompareTo, StructuralItem::acceptHashing);
}
+ public final <S extends StructuralItem<S>> V withItemIterator(Function<T, Iterator<S>> getter) {
+ return withCustomItemIterator(
+ getter, StructuralItem::acceptCompareTo, StructuralItem::acceptHashing);
+ }
+
public final <S extends StructuralItem<S>> V withItemCollection(
Function<T, Collection<S>> getter) {
- return withItemIterator(
- getter.andThen(Collection::iterator),
- StructuralItem::acceptCompareTo,
- StructuralItem::acceptHashing);
+ return withItemIterator(getter.andThen(Collection::iterator));
}
public final <S extends StructuralItem<S>> V withItemArray(Function<T, S[]> getter) {
- return withItemIterator(
- getter.andThen(a -> Arrays.asList(a).iterator()),
- StructuralItem::acceptCompareTo,
- StructuralItem::acceptHashing);
+ return withItemIterator(getter.andThen(a -> Arrays.asList(a).iterator()));
}
public final <S extends StructuralItem<S>> V withItemArrayAllowingNullMembers(
Function<T, S[]> getter) {
- return withItemIterator(
+ return withCustomItemIterator(
getter.andThen(a -> Arrays.asList(a).iterator()),
(a, b, visitor) -> {
if (a == null || b == null) {
diff --git a/src/test/examplesAndroidO/multidex004/ref-list-1.txt b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
index ac6e888..c817c33 100644
--- a/src/test/examplesAndroidO/multidex004/ref-list-1.txt
+++ b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
@@ -1,4 +1,4 @@
-Lmultidex004/-$$Lambda$MainActivity$g120D_43GXrTOaB3kfYt6wSIJh4;
+Lmultidex004/MainActivity-$$ExternalSyntheticLambda0;
Lmultidex004/MainActivity;
Lmultidex004/VersionInterface;
Lmultidex004/VersionStatic;
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 726136f..f5348c3 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -13,9 +13,10 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -93,12 +94,13 @@
for (String descriptor : descriptors) {
// classes are either lambda classes used by the main class, companion classes of the main
// interface, the main class/interface, or for JDK9, desugaring of try-with-resources.
+ ClassReference reference = Reference.classFromDescriptor(descriptor);
Assert.assertTrue(
- descriptor.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
- || descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
+ descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
|| descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
|| descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
- || descriptor.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+ || SyntheticItemsTestUtils.isExternalLambda(reference)
+ || SyntheticItemsTestUtils.isExternalStaticInterfaceCall(reference)
|| descriptor.equals(mainClassDescriptor));
}
String classDescriptor =
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 7157943..389b0fa 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -107,14 +107,14 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 101, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
.withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
.run();
}
@@ -146,14 +146,14 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 101, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
.withMinApiLevel(AndroidApiLevel.N)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
.run();
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
index adabb3e..63c1f18 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
@@ -4,16 +4,18 @@
package com.android.tools.r8.classmerging.horizontal;
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
import java.util.Set;
import java.util.stream.Collectors;
+import org.junit.Ignore;
import org.junit.Test;
public class JavaLambdaMergingTest extends HorizontalClassMergingTestBase {
@@ -23,6 +25,7 @@
}
@Test
+ @Ignore("b/174809311): Test does not work with hygienic lambdas. Rewrite or remove")
public void test() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
@@ -38,16 +41,12 @@
inspector -> {
Set<DexType> lambdaSources =
inspector.getSources().stream()
- .filter(x -> x.toSourceString().contains(LAMBDA_CLASS_NAME_PREFIX))
+ .filter(JavaLambdaMergingTest::isLambda)
.collect(Collectors.toSet());
assertEquals(3, lambdaSources.size());
DexType firstTarget = inspector.getTarget(lambdaSources.iterator().next());
for (DexType lambdaSource : lambdaSources) {
- assertTrue(
- inspector
- .getTarget(lambdaSource)
- .toSourceString()
- .contains(LAMBDA_CLASS_NAME_PREFIX));
+ assertTrue(isLambda(inspector.getTarget(lambdaSource)));
assertEquals(firstTarget, inspector.getTarget(lambdaSource));
}
})
@@ -59,6 +58,11 @@
.assertSuccessWithOutputLines("Hello world!");
}
+ private static boolean isLambda(DexType type) {
+ return SyntheticItemsTestUtils.isExternalLambda(
+ Reference.classFromDescriptor(type.toDescriptorString()));
+ }
+
public static class Main {
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 13d59dc..a5a1a8a 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
@@ -2161,8 +2162,28 @@
}
private static boolean isInLambdaClass(VmMirror mirror, Location location) {
- String classSig = mirror.getClassSignature(location.classID);
- return classSig.contains("$$Lambda$");
+ // TODO(b/174809573): These "lambda" specific methods are highly questionable.
+ // Determine the exact filtering behavior of intellij (which is very likely not name
+ // based) and update this filter accordingly.
+ CommandPacket cmd =
+ new CommandPacket(
+ ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.ModifiersCommand);
+ cmd.setNextValueAsReferenceTypeID(location.classID);
+ ReplyPacket reply = mirror.performCommand(cmd);
+ mirror.checkReply(reply);
+ int modifiers = reply.getNextValueAsInt();
+ ClassAccessFlags flags = ClassAccessFlags.fromCfAccessFlags(modifiers);
+ if (!flags.isSynthetic()) {
+ return false;
+ }
+ String signature = mirror.getClassSignature(location.classID);
+ if (signature.contains("-CC")) {
+ // TODO(b/174809573): The need to return false here indicates a questionable test
+ // expectation. Either the test is incorrect or there is a bug in our generation of
+ // -CC classes marked as synthetic as that would lead to unwanted debugger filtering.
+ return false;
+ }
+ return true;
}
private static boolean isLambdaMethod(VmMirror mirror, Location location) {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
index 8f304c6..875ead2 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
@@ -3,8 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -19,6 +17,7 @@
import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import org.junit.Test;
@@ -79,7 +78,7 @@
assertEquals(
Reference.classFromClass(MissingInterface.class), desugarWarning.getMissingType());
// TODO(b/132671303): The context class should not be the synthesized lambda class.
- assertThat(desugarWarning.getContextType().getDescriptor(), containsString("$$Lambda"));
+ assertTrue(SyntheticItemsTestUtils.isInternalLambda(desugarWarning.getContextType()));
// TODO(b/132671303): The position info should be the method context.
assertEquals(Position.UNKNOWN, desugarWarning.getPosition());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
index c9377fe..dc6e894 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
@@ -48,7 +48,7 @@
private void checkHasLambdaClass(CodeInspector inspector) {
assertTrue(
inspector.allClasses().stream()
- .anyMatch(subject -> subject.getOriginalName().contains("-$$Lambda$")));
+ .anyMatch(subject -> subject.isSynthesizedJavaLambdaClass()));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index d094c78..acfc021 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -3,9 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.backports;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -16,10 +13,10 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.desugar.backports.AbstractBackportTest.MiniAssert;
import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.google.common.collect.ImmutableList;
@@ -200,9 +197,7 @@
private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
inspector.forAllClasses(
clazz -> {
- assertThat(
- clazz.getFinalName(),
- not(containsString(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)));
+ SyntheticNaming.verifyNotInternalSynthetic(clazz.getFinalReference());
if (!clazz.getOriginalName().equals(MiniAssert.class.getTypeName())) {
clazz.forAllMethods(
method ->
@@ -233,11 +228,11 @@
// of intermediates.
Set<MethodReference> expectedSynthetics =
ImmutableSet.of(
- SyntheticItemsTestUtils.syntheticMethod(
+ SyntheticItemsTestUtils.syntheticBackportMethod(
User1.class, 0, Character.class.getMethod("compare", char.class, char.class)),
- SyntheticItemsTestUtils.syntheticMethod(
+ SyntheticItemsTestUtils.syntheticBackportMethod(
User1.class, 1, Boolean.class.getMethod("compare", boolean.class, boolean.class)),
- SyntheticItemsTestUtils.syntheticMethod(
+ SyntheticItemsTestUtils.syntheticBackportMethod(
User2.class, 0, Integer.class.getMethod("compare", int.class, int.class)));
assertEquals(expectedSynthetics, getSyntheticMethods(inspector));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 45f8957..05f84af 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.backports;
-import static com.android.tools.r8.synthesis.SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -25,11 +24,11 @@
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -57,11 +56,6 @@
static final List<Class<?>> MAIN_DEX_LIST_CLASSES =
ImmutableList.of(MiniAssert.class, TestClass.class, User2.class);
- static final String SyntheticUnderUser1 =
- User1.class.getTypeName() + EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
- static final String SyntheticUnderUser2 =
- User2.class.getTypeName() + EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
-
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
@@ -341,16 +335,16 @@
private ImmutableSet<MethodReference> getNonMainDexExpectedSynthetics()
throws NoSuchMethodException {
return ImmutableSet.of(
- SyntheticItemsTestUtils.syntheticMethod(
+ SyntheticItemsTestUtils.syntheticBackportMethod(
User1.class, 1, Boolean.class.getMethod("compare", boolean.class, boolean.class)));
}
private ImmutableSet<MethodReference> getMainDexExpectedSynthetics()
throws NoSuchMethodException {
return ImmutableSet.of(
- SyntheticItemsTestUtils.syntheticMethod(
+ SyntheticItemsTestUtils.syntheticBackportMethod(
User1.class, 0, Character.class.getMethod("compare", char.class, char.class)),
- SyntheticItemsTestUtils.syntheticMethod(
+ SyntheticItemsTestUtils.syntheticBackportMethod(
User2.class, 0, Integer.class.getMethod("compare", int.class, int.class)));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/bridge/LambdaReturnTypeBridgeTest.java b/src/test/java/com/android/tools/r8/desugar/bridge/LambdaReturnTypeBridgeTest.java
index 2dbcc30..fdb1044 100644
--- a/src/test/java/com/android/tools/r8/desugar/bridge/LambdaReturnTypeBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/bridge/LambdaReturnTypeBridgeTest.java
@@ -62,12 +62,7 @@
codeInspector -> {
boolean foundBridge = false;
for (FoundClassSubject clazz : codeInspector.allClasses()) {
- if (clazz
- .getOriginalName()
- .contains(
- "-$$Lambda$"
- + LambdaWithMultipleImplementingInterfaces.class.getSimpleName()
- + "$")) {
+ if (clazz.isSynthesizedJavaLambdaClass()) {
// Find bridge method and check whether or not it has a cast.
for (FoundMethodSubject bridge : clazz.allMethods(FoundMethodSubject::isBridge)) {
foundBridge = true;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
index f7b5e5d..bfb1885 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
@@ -17,11 +17,16 @@
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,12 +58,29 @@
private void assertIdenticalInspectors(Path libDexFile1, Path libDexFile2) throws IOException {
CodeInspector i1 = new CodeInspector(libDexFile1.resolve("classes.dex"));
CodeInspector i2 = new CodeInspector(libDexFile2.resolve("classes.dex"));
+ assertIdenticalInspectors(i1, i2);
+ }
+
+ public static void assertIdenticalInspectors(CodeInspector i1, CodeInspector i2) {
assertEquals(i1.allClasses().size(), i2.allClasses().size());
Map<DexEncodedMethod, DexEncodedMethod> diffs = new IdentityHashMap<>();
for (FoundClassSubject clazz1 : i1.allClasses()) {
ClassSubject clazz = i2.clazz(clazz1.getOriginalName());
assertTrue(clazz.isPresent());
FoundClassSubject clazz2 = clazz.asFoundClassSubject();
+ Set<String> methods1 =
+ clazz1.allMethods().stream().map(m -> m.toString()).collect(Collectors.toSet());
+ Set<String> methods2 =
+ clazz2.allMethods().stream().map(m -> m.toString()).collect(Collectors.toSet());
+ SetView<String> union = Sets.union(methods1, methods2);
+ assertEquals(
+ "Inspector 1 contains more methods",
+ Collections.emptySet(),
+ Sets.difference(union, methods1));
+ assertEquals(
+ "Inspector 2 contains more methods",
+ Collections.emptySet(),
+ Sets.difference(union, methods2));
assertEquals(clazz1.allMethods().size(), clazz2.allMethods().size());
for (FoundMethodSubject method1 : clazz1.allMethods()) {
MethodSubject method = clazz2.method(method1.asMethodReference());
@@ -79,7 +101,7 @@
assertTrue(printDiffs(diffs), diffs.isEmpty());
}
- private String printDiffs(Map<DexEncodedMethod, DexEncodedMethod> diffs) {
+ private static String printDiffs(Map<DexEncodedMethod, DexEncodedMethod> diffs) {
StringBuilder sb = new StringBuilder();
sb.append("The following methods had differences from one dex file to the other (")
.append(diffs.size())
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java
new file mode 100644
index 0000000..b252cf4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DeduplicateLambdasWithDefaultMethodsTest extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public DeduplicateLambdasWithDefaultMethodsTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ @Test
+ public void test() throws Exception {
+ assertEquals(
+ ImmutableSet.of(
+ Reference.classFromClass(I.class),
+ Reference.classFromClass(TestClass.class),
+ SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+ SyntheticItemsTestUtils.syntheticLambdaClass(TestClass.class, 0)),
+ testForD8(Backend.CF)
+ .addInnerClasses(getClass())
+ .setIntermediate(true)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .inspector()
+ .allClasses()
+ .stream()
+ .map(FoundClassSubject::getFinalReference)
+ .collect(Collectors.toSet()));
+ }
+
+ interface I {
+ void foo();
+
+ // Lots of methods which may cause the ordering of methods on a class to change between builds.
+ default void a() {
+ System.out.print("a");
+ }
+
+ default void b() {
+ System.out.print("b");
+ }
+
+ default void c() {
+ System.out.print("c");
+ }
+
+ default void x() {
+ System.out.print("x");
+ }
+
+ default void y() {
+ System.out.print("y");
+ }
+
+ default void z() {
+ System.out.print("z");
+ }
+ }
+
+ public static class TestClass {
+ private static void foo() {
+ System.out.println("foo");
+ }
+
+ private static void pI(I i) {
+ i.a();
+ i.b();
+ i.c();
+ i.x();
+ i.y();
+ i.z();
+ i.foo();
+ }
+
+ public static void main(String[] args) {
+ // Duplication of the same lambda, each of which should become a shared instance.
+ pI(TestClass::foo);
+ pI(TestClass::foo);
+ pI(TestClass::foo);
+ pI(TestClass::foo);
+ pI(TestClass::foo);
+ pI(TestClass::foo);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
new file mode 100644
index 0000000..90c8009
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
@@ -0,0 +1,140 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * These tests document the behavior of lambdas w.r.t identity and equality.
+ *
+ * <p>The D8 and R8 compilers take the stance that a program should not rely on either identity or
+ * equality of any lambda metafactory allocated lambda. Thus the status of these tests differ
+ * between JVM, D8/CF, D8/DEX and R8 runs as the compilers may or may not share classes and
+ * allocations as seen fit.
+ */
+@RunWith(Parameterized.class)
+public class LambdaEqualityTest extends TestBase {
+
+ static final String EXPECTED_JAVAC =
+ StringUtils.lines(
+ "Same method refs",
+ "true",
+ "true",
+ "true",
+ "Different method refs",
+ "false",
+ "false",
+ "false",
+ "Empty lambda",
+ "false",
+ "false",
+ "false");
+
+ static final String EXPECTED_D8 =
+ StringUtils.lines(
+ "Same method refs",
+ "true",
+ "true",
+ "true",
+ "Different method refs",
+ "true", // D8 will share the class for the method references.
+ "false",
+ "false",
+ "Empty lambda",
+ "false",
+ "false",
+ "false");
+
+ static final String EXPECTED_R8 =
+ StringUtils.lines(
+ "Same method refs",
+ "true",
+ "true",
+ "true",
+ "Different method refs",
+ "true", // R8 will share the class for the method references.
+ "false",
+ "false",
+ "Empty lambda",
+ "true", // R8 will eliminate the call to the impl method thus making lambdas equal.
+ "true",
+ "true");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public LambdaEqualityTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForRuntime(parameters)
+ .addInnerClasses(LambdaEqualityTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_JAVAC);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClasses(LambdaEqualityTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_D8);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LambdaEqualityTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .addKeepMethodRules(
+ Reference.methodFromMethod(
+ TestClass.class.getDeclaredMethod(
+ "compare", String.class, MyInterface.class, MyInterface.class)))
+ .run(parameters.getRuntime(), TestClass.class)
+ // The use of invoke dynamics prohibits the optimization and sharing of lambdas in R8.
+ .assertSuccessWithOutput(parameters.isCfRuntime() ? EXPECTED_JAVAC : EXPECTED_R8);
+ }
+
+ interface MyInterface {
+ void foo();
+ }
+
+ static class TestClass {
+
+ public static void compare(String msg, MyInterface i1, MyInterface i2) {
+ System.out.println(msg);
+ System.out.println(i1.getClass() == i2.getClass());
+ System.out.println(i1 == i2);
+ System.out.println(i1.equals(i2));
+ }
+
+ public static void main(String[] args) {
+ MyInterface println = System.out::println;
+ // These lambdas are physically the same and should remain so in all cases.
+ compare("Same method refs", println, println);
+ // These lambdas can be shared as they reference the same actual function.
+ compare("Different method refs", println, System.out::println);
+ // These lambdas cannot be shared (by D8) as javac will generate a lambda$main$X for each.
+ compare("Empty lambda", () -> {}, () -> {});
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java
new file mode 100644
index 0000000..1a6e364
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaNamingConflictTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("boo!");
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimes()
+ .withApiLevel(AndroidApiLevel.B)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ // The expected synthetic name is the context of the lambda, TestClass, and the first id.
+ private static final ClassReference CONFLICTING_NAME =
+ SyntheticItemsTestUtils.syntheticLambdaClass(TestClass.class, 0);
+
+ private final TestParameters parameters;
+
+ public LambdaNamingConflictTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getConflictingNameClass())
+ .addProgramClassFileData(getTransformedMainClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getConflictingNameClass())
+ .addProgramClassFileData(getTransformedMainClass())
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(o -> o.testing.allowConflictingSyntheticTypes = true)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class)
+ .addProgramClassFileData(getConflictingNameClass())
+ .addProgramClassFileData(getTransformedMainClass())
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(o -> o.testing.allowConflictingSyntheticTypes = true)
+ .addKeepMainRule(TestClass.class)
+ // Ensure that R8 cannot remove or rename the conflicting name.
+ .addKeepClassAndMembersRules(CONFLICTING_NAME.getTypeName())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private byte[] getTransformedMainClass() throws Exception {
+ return transformer(TestClass.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, visitor) ->
+ visitor.visitMethodInsn(
+ opcode, CONFLICTING_NAME.getBinaryName(), name, descriptor, isInterface))
+ .transform();
+ }
+
+ private byte[] getConflictingNameClass() throws Exception {
+ return transformer(WillBeConflictingName.class)
+ .setClassDescriptor(CONFLICTING_NAME.getDescriptor())
+ .transform();
+ }
+
+ interface I {
+ void bar();
+ }
+
+ static class WillBeConflictingName {
+ public static void foo(I i) {
+ i.bar();
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ WillBeConflictingName.foo(() -> System.out.println("boo!"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java
new file mode 100644
index 0000000..ac5b076
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java
@@ -0,0 +1,281 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaStaticInstanceFieldDuplicationTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("User1.1", "User1.2", "User2");
+
+ static final List<Class<?>> CLASSES =
+ ImmutableList.of(TestClass.class, MyConsumer.class, Accept.class, User1.class, User2.class);
+
+ static final List<String> CLASS_TYPE_NAMES =
+ CLASSES.stream().map(Class::getTypeName).collect(Collectors.toList());
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimes()
+ .withApiLevel(AndroidApiLevel.J)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public LambdaStaticInstanceFieldDuplicationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ // R8 does not support desugaring with class file output so this test is only valid for DEX.
+ assumeTrue(parameters.isDexRuntime());
+ runR8(false);
+ runR8(true);
+ }
+
+ private void runR8(boolean minify) throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(TestClass.class)
+ // Prevent R8 from eliminating the lambdas by keeping the application of them.
+ .addKeepClassAndMembersRules(Accept.class)
+ .setMinApi(parameters.getApiLevel())
+ .minification(minify)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(this::checkExpectedSynthetics);
+ ;
+ }
+
+ @Test
+ public void testD8Merging() throws Exception {
+ assumeTrue(
+ "b/147485959: Merging does not happen for CF due to lack of synthetic annotations",
+ parameters.isDexRuntime());
+ boolean intermediate = true;
+ runD8Merging(intermediate);
+ }
+
+ @Test
+ public void testD8MergingNonIntermediate() throws Exception {
+ boolean intermediate = false;
+ runD8Merging(intermediate);
+ }
+
+ private void runD8Merging(boolean intermediate) throws Exception {
+ // Compile part 1 of the input (maybe intermediate)
+ Path out1 =
+ testForD8(parameters.getBackend())
+ .addProgramClasses(User1.class)
+ .addClasspathClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .setIntermediate(intermediate)
+ .compile()
+ .writeToZip();
+
+ // Compile part 2 of the input (maybe intermediate)
+ Path out2 =
+ testForD8(parameters.getBackend())
+ .addProgramClasses(User2.class)
+ .addClasspathClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .setIntermediate(intermediate)
+ .compile()
+ .writeToZip();
+
+ SetView<MethodReference> syntheticsInParts =
+ Sets.union(
+ getSyntheticMethods(new CodeInspector(out1)),
+ getSyntheticMethods(new CodeInspector(out2)));
+
+ // Merge parts as an intermediate artifact.
+ // This will not merge synthetics regardless of the setting of intermediate.
+ Path out3 = temp.newFolder().toPath().resolve("out3.zip");
+ testForD8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, MyConsumer.class, Accept.class)
+ .addProgramFiles(out1, out2)
+ .setMinApi(parameters.getApiLevel())
+ .setIntermediate(true)
+ .compile()
+ .writeToZip(out3)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(inspector -> assertEquals(syntheticsInParts, getSyntheticMethods(inspector)));
+
+ // Finally do a non-intermediate merge.
+ testForD8(parameters.getBackend())
+ .addProgramFiles(out3)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(
+ inspector -> {
+ if (intermediate) {
+ // If all previous builds where intermediate then synthetics are merged.
+ checkExpectedSynthetics(inspector);
+ } else {
+ // Otherwise merging non-intermediate artifacts, synthetics will not be identified.
+ // Check that they are exactly as in the part inputs.
+ assertEquals(syntheticsInParts, getSyntheticMethods(inspector));
+ }
+ });
+ }
+
+ @Test
+ public void testD8FilePerClassFile() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ runD8FilePerMode(OutputMode.DexFilePerClassFile);
+ }
+
+ @Test
+ public void testD8FilePerClass() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ runD8FilePerMode(OutputMode.DexFilePerClass);
+ }
+
+ public void runD8FilePerMode(OutputMode outputMode) throws Exception {
+ Path perClassOutput =
+ testForD8(parameters.getBackend())
+ .setOutputMode(outputMode)
+ .addProgramClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ testForD8()
+ .addProgramFiles(perClassOutput)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(this::checkExpectedSynthetics);
+ }
+
+ private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
+ inspector.forAllClasses(
+ clazz -> {
+ assertFalse(SyntheticItemsTestUtils.isInternalLambda(clazz.getFinalReference()));
+ clazz.forAllMethods(
+ method ->
+ assertTrue(
+ "Unexpected invoke dynamic:\n" + method.getMethod().codeToString(),
+ method.isAbstract()
+ || method
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isInvokeDynamic)));
+ });
+ }
+
+ private Set<MethodReference> getSyntheticMethods(CodeInspector inspector) {
+ Set<MethodReference> methods = new HashSet<>();
+ inspector.allClasses().stream()
+ .filter(c -> !CLASS_TYPE_NAMES.contains(c.getFinalName()))
+ .forEach(
+ c ->
+ c.allMethods(m -> !m.isInstanceInitializer() && !m.isClassInitializer())
+ .forEach(m -> methods.add(m.asMethodReference())));
+ return methods;
+ }
+
+ private void checkExpectedSynthetics(CodeInspector inspector) throws Exception {
+ // Hardcoded set of expected synthetics in a "final" build. This set could change if the
+ // compiler makes any changes to the naming, sorting or grouping of synthetics. It is hard-coded
+ // here to check that the compiler generates this deterministically for any single run or merge
+ // of intermediates.
+ Set<MethodReference> expectedSynthetics =
+ ImmutableSet.of(
+ // User1 has two lambdas.
+ SyntheticItemsTestUtils.syntheticLambdaMethod(
+ User1.class, 0, MyConsumer.class.getMethod("accept", Object.class)),
+ SyntheticItemsTestUtils.syntheticLambdaMethod(
+ User1.class, 1, MyConsumer.class.getMethod("accept", Object.class)),
+ // User2 has one lambda.
+ SyntheticItemsTestUtils.syntheticLambdaMethod(
+ User2.class, 0, MyConsumer.class.getMethod("accept", Object.class)));
+ assertEquals(expectedSynthetics, getSyntheticMethods(inspector));
+ }
+
+ interface MyConsumer {
+ void accept(Object o);
+ }
+
+ static class Accept {
+ public static void accept(Object o, MyConsumer consumer) {
+ consumer.accept(o);
+ }
+ }
+
+ static class User1 {
+
+ private static void testSystemPrintln1() {
+ // The lambda will reference a lambda$x method on User1 which is created by javac for each
+ // lambda on the class. Thus there can be no sharing unless R8 inlines the lambda method into
+ // the desugared lambda classes.
+ Accept.accept("1.1", o -> System.out.println("User" + o));
+ }
+
+ private static void testSystemPrintln2() {
+ Accept.accept("1.2", o -> System.out.println("User" + o));
+ }
+ }
+
+ static class User2 {
+
+ private static void testSystemPrintln() {
+ Accept.accept("2", o -> System.out.println("User" + o));
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ User1.testSystemPrintln1();
+ User1.testSystemPrintln2();
+ User2.testSystemPrintln();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java
new file mode 100644
index 0000000..5b73125
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java
@@ -0,0 +1,265 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaToSysOutPrintlnDuplicationTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("User1", "User2");
+
+ static final List<Class<?>> CLASSES =
+ ImmutableList.of(TestClass.class, MyConsumer.class, Accept.class, User1.class, User2.class);
+
+ static final List<String> CLASS_TYPE_NAMES =
+ CLASSES.stream().map(Class::getTypeName).collect(Collectors.toList());
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimes()
+ .withApiLevel(AndroidApiLevel.J)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public LambdaToSysOutPrintlnDuplicationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ // R8 does not support desugaring with class file output so this test is only valid for DEX.
+ assumeTrue(parameters.isDexRuntime());
+ runR8(false);
+ runR8(true);
+ }
+
+ private void runR8(boolean minify) throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .minification(minify)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(this::checkExpectedSynthetics);
+ ;
+ }
+
+ @Test
+ public void testD8Merging() throws Exception {
+ assumeTrue(
+ "b/147485959: Merging does not happen for CF due to lack of synthetic annotations",
+ parameters.isDexRuntime());
+ boolean intermediate = true;
+ runD8Merging(intermediate);
+ }
+
+ @Test
+ public void testD8MergingNonIntermediate() throws Exception {
+ boolean intermediate = false;
+ runD8Merging(intermediate);
+ }
+
+ private void runD8Merging(boolean intermediate) throws Exception {
+ // Compile part 1 of the input (maybe intermediate)
+ Path out1 =
+ testForD8(parameters.getBackend())
+ .addProgramClasses(User1.class)
+ .addClasspathClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .setIntermediate(intermediate)
+ .compile()
+ .writeToZip();
+
+ // Compile part 2 of the input (maybe intermediate)
+ Path out2 =
+ testForD8(parameters.getBackend())
+ .addProgramClasses(User2.class)
+ .addClasspathClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .setIntermediate(intermediate)
+ .compile()
+ .writeToZip();
+
+ SetView<MethodReference> syntheticsInParts =
+ Sets.union(
+ getSyntheticMethods(new CodeInspector(out1)),
+ getSyntheticMethods(new CodeInspector(out2)));
+
+ // Merge parts as an intermediate artifact.
+ // This will not merge synthetics regardless of the setting of intermediate.
+ Path out3 = temp.newFolder().toPath().resolve("out3.zip");
+ testForD8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, MyConsumer.class, Accept.class)
+ .addProgramFiles(out1, out2)
+ .setMinApi(parameters.getApiLevel())
+ .setIntermediate(true)
+ .compile()
+ .writeToZip(out3)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(inspector -> assertEquals(syntheticsInParts, getSyntheticMethods(inspector)));
+
+ // Finally do a non-intermediate merge.
+ testForD8(parameters.getBackend())
+ .addProgramFiles(out3)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(
+ inspector -> {
+ if (intermediate) {
+ // If all previous builds where intermediate then synthetics are merged.
+ checkExpectedSynthetics(inspector);
+ } else {
+ // Otherwise merging non-intermediate artifacts, synthetics will not be identified.
+ // Check that they are exactly as in the part inputs.
+ assertEquals(syntheticsInParts, getSyntheticMethods(inspector));
+ }
+ });
+ }
+
+ @Test
+ public void testD8FilePerClassFile() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ runD8FilePerMode(OutputMode.DexFilePerClassFile);
+ }
+
+ @Test
+ public void testD8FilePerClass() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ runD8FilePerMode(OutputMode.DexFilePerClass);
+ }
+
+ public void runD8FilePerMode(OutputMode outputMode) throws Exception {
+ Path perClassOutput =
+ testForD8(parameters.getBackend())
+ .setOutputMode(outputMode)
+ .addProgramClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ testForD8()
+ .addProgramFiles(perClassOutput)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(this::checkExpectedSynthetics);
+ }
+
+ private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
+ inspector.forAllClasses(
+ clazz -> {
+ assertFalse(SyntheticItemsTestUtils.isInternalLambda(clazz.getFinalReference()));
+ clazz.forAllMethods(
+ method ->
+ assertTrue(
+ "Unexpected invoke dynamic:\n" + method.getMethod().codeToString(),
+ method.isAbstract()
+ || method
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isInvokeDynamic)));
+ });
+ }
+
+ private Set<MethodReference> getSyntheticMethods(CodeInspector inspector) {
+ Set<MethodReference> methods = new HashSet<>();
+ inspector.allClasses().stream()
+ .filter(c -> !CLASS_TYPE_NAMES.contains(c.getFinalName()))
+ .forEach(
+ c ->
+ c.allMethods(m -> !m.isInstanceInitializer())
+ .forEach(m -> methods.add(m.asMethodReference())));
+ return methods;
+ }
+
+ private void checkExpectedSynthetics(CodeInspector inspector) throws Exception {
+ // Hardcoded set of expected synthetics in a "final" build. This set could change if the
+ // compiler makes any changes to the naming, sorting or grouping of synthetics. It is hard-coded
+ // here to check that the compiler generates this deterministically for any single run or merge
+ // of intermediates.
+ Set<MethodReference> expectedSynthetics =
+ ImmutableSet.of(
+ SyntheticItemsTestUtils.syntheticLambdaMethod(
+ User1.class, 0, MyConsumer.class.getMethod("accept", Object.class)));
+ assertEquals(expectedSynthetics, getSyntheticMethods(inspector));
+ }
+
+ interface MyConsumer {
+ void accept(Object o);
+ }
+
+ static class Accept {
+ public static void accept(Object o, MyConsumer consumer) {
+ consumer.accept(o);
+ }
+ }
+
+ static class User1 {
+
+ private static void testSystemPrintln() {
+ Accept.accept("User1", System.out::println);
+ }
+ }
+
+ static class User2 {
+
+ private static void testSystemPrintln() {
+ Accept.accept("User2", System.out::println);
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ User1.testSystemPrintln();
+ User2.testSystemPrintln();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LegacyLambdaMergeTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LegacyLambdaMergeTest.java
new file mode 100644
index 0000000..dd00acf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LegacyLambdaMergeTest.java
@@ -0,0 +1,123 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodInsnTransform;
+import com.android.tools.r8.transformers.ClassFileTransformer.TypeInsnTransform;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class LegacyLambdaMergeTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public LegacyLambdaMergeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(getTransformedMain())
+ // Add the lambda twice (JVM just picks the first).
+ .addProgramClassFileData(getTransformedLambda())
+ .addProgramClassFileData(getTransformedLambda())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ // Merging legacy lambdas is only valid for DEX inputs, thus also not R8 applicable.
+ assumeTrue(parameters.isDexRuntime());
+ D8TestCompileResult lambda =
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClassFileData(getTransformedLambda())
+ .compile();
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClassFileData(getTransformedMain())
+ // Add the lambda twice.
+ .addProgramFiles(lambda.writeToZip())
+ .addProgramFiles(lambda.writeToZip())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private ClassReference LAMBDA =
+ Reference.classFromDescriptor(
+ Reference.classFromClass(WillBeLambda.class)
+ .getDescriptor()
+ .replace("WillBeLambda", "-$$Lambda$XYZ"));
+
+ private byte[] getTransformedLambda() throws Exception {
+ return transformer(WillBeLambda.class)
+ .setClassDescriptor(LAMBDA.getDescriptor())
+ .setAccessFlags(AccessFlags::setSynthetic)
+ .transform();
+ }
+
+ private byte[] getTransformedMain() throws Exception {
+ return transformer(TestClass.class)
+ .transformMethodInsnInMethod(
+ "main",
+ new MethodInsnTransform() {
+ @Override
+ public void visitMethodInsn(
+ int opcode,
+ String owner,
+ String name,
+ String descriptor,
+ boolean isInterface,
+ MethodVisitor visitor) {
+ visitor.visitMethodInsn(
+ opcode, LAMBDA.getBinaryName(), name, descriptor, isInterface);
+ }
+ })
+ .transformTypeInsnInMethod(
+ "main",
+ new TypeInsnTransform() {
+ @Override
+ public void visitTypeInsn(int opcode, String type, MethodVisitor visitor) {
+ visitor.visitTypeInsn(opcode, LAMBDA.getBinaryName());
+ }
+ })
+ .transform();
+ }
+
+ static class WillBeLambda {
+ public void foo() {
+ System.out.println("Hello, world");
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new WillBeLambda().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java
new file mode 100644
index 0000000..35e31d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas.mergedcontext;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MergedContextTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("B::foo", "C::bar");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public MergedContextTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, A.class, B.class, C.class)
+ .addKeepClassAndMembersRules(TestClass.class)
+ .addKeepRules("-repackageclasses \"repackaged\"")
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspector(
+ inspector -> {
+ inspector.assertClassNotMerged(C.class);
+ inspector.assertMergedInto(B.class, A.class);
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ /* This class will be merged with class B (with A being the result). This class has a package
+ * protected access to ensure that it cannot be repackaged. */
+ @NeverClassInline
+ public static class A {
+
+ @NeverInline
+ public void ensureNotRepackaged() {
+ TestClass.packageProtectedMethodToDisableRepackage();
+ }
+ }
+
+ /* This class is merged into A. */
+ @NeverClassInline
+ public static class B {
+
+ @NeverInline
+ public Runnable foo() {
+ C c = new C();
+ // This synthetic lambda class uses package protected access to C. Its context will initially
+ // be B, thus the synthetic will internally be B-$$Synthetic. The lambda can be repackaged
+ // together with the accessed class C. However, once A and B are merged as A, the context
+ // implicitly changes. If repackaging does not either see or adjust the context, the result
+ // will be that the external synthetic lambda will become A-$$Synthetic,
+ // with the consequence that the call to repackaged.C.protectedMethod() will throw IAE.
+ return () -> {
+ System.out.println("B::foo");
+ c.packageProtectedMethod();
+ };
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ public static class C {
+
+ @NeverInline
+ void packageProtectedMethod() {
+ System.out.println("C::bar");
+ }
+ }
+
+ static class TestClass {
+
+ static void packageProtectedMethodToDisableRepackage() {
+ if (System.nanoTime() < 0) {
+ throw new RuntimeException();
+ }
+ }
+
+ public static void main(String[] args) {
+ new A().ensureNotRepackaged();
+ new B().foo().run();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java b/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
index 934a15b..5730599 100644
--- a/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
@@ -47,9 +47,9 @@
if (parameters.isDexRuntime()) {
result.inspect(
inspector -> {
- // When in the same package we expect the two System.out::print lambdas to be shared.
+ // With the hygienic synthetics the reference to System.out::print can always be shared.
assertEquals(
- samePackage ? 2 : 3,
+ 2,
inspector.allClasses().stream()
.filter(c -> c.isSynthesizedJavaLambdaClass())
.count());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index e0266bd..38bb02e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.optimize.classinliner;
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
@@ -322,8 +321,8 @@
Set<String> expectedTypes = Sets.newHashSet("java.lang.StringBuilder");
expectedTypes.addAll(
inspector.allClasses().stream()
+ .filter(FoundClassSubject::isSynthesizedJavaLambdaClass)
.map(FoundClassSubject::getFinalName)
- .filter(name -> name.contains(LAMBDA_CLASS_NAME_PREFIX))
.collect(Collectors.toList()));
assertEquals(expectedTypes, collectTypes(clazz.uniqueMethodWithName("testStatefulLambda")));
assertTrue(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 7565cf7..1bbcb9e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -16,6 +17,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ImmutableList;
@@ -59,8 +61,8 @@
@Override
public void finished(DiagnosticsHandler handler) {
String string = builder.toString();
- assertTrue(string.contains(testClassMainDexName));
- assertTrue(string.contains("Lambda"));
+ assertThat(string, containsString(testClassMainDexName));
+ assertThat(string, SyntheticItemsTestUtils.containsExternalSyntheticReference());
}
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 52b60fe..cf2aeeb 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -24,9 +24,9 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -433,13 +433,13 @@
for (int i = 0; i < refList.length; i++) {
String reference = refList[i].trim();
// The main dex list generator does not do any lambda desugaring.
- if (!isLambda(reference)) {
+ if (!isExternalSyntheticLambda(reference)) {
if (mainDexGeneratorMainDexList.size() <= i - nonLambdaOffset) {
fail("Main dex list generator is missing '" + reference + "'");
}
String fromList = mainDexGeneratorMainDexList.get(i - nonLambdaOffset);
String fromConsumer = mainDexGeneratorMainDexListFromConsumer.get(i - nonLambdaOffset);
- if (isLambda(fromList)) {
+ if (isExternalSyntheticLambda(fromList)) {
assertEquals(Backend.DEX, backend);
assertEquals(fromList, fromConsumer);
nonLambdaOffset--;
@@ -481,8 +481,8 @@
assertArrayEquals(entriesUnsorted, entriesSorted);
}
- private boolean isLambda(String mainDexEntry) {
- return mainDexEntry.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX);
+ private boolean isExternalSyntheticLambda(String mainDexEntry) {
+ return SyntheticItemsTestUtils.isExternalLambda(Reference.classFromDescriptor(mainDexEntry));
}
private String mainDexStringToDescriptor(String mainDexString) {
@@ -492,11 +492,8 @@
}
private void checkSameMainDexEntry(String reference, String computed) {
- if (isLambda(reference)) {
- // For lambda classes we check that there is a lambda class for the right containing
- // class. However, we do not check the hash for the generated lambda class. The hash
- // changes for different compiler versions because different compiler versions generate
- // different lambda implementation method names.
+ if (isExternalSyntheticLambda(reference)) {
+ // For synthetic classes we check that the context classes match.
reference = reference.substring(0, reference.lastIndexOf('$'));
computed = computed.substring(0, computed.lastIndexOf('$'));
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index d102004..9fa7fe9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -100,10 +100,11 @@
inspector.allClasses().stream()
.anyMatch(
clazz ->
- clazz.getOriginalName().contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
+ clazz.isSynthesizedJavaLambdaClass()
&& clazz
- .getOriginalName()
- .contains("$" + lambdaHolder.getSimpleName() + "$")));
+ .getOriginalReference()
+ .equals(
+ SyntheticItemsTestUtils.syntheticLambdaClass(lambdaHolder, 0))));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
index 3959d84..6ae3a6f 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -57,7 +59,8 @@
}
private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
- return line.className.contains("-$$Lambda$");
+ // TODO(141287349): The mapping should not map the external name to the internal name!
+ return SyntheticItemsTestUtils.isInternalLambda(Reference.classFromTypeName(line.className));
}
private void checkLambdaFrame(StackTrace retracedStackTrace) {
diff --git a/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java b/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java
index cd153f2..c2f8041 100644
--- a/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java
@@ -79,8 +79,7 @@
codeInspector.allClasses().stream()
.anyMatch(
c -> {
- if (c.getOriginalName()
- .contains("-$$Lambda$PreserveDesugaredLambdaTest$Main")) {
+ if (c.isSynthesizedJavaLambdaClass()) {
assertThat(c.uniqueMethodWithName("computeTheFoo"), isPresent());
return true;
}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
index 4d385a0..436c030 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
@@ -28,6 +29,7 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.function.Supplier;
+import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -129,12 +131,13 @@
consumer.printWhyAreYouKeeping(classFromClass(A.class), new PrintStream(baos));
assertThat(baos.toString(), containsString(KEPT_REASON_SUFFIX));
- // TODO(b/124499108): Currently synthetic lambda classes are referenced,
+ // TODO(b/124499108): Currently (internal) synthetic lambda classes are referenced,
// should be their originating context.
+ Matcher<String> hasLambda = SyntheticItemsTestUtils.containsInternalSyntheticReference();
if (parameters.isDexRuntime()) {
- assertThat(baos.toString(), containsString("-$$Lambda$"));
+ assertThat(baos.toString(), hasLambda);
} else {
- assertThat(baos.toString(), not(containsString("-$$Lambda$")));
+ assertThat(baos.toString(), not(hasLambda));
}
}
}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
new file mode 100644
index 0000000..dba85d6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.lang.reflect.Method;
+import org.hamcrest.Matcher;
+
+public class SyntheticItemsTestUtils {
+
+ public static ClassReference syntheticCompanionClass(Class<?> clazz) {
+ return Reference.classFromDescriptor(
+ InterfaceMethodRewriter.getCompanionClassDescriptor(
+ Reference.classFromClass(clazz).getDescriptor()));
+ }
+
+ private static ClassReference syntheticClass(Class<?> clazz, SyntheticKind kind, int id) {
+ return SyntheticNaming.makeSyntheticReferenceForTest(
+ Reference.classFromClass(clazz), kind, "" + id);
+ }
+
+ public static MethodReference syntheticBackportMethod(Class<?> clazz, int id, Method method) {
+ ClassReference syntheticHolder =
+ syntheticClass(clazz, SyntheticNaming.SyntheticKind.BACKPORT, id);
+ MethodReference originalMethod = Reference.methodFromMethod(method);
+ return Reference.methodFromDescriptor(
+ syntheticHolder.getDescriptor(),
+ SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX,
+ originalMethod.getMethodDescriptor());
+ }
+
+ public static ClassReference syntheticLambdaClass(Class<?> clazz, int id) {
+ return syntheticClass(clazz, SyntheticNaming.SyntheticKind.LAMBDA, id);
+ }
+
+ public static MethodReference syntheticLambdaMethod(Class<?> clazz, int id, Method method) {
+ ClassReference syntheticHolder = syntheticLambdaClass(clazz, id);
+ MethodReference originalMethod = Reference.methodFromMethod(method);
+ return Reference.methodFromDescriptor(
+ syntheticHolder.getDescriptor(),
+ originalMethod.getMethodName(),
+ originalMethod.getMethodDescriptor());
+ }
+
+ public static boolean isInternalLambda(ClassReference reference) {
+ return SyntheticNaming.isSynthetic(reference, Phase.INTERNAL, SyntheticKind.LAMBDA);
+ }
+
+ public static boolean isExternalLambda(ClassReference reference) {
+ return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.LAMBDA);
+ }
+
+ public static boolean isExternalStaticInterfaceCall(ClassReference reference) {
+ return SyntheticNaming.isSynthetic(
+ reference, Phase.EXTERNAL, SyntheticKind.STATIC_INTERFACE_CALL);
+ }
+
+ public static Matcher<String> containsInternalSyntheticReference() {
+ return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
+ }
+
+ public static Matcher<String> containsExternalSyntheticReference() {
+ return containsString(SyntheticNaming.getPhaseSeparator(Phase.EXTERNAL));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/utils/SyntheticItemsTestUtils.java
deleted file mode 100644
index 9e78bac..0000000
--- a/src/test/java/com/android/tools/r8/utils/SyntheticItemsTestUtils.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticItems;
-import java.lang.reflect.Method;
-
-public class SyntheticItemsTestUtils {
-
- public static ClassReference syntheticClass(Class<?> clazz, int id) {
- return Reference.classFromTypeName(
- clazz.getTypeName() + SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR + id);
- }
-
- public static MethodReference syntheticMethod(Class<?> clazz, int id, Method method) {
- ClassReference syntheticHolder = syntheticClass(clazz, id);
- MethodReference originalMethod = Reference.methodFromMethod(method);
- return Reference.methodFromDescriptor(
- syntheticHolder.getDescriptor(),
- SyntheticItems.INTERNAL_SYNTHETIC_METHOD_PREFIX + 0,
- originalMethod.getMethodDescriptor());
- }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index ffe8151..a42fa4b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -136,6 +136,11 @@
}
@Override
+ public ClassReference getOriginalReference() {
+ return null;
+ }
+
+ @Override
public ClassReference getFinalReference() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 3a9976f..f927e62 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -285,6 +285,7 @@
&& ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKESPECIAL;
}
+ @Override
public boolean isInvokeDynamic() {
return instruction instanceof CfInvokeDynamic;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index a2d32eb..20d7466 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -191,6 +191,8 @@
public abstract String getOriginalBinaryName();
+ public abstract ClassReference getOriginalReference();
+
public abstract ClassReference getFinalReference();
public abstract String getFinalName();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 4b9f733..2ae5f05 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -209,6 +209,11 @@
return false;
}
+ @Override
+ public boolean isInvokeDynamic() {
+ return isInvokeCustom();
+ }
+
public boolean isInvokeCustom() {
return instruction instanceof InvokeCustom || instruction instanceof InvokeCustomRange;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index a676666..4220b50 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.retrace.RetraceTypeResult;
import com.android.tools.r8.retrace.RetracedField;
import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
@@ -389,6 +390,11 @@
}
@Override
+ public ClassReference getOriginalReference() {
+ return Reference.classFromDescriptor(getOriginalDescriptor());
+ }
+
+ @Override
public ClassReference getFinalReference() {
return Reference.classFromDescriptor(getFinalDescriptor());
}
@@ -430,7 +436,9 @@
@Override
public boolean isSynthesizedJavaLambdaClass() {
- return dexClass.type.getName().contains("$Lambda$");
+ // TODO(141287349): Make this precise based on the map input.
+ return SyntheticItemsTestUtils.isExternalLambda(getOriginalReference())
+ || SyntheticItemsTestUtils.isExternalLambda(getFinalReference());
}
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 56436e9..8b34260 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -43,6 +43,8 @@
boolean isInvokeSpecial();
+ boolean isInvokeDynamic();
+
DexMethod getMethod();
boolean isNop();
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index 45d84e2..1cfd0f0 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -27,6 +27,10 @@
cmd.append(jdk.GetJavaExecutable())
if extra_args:
cmd.extend(extra_args)
+ agent, args = extract_debug_agent_from_args(args)
+ if agent:
+ cmd.append(
+ '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
if debug:
cmd.append('-ea')
if profile:
@@ -103,3 +107,13 @@
else:
args.append(arg)
return lib, args
+
+def extract_debug_agent_from_args(input_args):
+ agent = False
+ args = []
+ for arg in input_args:
+ if arg in ('--debug-agent', '--debug_agent'):
+ agent = True
+ else:
+ args.append(arg)
+ return agent, args