Version 2.1.70 Cherry pick: Desugared library: GSON support CL: https://r8-review.googlesource.com/c/r8/+/54760 Cherry pick: Desugared library: fix test expectation CL: https://r8-review.googlesource.com/c/r8/+/54924 Cherry-pick: Base clean up of parsing generic signatures CL: https://r8-review.googlesource.com/c/r8/+/54144 Cherry-pick: Add Generic Signature printer to convert generic signatures CL: https://r8-review.googlesource.com/c/r8/+/54362 - L8 now clears all attributes but the signature by default - Interface duplication now parses the signature attribute, modifies it, and writes it back instead of directly editing the DexClass classSignature field which did not exist in 2.1. Bug: 167649682 Change-Id: Idf298097619007de382aa7051fb6da514abc0a9a
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java index a496472..0a23b2c 100644 --- a/src/main/java/com/android/tools/r8/Version.java +++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@ // This field is accessed from release scripts using simple pattern matching. // Therefore, changing this field could break our release scripts. - public static final String LABEL = "2.1.69"; + public static final String LABEL = "2.1.70"; private Version() { }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index 1c9b40c..2b75c58 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.GenericSignature.ClassSignature; import com.android.tools.r8.kotlin.KotlinClassLevelInfo; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.InternalOptions; @@ -329,6 +330,20 @@ assert verifyNoDuplicateFields(); } + public ClassSignature getClassSignature(AppView<?> appView) { + for (DexAnnotation annotation : annotations().annotations) { + if (DexAnnotation.isSignatureAnnotation(annotation, appView.dexItemFactory())) { + return GenericSignature.parseClassSignature( + type.getName(), + DexAnnotation.getSignature(annotation), + origin, + appView.dexItemFactory(), + appView.options().reporter); + } + } + return ClassSignature.NO_CLASS_SIGNATURE; + } + private boolean verifyCorrectnessOfFieldHolder(DexEncodedField field) { assert field.holder() == type : "Expected field `"
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 a211636..b1c621c 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -11,11 +11,14 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.graph.GenericSignature.ClassSignature; +import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature; import com.android.tools.r8.kotlin.KotlinClassLevelInfo; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.TraversalContinuation; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -443,33 +446,71 @@ methodCollection.addDirectMethod(directMethod); } - public void addExtraInterfaces(List<DexType> extraInterfaces, DexItemFactory factory) { + public void replaceInterfaces(List<ClassTypeSignature> newInterfaces, AppView<?> appView) { + if (newInterfaces.isEmpty()) { + return; + } + clearInterfaces(appView); + addExtraInterfaces(newInterfaces, appView); + } + + private void clearInterfaces(AppView<?> appView) { + interfaces = DexTypeList.empty(); + ClassSignature classSignature = getClassSignature(appView); + if (classSignature.hasNoSignature()) { + return; + } + replaceSignatureAnnotation( + new ClassSignature( + classSignature.formalTypeParameters, + classSignature.superClassSignature, + ImmutableList.of()), + appView.dexItemFactory()); + } + + public void addExtraInterfaces(List<ClassTypeSignature> extraInterfaces, AppView<?> appView) { if (extraInterfaces.isEmpty()) { return; } addExtraInterfacesToInterfacesArray(extraInterfaces); - addExtraInterfacesToSignatureAnnotationIfPresent(extraInterfaces, factory); + addExtraInterfacesToSignatureAnnotationIfPresent(extraInterfaces, appView); } - private void addExtraInterfacesToInterfacesArray(List<DexType> extraInterfaces) { + private void addExtraInterfacesToInterfacesArray(List<ClassTypeSignature> extraInterfaces) { DexType[] newInterfaces = Arrays.copyOf(interfaces.values, interfaces.size() + extraInterfaces.size()); for (int i = interfaces.size(); i < newInterfaces.length; i++) { - newInterfaces[i] = extraInterfaces.get(i - interfaces.size()); + newInterfaces[i] = extraInterfaces.get(i - interfaces.size()).type(); } interfaces = new DexTypeList(newInterfaces); } private void addExtraInterfacesToSignatureAnnotationIfPresent( - List<DexType> extraInterfaces, DexItemFactory factory) { + List<ClassTypeSignature> extraInterfaces, AppView<?> appView) { // We need to introduce in the dalvik.annotation.Signature annotation the extra interfaces. - // At this point we cheat and pretend the extraInterfaces simply don't use any generic types. + ClassSignature classSignature = getClassSignature(appView); + if (classSignature.hasNoSignature()) { + return; + } + ImmutableList.Builder<ClassTypeSignature> interfacesBuilder = + ImmutableList.<ClassTypeSignature>builder().addAll(classSignature.superInterfaceSignatures); + interfacesBuilder.addAll(extraInterfaces); + replaceSignatureAnnotation( + new ClassSignature( + classSignature.formalTypeParameters, + classSignature.superClassSignature, + interfacesBuilder.build()), + appView.dexItemFactory()); + } + + private void replaceSignatureAnnotation(ClassSignature classSignature, DexItemFactory factory) { DexAnnotation[] annotations = annotations().annotations; for (int i = 0; i < annotations.length; i++) { DexAnnotation annotation = annotations[i]; if (DexAnnotation.isSignatureAnnotation(annotation, factory)) { DexAnnotation[] rewrittenAnnotations = annotations.clone(); - rewrittenAnnotations[i] = rewriteSignatureAnnotation(annotation, extraInterfaces, factory); + rewrittenAnnotations[i] = + DexAnnotation.createSignatureAnnotation(classSignature.toString(), factory); setAnnotations(new DexAnnotationSet(rewrittenAnnotations)); // There is at most one signature annotation, so we can return here. return; @@ -477,14 +518,16 @@ } } - private DexAnnotation rewriteSignatureAnnotation( - DexAnnotation annotation, List<DexType> extraInterfaces, DexItemFactory factory) { - String signature = DexAnnotation.getSignature(annotation); - StringBuilder newSignatureBuilder = new StringBuilder(signature); - for (DexType extraInterface : extraInterfaces) { - newSignatureBuilder.append(extraInterface.descriptor.toString()); + public void clearAnnotationsButSignature(DexItemFactory factory) { + DexAnnotation[] annotations = annotations().annotations; + for (DexAnnotation annotation : annotations) { + if (DexAnnotation.isSignatureAnnotation(annotation, factory)) { + setAnnotations(new DexAnnotationSet(new DexAnnotation[] {annotation})); + // There is at most one signature annotation, so we can return here. + return; + } } - return DexAnnotation.createSignatureAnnotation(newSignatureBuilder.toString(), factory); + clearAnnotations(); } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java index 3b4d284..9c34474 100644 --- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java +++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -5,15 +5,18 @@ import static com.android.tools.r8.utils.DescriptorUtils.getClassBinaryNameFromDescriptor; import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName; +import static com.google.common.base.Predicates.alwaysTrue; import com.android.tools.r8.errors.Unreachable; -import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.Reporter; import com.google.common.collect.ImmutableList; import java.lang.reflect.GenericSignatureFormatError; import java.nio.CharBuffer; import java.util.List; -import java.util.function.Function; +import java.util.function.Predicate; /** * Internal encoding of the generics signature attribute as defined by JVMS 7 $ 4.3.4. @@ -95,32 +98,42 @@ */ public class GenericSignature { - private static final List<FormalTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of(); + static final List<FormalTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of(); + static final List<FieldTypeSignature> EMPTY_TYPE_ARGUMENTS = ImmutableList.of(); + static final List<ClassTypeSignature> EMPTY_SUPER_INTERFACES = ImmutableList.of(); + static final List<TypeSignature> EMPTY_TYPE_SIGNATURES = ImmutableList.of(); interface DexDefinitionSignature<T extends DexDefinition> { + default boolean isClassSignature() { return false; } + default boolean isFieldTypeSignature() { + return false; + } + + default boolean isMethodTypeSignature() { + return false; + } + default ClassSignature asClassSignature() { return null; } - default boolean isFieldTypeSignature() { - return false; - } - default FieldTypeSignature asFieldTypeSignature() { return null; } - default boolean isMethodTypeSignature() { - return false; - } - default MethodTypeSignature asMethodTypeSignature() { return null; } + + boolean hasSignature(); + + default boolean hasNoSignature() { + return !hasSignature(); + } } public static class FormalTypeParameter { @@ -147,14 +160,22 @@ public List<FieldTypeSignature> getInterfaceBounds() { return interfaceBounds; } + + public void visit(GenericSignatureVisitor visitor) { + visitor.visitClassBound(classBound); + if (interfaceBounds == null) { + return; + } + for (FieldTypeSignature interfaceBound : interfaceBounds) { + visitor.visitInterfaceBound(interfaceBound); + } + } } public static class ClassSignature implements DexDefinitionSignature<DexClass> { - static final ClassSignature UNKNOWN_CLASS_SIGNATURE = - new ClassSignature( - ImmutableList.of(), - ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE, - ImmutableList.of()); + + public static final ClassSignature NO_CLASS_SIGNATURE = + new ClassSignature(EMPTY_TYPE_PARAMS, NO_FIELD_TYPE_SIGNATURE, EMPTY_SUPER_INTERFACES); final List<FormalTypeParameter> formalTypeParameters; final ClassTypeSignature superClassSignature; @@ -181,6 +202,11 @@ } @Override + public boolean hasSignature() { + return this != NO_CLASS_SIGNATURE; + } + + @Override public boolean isClassSignature() { return true; } @@ -189,9 +215,37 @@ public ClassSignature asClassSignature() { return this; } + + public void visit(GenericSignatureVisitor visitor) { + visitor.visitFormalTypeParameters(formalTypeParameters); + visitor.visitSuperClass(superClassSignature); + for (ClassTypeSignature superInterface : superInterfaceSignatures) { + visitor.visitSuperInterface(superInterface); + } + } + + public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) { + if (hasNoSignature()) { + return null; + } + GenericSignaturePrinter genericSignaturePrinter = + new GenericSignaturePrinter(namingLens, isTypeMissing); + genericSignaturePrinter.visitClassSignature(this); + return genericSignaturePrinter.toString(); + } + + @Override + public String toString() { + return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue()); + } + + public static ClassSignature noSignature() { + return NO_CLASS_SIGNATURE; + } } public abstract static class TypeSignature { + public boolean isFieldTypeSignature() { return false; } @@ -208,11 +262,7 @@ return null; } - public TypeSignature toArrayTypeSignature(AppView<?> appView) { - return null; - } - - public TypeSignature toArrayElementTypeSignature(AppView<?> appView) { + public ArrayTypeSignature toArrayTypeSignature() { return null; } } @@ -275,8 +325,9 @@ return null; } - public boolean isUnknown() { - return this == ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE; + @Override + public boolean hasSignature() { + return this != GenericSignature.NO_FIELD_TYPE_SIGNATURE; } public abstract FieldTypeSignature asArgument(WildcardIndicator indicator); @@ -284,12 +335,30 @@ public boolean isStar() { return false; } + + public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) { + if (hasNoSignature()) { + return null; + } + GenericSignaturePrinter genericSignaturePrinter = + new GenericSignaturePrinter(namingLens, isTypeMissing); + genericSignaturePrinter.visitTypeSignature(this); + return genericSignaturePrinter.toString(); + } + + @Override + public String toString() { + return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue()); + } + + public static FieldTypeSignature noSignature() { + return NO_FIELD_TYPE_SIGNATURE; + } } - private static final class StarFieldTypeSignature extends FieldTypeSignature { + static final class StarFieldTypeSignature extends FieldTypeSignature { - private static final StarFieldTypeSignature STAR_FIELD_TYPE_SIGNATURE = - new StarFieldTypeSignature(); + static final StarFieldTypeSignature STAR_FIELD_TYPE_SIGNATURE = new StarFieldTypeSignature(); private StarFieldTypeSignature() { super(WildcardIndicator.NONE); @@ -306,11 +375,13 @@ } } + private static final ClassTypeSignature NO_FIELD_TYPE_SIGNATURE = + new ClassTypeSignature(DexItemFactory.nullValueType, EMPTY_TYPE_ARGUMENTS); + public static class ClassTypeSignature extends FieldTypeSignature { - static final ClassTypeSignature UNKNOWN_CLASS_TYPE_SIGNATURE = - new ClassTypeSignature(DexItemFactory.nullValueType, ImmutableList.of()); final DexType type; + // E.g., for Map<K, V>, a signature will indicate what types are for K and V. // Note that this could be nested, e.g., Map<K, Consumer<V>>. final List<FieldTypeSignature> typeArguments; @@ -320,7 +391,11 @@ ClassTypeSignature enclosingTypeSignature; ClassTypeSignature innerTypeSignature; - ClassTypeSignature(DexType type, List<FieldTypeSignature> typeArguments) { + public ClassTypeSignature(DexType type) { + this(type, EMPTY_TYPE_ARGUMENTS); + } + + public ClassTypeSignature(DexType type, List<FieldTypeSignature> typeArguments) { this(type, typeArguments, WildcardIndicator.NOT_AN_ARGUMENT); } @@ -361,8 +436,12 @@ return argument; } + public boolean isNoSignature() { + return this == NO_FIELD_TYPE_SIGNATURE; + } + @Override - public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) { + public ArrayTypeSignature toArrayTypeSignature() { return new ArrayTypeSignature(this); } @@ -371,6 +450,13 @@ outer.innerTypeSignature = inner; inner.enclosingTypeSignature = outer; } + + public void visit(GenericSignatureVisitor visitor) { + visitor.visitTypeArguments(typeArguments); + if (innerTypeSignature != null) { + visitor.visitSimpleClass(innerTypeSignature); + } + } } public static class ArrayTypeSignature extends FieldTypeSignature { @@ -408,13 +494,12 @@ } @Override - public TypeSignature toArrayTypeSignature(AppView<?> appView) { + public ArrayTypeSignature toArrayTypeSignature() { return new ArrayTypeSignature(this); } - @Override - public TypeSignature toArrayElementTypeSignature(AppView<?> appView) { - return elementSignature; + public void visit(GenericSignatureVisitor visitor) { + visitor.visitTypeSignature(elementSignature); } } @@ -449,7 +534,7 @@ } @Override - public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) { + public ArrayTypeSignature toArrayTypeSignature() { return new ArrayTypeSignature(this); } @@ -479,7 +564,7 @@ } @Override - public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) { + public ArrayTypeSignature toArrayTypeSignature() { assert !type.isVoidType(); return new ArrayTypeSignature(this); } @@ -505,15 +590,20 @@ } public static class MethodTypeSignature implements DexDefinitionSignature<DexEncodedMethod> { - static final MethodTypeSignature UNKNOWN_METHOD_TYPE_SIGNATURE = + + private static final MethodTypeSignature NO_METHOD_TYPE_SIGNATURE = new MethodTypeSignature( - ImmutableList.of(), ImmutableList.of(), ReturnType.VOID, ImmutableList.of()); + EMPTY_TYPE_PARAMS, EMPTY_TYPE_SIGNATURES, ReturnType.VOID, EMPTY_TYPE_SIGNATURES); final List<FormalTypeParameter> formalTypeParameters; final List<TypeSignature> typeSignatures; final ReturnType returnType; final List<TypeSignature> throwsSignatures; + public static MethodTypeSignature noSignature() { + return NO_METHOD_TYPE_SIGNATURE; + } + MethodTypeSignature( final List<FormalTypeParameter> formalTypeParameters, List<TypeSignature> typeSignatures, @@ -550,109 +640,100 @@ } @Override + public boolean hasSignature() { + return this != NO_METHOD_TYPE_SIGNATURE; + } + + @Override public MethodTypeSignature asMethodTypeSignature() { return this; } + public void visit(GenericSignatureVisitor visitor) { + visitor.visitFormalTypeParameters(formalTypeParameters); + visitor.visitMethodTypeSignatures(typeSignatures); + visitor.visitReturnType(returnType); + visitor.visitThrowsSignatures(throwsSignatures); + } + public List<FormalTypeParameter> getFormalTypeParameters() { return formalTypeParameters; } - } - enum Kind { - CLASS, FIELD, METHOD; - - static Kind fromDexDefinition(DexDefinition definition) { - if (definition.isDexClass()) { - return CLASS; + public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) { + if (hasNoSignature()) { + return null; } - if (definition.isDexEncodedField()) { - return FIELD; - } - if (definition.isDexEncodedMethod()) { - return METHOD; - } - throw new Unreachable("Unexpected kind of DexDefinition: " + definition); + GenericSignaturePrinter genericSignaturePrinter = + new GenericSignaturePrinter(namingLens, isTypeMissing); + genericSignaturePrinter.visitMethodSignature(this); + return genericSignaturePrinter.toString(); } - Function<String, ? extends DexDefinitionSignature<? extends DexDefinition>> - parserMethod(Parser parser) { - switch (this) { - case CLASS: - return parser::parseClassSignature; - case FIELD: - return parser::parseFieldTypeSignature; - case METHOD: - return parser::parseMethodTypeSignature; - } - throw new Unreachable("Unexpected kind: " + this); + @Override + public String toString() { + return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue()); + } + } + + public static ClassSignature parseClassSignature( + String className, + String signature, + Origin origin, + DexItemFactory factory, + Reporter reporter) { + if (signature == null || signature.isEmpty()) { + return ClassSignature.NO_CLASS_SIGNATURE; + } + Parser parser = new Parser(factory); + try { + return parser.parseClassSignature(signature); + } catch (GenericSignatureFormatError e) { + reporter.warning( + GenericSignatureDiagnostic.invalidClassSignature(signature, className, origin, e)); + return ClassSignature.NO_CLASS_SIGNATURE; + } + } + + public static FieldTypeSignature parseFieldTypeSignature( + String fieldName, + String signature, + Origin origin, + DexItemFactory factory, + Reporter reporter) { + if (signature == null || signature.isEmpty()) { + return NO_FIELD_TYPE_SIGNATURE; + } + Parser parser = new Parser(factory); + try { + return parser.parseFieldTypeSignature(signature); + } catch (GenericSignatureFormatError e) { + reporter.warning( + GenericSignatureDiagnostic.invalidFieldSignature(signature, fieldName, origin, e)); + return GenericSignature.NO_FIELD_TYPE_SIGNATURE; + } + } + + public static MethodTypeSignature parseMethodSignature( + String methodName, + String signature, + Origin origin, + DexItemFactory factory, + Reporter reporter) { + if (signature == null || signature.isEmpty()) { + return MethodTypeSignature.NO_METHOD_TYPE_SIGNATURE; + } + Parser parser = new Parser(factory); + try { + return parser.parseMethodTypeSignature(signature); + } catch (GenericSignatureFormatError e) { + reporter.warning( + GenericSignatureDiagnostic.invalidMethodSignature(signature, methodName, origin, e)); + return MethodTypeSignature.NO_METHOD_TYPE_SIGNATURE; } } public static class Parser { - // TODO(b/129925954): Can we merge variants of to*Signature below and just expose - // type-parameterized version of this, like - // <T extends DexDefinitionSignature<?>> T toGenericSignature - // without unchecked cast? - private static DexDefinitionSignature<? extends DexDefinition> toGenericSignature( - DexClass currentClassContext, - DexDefinition definition, - AppView<AppInfoWithLiveness> appView) { - DexAnnotationSet annotations = definition.annotations(); - if (annotations.annotations.length == 0) { - return null; - } - for (int i = 0; i < annotations.annotations.length; i++) { - DexAnnotation annotation = annotations.annotations[i]; - if (!DexAnnotation.isSignatureAnnotation(annotation, appView.dexItemFactory())) { - continue; - } - Kind kind = Kind.fromDexDefinition(definition); - Parser parser = new Parser(currentClassContext, appView); - String signature = DexAnnotation.getSignature(annotation); - try { - return kind.parserMethod(parser).apply(signature); - } catch (GenericSignatureFormatError e) { - appView.options().warningInvalidSignature( - definition, currentClassContext.getOrigin(), signature, e); - } - } - return null; - } - - public static ClassSignature toClassSignature( - DexClass clazz, AppView<AppInfoWithLiveness> appView) { - DexDefinitionSignature<?> signature = toGenericSignature(clazz, clazz, appView); - if (signature != null) { - assert signature.isClassSignature(); - return signature.asClassSignature(); - } - return ClassSignature.UNKNOWN_CLASS_SIGNATURE; - } - - public static FieldTypeSignature toFieldTypeSignature( - DexEncodedField field, AppView<AppInfoWithLiveness> appView) { - DexClass currentClassContext = appView.definitionFor(field.holder()); - DexDefinitionSignature<?> signature = - toGenericSignature(currentClassContext, field, appView); - if (signature != null) { - assert signature.isFieldTypeSignature(); - return signature.asFieldTypeSignature(); - } - return ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE; - } - - public static MethodTypeSignature toMethodTypeSignature( - DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) { - DexClass currentClassContext = appView.definitionFor(method.holder()); - DexDefinitionSignature<?> signature = - toGenericSignature(currentClassContext, method, appView); - if (signature != null) { - assert signature.isMethodTypeSignature(); - return signature.asMethodTypeSignature(); - } - return MethodTypeSignature.UNKNOWN_METHOD_TYPE_SIGNATURE; - } /* * Parser: @@ -672,9 +753,8 @@ private int pos; - private Parser(DexClass currentClassContext, AppView<AppInfoWithLiveness> appView) { - this.currentClassContext = currentClassContext; - this.appView = appView; + private Parser(DexItemFactory factory) { + this.factory = factory; } ClassSignature parseClassSignature(String signature) { @@ -708,7 +788,7 @@ FieldTypeSignature parseFieldTypeSignature(String signature) { try { setInput(signature); - return parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION); + return parseFieldTypeSignature(); } catch (GenericSignatureFormatError e) { throw e; } catch (Throwable t) { @@ -732,40 +812,11 @@ // Action: // - enum ParserPosition { - CLASS_SUPER_OR_INTERFACE_ANNOTATION, - ENCLOSING_INNER_OR_TYPE_ANNOTATION, - MEMBER_ANNOTATION - } + private final DexItemFactory factory; - private final AppView<AppInfoWithLiveness> appView; - private final DexClass currentClassContext; - private DexType lastWrittenType = null; - - private DexType parsedTypeName(String name, ParserPosition parserPosition) { - if (parserPosition == ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION - && lastWrittenType == null) { - // We are writing type-arguments for a merged class. - return null; - } + private DexType parsedTypeName(String name) { String originalDescriptor = getDescriptorFromClassBinaryName(name); - DexType type = - appView.graphLense().lookupType(appView.dexItemFactory().createType(originalDescriptor)); - if (appView.appInfo().wasPruned(type)) { - type = appView.dexItemFactory().objectType; - } - if (parserPosition == ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION - && currentClassContext != null) { - // We may have merged the type down to the current class type. - DexString classDescriptor = currentClassContext.type.descriptor; - if (!originalDescriptor.equals(classDescriptor.toString()) - && type.descriptor.equals(classDescriptor)) { - lastWrittenType = null; - return type; - } - } - lastWrittenType = type; - return type; + return factory.createType(originalDescriptor); } private DexType parsedInnerTypeName(DexType enclosingType, String name) { @@ -775,15 +826,11 @@ } assert enclosingType.isClassType(); String enclosingDescriptor = enclosingType.toDescriptorString(); - DexType type = - appView - .dexItemFactory() - .createType( - getDescriptorFromClassBinaryName( - getClassBinaryNameFromDescriptor(enclosingDescriptor) - + DescriptorUtils.INNER_CLASS_SEPARATOR - + name)); - return appView.graphLense().lookupType(type); + return factory.createType( + getDescriptorFromClassBinaryName( + getClassBinaryNameFromDescriptor(enclosingDescriptor) + + DescriptorUtils.INNER_CLASS_SEPARATOR + + name)); } // @@ -796,13 +843,12 @@ List<FormalTypeParameter> formalTypeParameters = parseOptFormalTypeParameters(); // SuperclassSignature ::= ClassTypeSignature. - ClassTypeSignature superClassSignature = - parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION); + ClassTypeSignature superClassSignature = parseClassTypeSignature(); ImmutableList.Builder<ClassTypeSignature> builder = ImmutableList.builder(); while (symbol > 0) { // SuperinterfaceSignature ::= ClassTypeSignature. - builder.add(parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION)); + builder.add(parseClassTypeSignature()); } return new ClassSignature(formalTypeParameters, superClassSignature, builder.build()); @@ -833,9 +879,9 @@ // ClassBound ::= ":" FieldTypeSignature?. expect(':'); - FieldTypeSignature classBound = ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE; + FieldTypeSignature classBound = GenericSignature.NO_FIELD_TYPE_SIGNATURE; if (symbol == 'L' || symbol == '[' || symbol == 'T') { - classBound = parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION); + classBound = parseFieldTypeSignature(); } // Only build the interfacebound builder, which is uncommon, if we actually see an interface. @@ -846,7 +892,7 @@ builder = ImmutableList.builder(); } scanSymbol(); - builder.add(parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION)); + builder.add(parseFieldTypeSignature()); } if (builder == null) { return new FormalTypeParameter(typeParameterIdentifier, classBound, null); @@ -854,16 +900,16 @@ return new FormalTypeParameter(typeParameterIdentifier, classBound, builder.build()); } - private FieldTypeSignature parseFieldTypeSignature(ParserPosition parserPosition) { + private FieldTypeSignature parseFieldTypeSignature() { // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature. switch (symbol) { case 'L': - return parseClassTypeSignature(parserPosition); + return parseClassTypeSignature(); case '[': // ArrayTypeSignature ::= "[" TypeSignature. scanSymbol(); - TypeSignature baseTypeSignature = updateTypeSignature(parserPosition); - return baseTypeSignature.toArrayTypeSignature(appView).asFieldTypeSignature(); + TypeSignature baseTypeSignature = updateTypeSignature(); + return baseTypeSignature.toArrayTypeSignature().asFieldTypeSignature(); case 'T': return updateTypeVariableSignature(); default: @@ -872,7 +918,7 @@ throw new Unreachable("Either FieldTypeSignature is returned or a parse error is thrown."); } - private ClassTypeSignature parseClassTypeSignature(ParserPosition parserPosition) { + private ClassTypeSignature parseClassTypeSignature() { // ClassTypeSignature ::= // "L" (Identifier "/")* Identifier TypeArguments? ("." Identifier TypeArguments?)* ";". expect('L'); @@ -888,11 +934,12 @@ } qualIdent.append(this.identifier); - DexType parsedEnclosingType = parsedTypeName(qualIdent.toString(), parserPosition); + DexType parsedEnclosingType = parsedTypeName(qualIdent.toString()); List<FieldTypeSignature> typeArguments = updateOptTypeArguments(); ClassTypeSignature outerMostTypeSignature = - new ClassTypeSignature(parsedEnclosingType, typeArguments); + new ClassTypeSignature( + parsedEnclosingType, typeArguments.isEmpty() ? EMPTY_TYPE_ARGUMENTS : typeArguments); ClassTypeSignature outerTypeSignature = outerMostTypeSignature; ClassTypeSignature innerTypeSignature; @@ -903,7 +950,10 @@ assert identifier != null; parsedEnclosingType = parsedInnerTypeName(parsedEnclosingType, identifier); typeArguments = updateOptTypeArguments(); - innerTypeSignature = new ClassTypeSignature(parsedEnclosingType, typeArguments); + innerTypeSignature = + new ClassTypeSignature( + parsedEnclosingType, + typeArguments.isEmpty() ? EMPTY_TYPE_ARGUMENTS : typeArguments); ClassTypeSignature.link(outerTypeSignature, innerTypeSignature); outerTypeSignature = innerTypeSignature; } @@ -935,15 +985,12 @@ return StarFieldTypeSignature.STAR_FIELD_TYPE_SIGNATURE; } else if (symbol == '+') { scanSymbol(); - return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION) - .asArgument(WildcardIndicator.POSITIVE); + return parseFieldTypeSignature().asArgument(WildcardIndicator.POSITIVE); } else if (symbol == '-') { scanSymbol(); - return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION) - .asArgument(WildcardIndicator.NEGATIVE); + return parseFieldTypeSignature().asArgument(WildcardIndicator.NEGATIVE); } else { - return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION) - .asArgument(WildcardIndicator.NONE); + return parseFieldTypeSignature().asArgument(WildcardIndicator.NONE); } } @@ -958,7 +1005,7 @@ return new TypeVariableSignature(identifier); } - private TypeSignature updateTypeSignature(ParserPosition parserPosition) { + private TypeSignature updateTypeSignature() { switch (symbol) { case 'B': case 'C': @@ -968,13 +1015,13 @@ case 'J': case 'S': case 'Z': - DexType type = appView.dexItemFactory().createType(String.valueOf(symbol)); + DexType type = factory.createType(String.valueOf(symbol)); BaseTypeSignature baseTypeSignature = new BaseTypeSignature(type); scanSymbol(); return baseTypeSignature; default: // Not an elementary type, but a FieldTypeSignature. - return parseFieldTypeSignature(parserPosition); + return parseFieldTypeSignature(); } } @@ -987,7 +1034,7 @@ ImmutableList.Builder<TypeSignature> parameterSignatureBuilder = ImmutableList.builder(); while (symbol != ')' && (symbol > 0)) { - parameterSignatureBuilder.add(updateTypeSignature(ParserPosition.MEMBER_ANNOTATION)); + parameterSignatureBuilder.add(updateTypeSignature()); } expect(')'); @@ -998,12 +1045,11 @@ if (symbol == '^') { do { scanSymbol(); - // ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature). if (symbol == 'T') { throwsSignatureBuilder.add(updateTypeVariableSignature()); } else { - throwsSignatureBuilder.add(parseClassTypeSignature(ParserPosition.MEMBER_ANNOTATION)); + throwsSignatureBuilder.add(parseClassTypeSignature()); } } while (symbol == '^'); } @@ -1018,7 +1064,7 @@ private ReturnType updateReturnType() { // ReturnType ::= TypeSignature | "V". if (symbol != 'V') { - return new ReturnType(updateTypeSignature(ParserPosition.MEMBER_ANNOTATION)); + return new ReturnType(updateTypeSignature()); } else { scanSymbol(); return ReturnType.VOID;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureDiagnostic.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureDiagnostic.java new file mode 100644 index 0000000..fa95bce --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureDiagnostic.java
@@ -0,0 +1,75 @@ +// 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.graph; + +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; +import java.lang.reflect.GenericSignatureFormatError; + +public class GenericSignatureDiagnostic implements Diagnostic { + + private final Origin origin; + private final Position position; + private final String message; + + GenericSignatureDiagnostic(Origin origin, Position position, String message) { + this.origin = origin; + this.position = position; + this.message = message; + } + + @Override + public Origin getOrigin() { + return origin; + } + + @Override + public Position getPosition() { + return position; + } + + @Override + public String getDiagnosticMessage() { + return message; + } + + static GenericSignatureDiagnostic invalidClassSignature( + String signature, String name, Origin origin, GenericSignatureFormatError error) { + return invalidSignature(signature, "class", name, origin, error); + } + + static GenericSignatureDiagnostic invalidMethodSignature( + String signature, String name, Origin origin, GenericSignatureFormatError error) { + return invalidSignature(signature, "method", name, origin, error); + } + + static GenericSignatureDiagnostic invalidFieldSignature( + String signature, String name, Origin origin, GenericSignatureFormatError error) { + return invalidSignature(signature, "field", name, origin, error); + } + + private static GenericSignatureDiagnostic invalidSignature( + String signature, + String kind, + String name, + Origin origin, + GenericSignatureFormatError error) { + String message = + "Invalid signature '" + + signature + + "' for " + + kind + + " " + + name + + "." + + System.lineSeparator() + + "Signature is ignored and will not be present in the output." + + System.lineSeparator() + + "Parser error: " + + error.getMessage(); + return new GenericSignatureDiagnostic(origin, Position.UNKNOWN, message); + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java new file mode 100644 index 0000000..f68675f --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -0,0 +1,184 @@ +// 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.graph; + +import com.android.tools.r8.graph.GenericSignature.ClassSignature; +import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature; +import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature; +import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter; +import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; +import com.android.tools.r8.graph.GenericSignature.ReturnType; +import com.android.tools.r8.graph.GenericSignature.TypeSignature; +import com.android.tools.r8.graph.GenericSignature.WildcardIndicator; +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.utils.DescriptorUtils; +import java.util.List; +import java.util.function.Predicate; + +public class GenericSignaturePrinter implements GenericSignatureVisitor { + + private final NamingLens namingLens; + private final Predicate<DexType> isTypeMissing; + + public GenericSignaturePrinter(NamingLens namingLens, Predicate<DexType> isTypeMissing) { + this.namingLens = namingLens; + this.isTypeMissing = isTypeMissing; + } + + private final StringBuilder sb = new StringBuilder(); + + @Override + public void visitClassSignature(ClassSignature classSignature) { + classSignature.visit(this); + } + + @Override + public void visitFieldTypeSignature(FieldTypeSignature fieldSignature) { + printFieldTypeSignature(fieldSignature, false); + } + + @Override + public void visitMethodSignature(MethodTypeSignature methodSignature) { + methodSignature.visit(this); + } + + @Override + public void visitMethodTypeSignatures(List<TypeSignature> typeSignatures) { + sb.append("("); + typeSignatures.forEach(this::visitTypeSignature); + sb.append(")"); + } + + @Override + public void visitReturnType(ReturnType returnType) { + if (returnType.isVoidDescriptor()) { + sb.append("V"); + } else { + visitTypeSignature(returnType.typeSignature); + } + } + + @Override + public void visitThrowsSignatures(List<TypeSignature> typeSignatures) { + for (TypeSignature typeSignature : typeSignatures) { + sb.append("^"); + visitTypeSignature(typeSignature); + } + } + + @Override + public void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) { + if (formalTypeParameters.isEmpty()) { + return; + } + sb.append("<"); + for (FormalTypeParameter formalTypeParameter : formalTypeParameters) { + sb.append(formalTypeParameter.name); + formalTypeParameter.visit(this); + } + sb.append(">"); + } + + @Override + public void visitClassBound(FieldTypeSignature fieldSignature) { + sb.append(":"); + printFieldTypeSignature(fieldSignature, false); + } + + @Override + public void visitInterfaceBound(FieldTypeSignature fieldSignature) { + sb.append(":"); + printFieldTypeSignature(fieldSignature, false); + } + + @Override + public void visitSuperClass(ClassTypeSignature classTypeSignature) { + printFieldTypeSignature(classTypeSignature, false); + } + + @Override + public void visitSuperInterface(ClassTypeSignature classTypeSignature) { + printFieldTypeSignature(classTypeSignature, false); + } + + @Override + public void visitTypeSignature(TypeSignature typeSignature) { + if (typeSignature.isBaseTypeSignature()) { + DexType type = typeSignature.asBaseTypeSignature().type; + sb.append(type.toDescriptorString()); + } else { + printFieldTypeSignature(typeSignature.asFieldTypeSignature(), false); + } + } + + @Override + public void visitSimpleClass(ClassTypeSignature classTypeSignature) { + printFieldTypeSignature(classTypeSignature, true); + } + + @Override + public void visitTypeArguments(List<FieldTypeSignature> typeArguments) { + if (typeArguments.isEmpty()) { + return; + } + sb.append("<"); + for (FieldTypeSignature typeArgument : typeArguments) { + WildcardIndicator wildcardIndicator = typeArgument.getWildcardIndicator(); + if (wildcardIndicator != WildcardIndicator.NONE) { + assert wildcardIndicator != WildcardIndicator.NOT_AN_ARGUMENT; + sb.append(wildcardIndicator == WildcardIndicator.POSITIVE ? "+" : "-"); + } + visitTypeSignature(typeArgument); + } + sb.append(">"); + } + + private void printFieldTypeSignature( + FieldTypeSignature fieldTypeSignature, boolean printingInner) { + // For inner member classes we only print the inner name and the type-arguments. + if (fieldTypeSignature.isStar()) { + sb.append("*"); + } else if (fieldTypeSignature.isTypeVariableSignature()) { + sb.append("T").append(fieldTypeSignature.asTypeVariableSignature().typeVariable).append(";"); + } else if (fieldTypeSignature.isArrayTypeSignature()) { + sb.append("["); + fieldTypeSignature.asArrayTypeSignature().visit(this); + } else { + assert fieldTypeSignature.isClassTypeSignature(); + ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature(); + if (classTypeSignature.isNoSignature()) { + return; + } + String renamedString = namingLens.lookupDescriptor(classTypeSignature.type).toString(); + if (!printingInner) { + sb.append("L").append(DescriptorUtils.getBinaryNameFromDescriptor(renamedString)); + } else { + assert classTypeSignature.enclosingTypeSignature != null; + DexType enclosingType = classTypeSignature.enclosingTypeSignature.type; + String outerDescriptor = namingLens.lookupDescriptor(enclosingType).toString(); + String innerClassName = DescriptorUtils.getInnerClassName(outerDescriptor, renamedString); + if (innerClassName == null && isTypeMissing.test(classTypeSignature.type)) { + assert renamedString.equals(classTypeSignature.type.toDescriptorString()); + innerClassName = + DescriptorUtils.getInnerClassName(enclosingType.toDescriptorString(), renamedString); + } + if (innerClassName == null) { + // We can no longer encode the inner name in the generic signature. + return; + } + sb.append(".").append(innerClassName); + } + classTypeSignature.visit(this); + if (!printingInner) { + sb.append(";"); + } + } + } + + @Override + public String toString() { + return sb.toString(); + } +}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java new file mode 100644 index 0000000..d3840dd --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
@@ -0,0 +1,74 @@ +// 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.graph; + +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.GenericSignature.ClassSignature; +import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature; +import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature; +import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter; +import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; +import com.android.tools.r8.graph.GenericSignature.ReturnType; +import com.android.tools.r8.graph.GenericSignature.TypeSignature; +import java.util.List; + +public interface GenericSignatureVisitor { + + default void visitClassSignature(ClassSignature classSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitMethodSignature(MethodTypeSignature methodSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitFieldTypeSignature(FieldTypeSignature fieldSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) { + throw new Unreachable("Implement if visited"); + } + + default void visitClassBound(FieldTypeSignature fieldSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitInterfaceBound(FieldTypeSignature fieldSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitSuperClass(ClassTypeSignature classTypeSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitSuperInterface(ClassTypeSignature classTypeSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitTypeSignature(TypeSignature typeSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitSimpleClass(ClassTypeSignature classTypeSignature) { + throw new Unreachable("Implement if visited"); + } + + default void visitReturnType(ReturnType returnType) { + throw new Unreachable("Implement if visited"); + } + + default void visitMethodTypeSignatures(List<TypeSignature> typeSignatures) { + throw new Unreachable("Implement if visited"); + } + + default void visitThrowsSignatures(List<TypeSignature> typeSignatures) { + throw new Unreachable("Implement if visited"); + } + + default void visitTypeArguments(List<FieldTypeSignature> typeArguments) { + throw new Unreachable("Implement if visited"); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java new file mode 100644 index 0000000..7651e51 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java
@@ -0,0 +1,124 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.desugar; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.GenericSignature; +import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature; +import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature; +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class DesugaredLibraryEmulatedInterfaceDuplicator { + + final AppView<?> appView; + final Map<DexType, DexType> emulatedInterfaces; + + public DesugaredLibraryEmulatedInterfaceDuplicator(AppView<?> appView) { + this.appView = appView; + emulatedInterfaces = + appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface(); + } + + public void duplicateEmulatedInterfaces() { + // All classes implementing an emulated interface now implements the interface and the + // emulated one, as well as hidden overrides, for correct emulated dispatch. + // We not that duplicated interfaces won't feature the correct type parameters in the + // class signature since such signature is expected to be unused. + for (DexProgramClass clazz : appView.appInfo().classes()) { + if (clazz.type == appView.dexItemFactory().objectType) { + continue; + } + if (emulatedInterfaces.containsKey(clazz.type)) { + transformEmulatedInterfaces(clazz); + } else { + duplicateEmulatedInterfaces(clazz); + } + } + } + + private void transformEmulatedInterfaces(DexProgramClass clazz) { + List<ClassTypeSignature> newInterfaces = new ArrayList<>(); + GenericSignature.ClassSignature classSignature = clazz.getClassSignature(appView); + for (int i = 0; i < clazz.interfaces.size(); i++) { + DexType itf = clazz.interfaces.values[i]; + assert emulatedInterfaces.containsKey(itf); + List<FieldTypeSignature> typeArguments; + if (classSignature.hasNoSignature()) { + typeArguments = Collections.emptyList(); + } else { + ClassTypeSignature classTypeSignature = classSignature.superInterfaceSignatures().get(i); + assert itf == classTypeSignature.type(); + typeArguments = classTypeSignature.typeArguments(); + } + newInterfaces.add(new ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments)); + } + clazz.replaceInterfaces(newInterfaces, appView); + } + + private void duplicateEmulatedInterfaces(DexProgramClass clazz) { + List<DexType> extraInterfaces = new ArrayList<>(); + LinkedList<DexClass> workList = new LinkedList<>(); + Set<DexType> processed = Sets.newIdentityHashSet(); + workList.add(clazz); + while (!workList.isEmpty()) { + DexClass dexClass = workList.removeFirst(); + if (processed.contains(dexClass.type)) { + continue; + } + processed.add(dexClass.type); + if (dexClass.superType != appView.dexItemFactory().objectType) { + processSuperType(clazz.superType, extraInterfaces, workList); + } + for (DexType itf : dexClass.interfaces.values) { + processSuperType(itf, extraInterfaces, workList); + } + } + extraInterfaces = removeDuplicates(extraInterfaces); + List<ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>(); + for (DexType extraInterface : extraInterfaces) { + extraInterfaceSignatures.add(new ClassTypeSignature(extraInterface)); + } + clazz.addExtraInterfaces(extraInterfaceSignatures, appView); + } + + private List<DexType> removeDuplicates(List<DexType> extraInterfaces) { + if (extraInterfaces.size() <= 1) { + return extraInterfaces; + } + // TODO(b/161399032): It would be nice to remove duplicate based on inheritance, i.e., + // if there is ConcurrentMap<K,V> and Map<K,V>, Map<K,V> can be removed. + return new ArrayList<>(new HashSet<>(extraInterfaces)); + } + + void processSuperType( + DexType superType, List<DexType> extraInterfaces, LinkedList<DexClass> workList) { + if (emulatedInterfaces.containsKey(superType)) { + extraInterfaces.add(emulatedInterfaces.get(superType)); + } else { + DexClass superClass = appView.definitionFor(superType); + if (shouldProcessSuperclass(superClass)) { + workList.add(superClass); + } + } + } + + private boolean shouldProcessSuperclass(DexClass superclazz) { + if (appView.options().isDesugaredLibraryCompilation()) { + return false; + } + // TODO(b/161399032): Pay-as-you-go design: stop duplication on library boundaries. + return superclazz != null && superclazz.isLibraryClass(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java index 408b246..d81df77 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -18,6 +18,7 @@ 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.GenericSignature.ClassTypeSignature; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ParameterAnnotationsList; import com.android.tools.r8.graph.ResolutionResult; @@ -324,7 +325,8 @@ // applies up to 24. for (DexMethod method : methods) { clazz.addExtraInterfaces( - Collections.singletonList(dispatchInterfaceTypeFor(method)), appView.dexItemFactory()); + Collections.singletonList(new ClassTypeSignature(dispatchInterfaceTypeFor(method))), + appView); if (clazz.lookupVirtualMethod(method) == null) { DexEncodedMethod newMethod = createForwardingMethod(method, clazz); clazz.addVirtualMethod(newMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java index b963160..20f7a7a 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
@@ -53,7 +53,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.IdentityHashMap; -import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @@ -917,63 +916,6 @@ return newMethods; } - private void duplicateEmulatedInterfaces() { - // All classes implementing an emulated interface now implements the interface and the - // emulated one, as well as hidden overrides, for correct emulated dispatch. - for (DexProgramClass clazz : appView.appInfo().classes()) { - if (clazz.type == appView.dexItemFactory().objectType) { - continue; - } - List<DexType> extraInterfaces = new ArrayList<>(); - for (DexType type : clazz.interfaces.values) { - if (emulatedInterfaces.containsKey(type)) { - extraInterfaces.add(emulatedInterfaces.get(type)); - } - } - if (!appView.options().isDesugaredLibraryCompilation()) { - assert clazz.superType != null; - DexClass superClazz = appView.definitionFor(clazz.superType); - if (superClazz != null && superClazz.isLibraryClass()) { - List<DexType> itfs = emulatedInterfacesOf(superClazz); - for (DexType itf : itfs) { - extraInterfaces.add(emulatedInterfaces.get(itf)); - } - } - // Remove duplicates. - if (extraInterfaces.size() > 1) { - extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces)); - } - } - clazz.addExtraInterfaces(extraInterfaces, appView.dexItemFactory()); - } - } - - private List<DexType> emulatedInterfacesOf(DexClass superClazz) { - if (superClazz.type == factory.objectType) { - return Collections.emptyList(); - } - ArrayList<DexType> itfs = new ArrayList<>(); - LinkedList<DexType> workList = new LinkedList<>(); - workList.add(superClazz.type); - while (!workList.isEmpty()) { - DexType dexType = workList.removeFirst(); - DexClass dexClass = appView.definitionFor(dexType); - if (dexClass != null) { - if (dexClass.superType != factory.objectType) { - workList.add(dexClass.superType); - } - for (DexType itf : dexClass.interfaces.values) { - if (emulatedInterfaces.containsKey(itf)) { - itfs.add(itf); - } else { - workList.add(itf); - } - } - } - } - return itfs; - } - /** * Move static and default interface methods to companion classes, add missing methods to forward * to moved default methods implementation. @@ -986,7 +928,7 @@ if (appView.options().isDesugaredLibraryCompilation()) { generateEmulateInterfaceLibrary(builder); } - duplicateEmulatedInterfaces(); + new DesugaredLibraryEmulatedInterfaceDuplicator(appView).duplicateEmulatedInterfaces(); // Process all classes first. Add missing forwarding methods to // replace desugared default interface methods.
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java index 584b84b..fa73933 100644 --- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -329,7 +329,7 @@ public static void clearAnnotations(AppView<?> appView) { for (DexProgramClass clazz : appView.appInfo().classes()) { - clazz.clearAnnotations(); + clazz.clearAnnotationsButSignature(appView.dexItemFactory()); clazz.members().forEach(DexDefinition::clearAnnotations); } }
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java index 1173ef5..3138d0c 100644 --- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java +++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -517,6 +517,27 @@ binaryName.replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR)); } + /** + * Computes the inner name from the outer- and inner descriptors. If outer is not a prefix of the + * inner descriptor null is returned. Do not use this method if the relationship between inner and + * outer is not reflected in the name. + * + * @param outerDescriptor the outer descriptor, such as Lfoo/bar/Baz; + * @param innerDescriptor the inner descriptor, such as Lfoo/bar/Baz$Qux; + * @return the inner name or null, i.e. Qux in the example above + */ + public static String getInnerClassName(String outerDescriptor, String innerDescriptor) { + if (innerDescriptor.length() <= outerDescriptor.length()) { + return null; + } + String prefix = + outerDescriptor.substring(0, outerDescriptor.length() - 1) + INNER_CLASS_SEPARATOR; + if (innerDescriptor.startsWith(prefix)) { + return innerDescriptor.substring(prefix.length(), innerDescriptor.length() - 1); + } + return null; + } + public static class ModuleAndDescriptor { private final String module; private final String descriptor;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java index 0eab37e..ced1c31 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
@@ -57,9 +57,7 @@ return; } String keepRules = keepRuleConsumer.get(); - assertThat(keepRules, containsString("-keep class j$.util.Map")); assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentHashMap")); - assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentMap")); } @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/AllMapsTestClass.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/AllMapsTestClass.java new file mode 100644 index 0000000..4c15672 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/AllMapsTestClass.java
@@ -0,0 +1,347 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.desugaredlibrary.gson; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class AllMapsTestClass { + // Program class extending ConcurrentHashMap. + static class NullableConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> { + NullableConcurrentHashMap() { + super(); + } + + @SuppressWarnings("NullableProblems") + @Override + public V put(K key, V value) { + if (key == null || value == null) { + return null; + } + return super.put(key, value); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + for (Entry<? extends K, ? extends V> entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + } + // Program class extending the library class HashMap, implementing Map. + static class NullableHashMap<K, V> extends HashMap<K, V> { + NullableHashMap() { + super(); + } + + @SuppressWarnings("NullableProblems") + @Override + public V put(K key, V value) { + if (key == null || value == null) { + return null; + } + return super.put(key, value); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + for (Entry<? extends K, ? extends V> entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + } + // Program class implementing Map. + static class NullableMap<K, V> implements Map<K, V> { + private HashMap<K, V> map = new HashMap<>(); + + NullableMap() { + super(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + return map.get(key); + } + + @Override + public V put(K key, V value) { + return map.put(key, value); + } + + @Override + public V remove(Object key) { + return map.remove(key); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + for (Entry<? extends K, ? extends V> entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set<K> keySet() { + return map.keySet(); + } + + @Override + public Collection<V> values() { + return map.values(); + } + + @Override + public Set<Entry<K, V>> entrySet() { + return map.entrySet(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NullableMap)) return false; + NullableMap<?, ?> that = (NullableMap<?, ?>) o; + return map.equals(that.map); + } + + @Override + public int hashCode() { + return Objects.hash(map); + } + } + // Program class implementing ConcurrentMap. + static class NullableConcurrentMap<K, V> implements ConcurrentMap<K, V> { + private HashMap<K, V> map = new HashMap<>(); + + NullableConcurrentMap() { + super(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + return map.get(key); + } + + @Override + public V put(K key, V value) { + return map.put(key, value); + } + + @Override + public V remove(Object key) { + return map.remove(key); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + for (Entry<? extends K, ? extends V> entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set<K> keySet() { + return map.keySet(); + } + + @Override + public Collection<V> values() { + return map.values(); + } + + @Override + public Set<Entry<K, V>> entrySet() { + return map.entrySet(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NullableConcurrentMap)) return false; + NullableConcurrentMap<?, ?> that = (NullableConcurrentMap<?, ?>) o; + return map.equals(that.map); + } + + @Override + public int hashCode() { + return Objects.hash(map); + } + + @Override + public V putIfAbsent(K key, V value) { + return null; + } + + @Override + public boolean remove(Object key, Object value) { + return false; + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return false; + } + + @Override + public V replace(K key, V value) { + return null; + } + } + + static class Data { + final int id; + final String name; + + Data(int id, String name) { + this.id = id; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Data)) return false; + Data data = (Data) o; + return id == data.id && name.equals(data.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @Override + public String toString() { + return "Data{" + "id=" + id + ", name='" + name + '\'' + '}'; + } + } + + public static void main(String[] args) { + Gson gson = new Gson(); + + HashMap<Integer, Data> hashMap = new HashMap<>(); + NullableHashMap<Integer, Data> nullableHashMap = new NullableHashMap<>(); + NullableMap<Integer, Data> nullableMap = new NullableMap<>(); + NullableConcurrentMap<Integer, Data> nullableConcurrentMap = new NullableConcurrentMap<>(); + ConcurrentHashMap<Integer, Data> concurrentHashMap = new ConcurrentHashMap<>(); + NullableConcurrentHashMap<Integer, Data> nullableConcurrentHashMap = + new NullableConcurrentHashMap<>(); + + fillMap(hashMap); + fillMap(nullableHashMap); + fillMap(nullableMap); + fillMap(nullableConcurrentMap); + fillMap(concurrentHashMap); + fillMap(nullableConcurrentHashMap); + + // Serialization. + String hashMapJson = gson.toJson(hashMap); + String nullableHashMapJson = gson.toJson(nullableHashMap); + String nullableMapJson = gson.toJson(nullableMap); + String nullableConcurrentMapJson = gson.toJson(nullableConcurrentMap); + String concurrentHashMapJson = gson.toJson(concurrentHashMap); + String nullableConcurrentHashMapJson = gson.toJson(nullableConcurrentHashMap); + + // Deserialization. + Type hashMapType = new TypeToken<HashMap<Integer, Data>>() {}.getType(); + HashMap<Integer, Data> hashMapDeserialized = gson.fromJson(hashMapJson, hashMapType); + Type nullableHashMapType = new TypeToken<NullableHashMap<Integer, Data>>() {}.getType(); + NullableHashMap<Integer, Data> nullableHashMapDeserialized = + gson.fromJson(nullableHashMapJson, nullableHashMapType); + Type nullableMapType = new TypeToken<NullableMap<Integer, Data>>() {}.getType(); + NullableMap<Integer, Data> nullableMapDeserialized = + gson.fromJson(nullableMapJson, nullableMapType); + Type nullableConcurrentMapType = + new TypeToken<NullableConcurrentMap<Integer, Data>>() {}.getType(); + NullableConcurrentMap<Integer, Data> nullableConcurrentMapDeserialized = + gson.fromJson(nullableConcurrentMapJson, nullableConcurrentMapType); + Type concurrentHashMapType = new TypeToken<ConcurrentHashMap<Integer, Data>>() {}.getType(); + ConcurrentHashMap<Integer, Data> concurrentHashMapDeserialized = + gson.fromJson(concurrentHashMapJson, concurrentHashMapType); + Type nullableConcurrentHashMapType = + new TypeToken<NullableConcurrentHashMap<Integer, Data>>() {}.getType(); + NullableConcurrentHashMap<Integer, Data> nullableConcurrentHashMapDeserialized = + gson.fromJson(nullableConcurrentHashMapJson, nullableConcurrentHashMapType); + + // Printing. + System.out.println(hashMap.getClass() == hashMapDeserialized.getClass()); + System.out.println(hashMap.equals(hashMapDeserialized)); + System.out.println(nullableHashMap.getClass() == nullableHashMapDeserialized.getClass()); + System.out.println(nullableHashMap.equals(nullableHashMapDeserialized)); + System.out.println(nullableMap.getClass() == nullableMapDeserialized.getClass()); + System.out.println(nullableMap.equals(nullableMapDeserialized)); + System.out.println( + nullableConcurrentMap.getClass() == nullableConcurrentMapDeserialized.getClass()); + System.out.println(nullableConcurrentMap.equals(nullableConcurrentMapDeserialized)); + System.out.println(concurrentHashMap.getClass() == concurrentHashMapDeserialized.getClass()); + System.out.println(concurrentHashMap.equals(concurrentHashMapDeserialized)); + System.out.println( + nullableConcurrentHashMap.getClass() == nullableConcurrentHashMapDeserialized.getClass()); + System.out.println(nullableConcurrentHashMap.equals(nullableConcurrentHashMapDeserialized)); + } + + public static void fillMap(Map<Integer, Data> map) { + map.put(1, new Data(1, "a")); + map.put(2, new Data(2, "b")); + map.put(3, new Data(3, "c")); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java similarity index 97% rename from src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java rename to src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java index 6e300ba..249f26f 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
@@ -1,13 +1,14 @@ // 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 +// for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.desugar.desugaredlibrary; +package com.android.tools.r8.desugar.desugaredlibrary.gson; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestParameters; +import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; import com.android.tools.r8.utils.BooleanUtils; import dalvik.system.PathClassLoader; import java.sql.SQLDataException; @@ -31,7 +32,7 @@ import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class LibrarySubclassInterfaceTest extends DesugaredLibraryTestBase { +public class GetGenericInterfaceTest extends DesugaredLibraryTestBase { private final TestParameters parameters; private final boolean shrinkDesugaredLibrary; @@ -42,7 +43,7 @@ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build()); } - public LibrarySubclassInterfaceTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + public GetGenericInterfaceTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; this.parameters = parameters; } @@ -52,7 +53,7 @@ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); String stdOut = testForD8() - .addInnerClasses(LibrarySubclassInterfaceTest.class) + .addInnerClasses(GetGenericInterfaceTest.class) .setMinApi(parameters.getApiLevel()) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) .compile() @@ -72,7 +73,7 @@ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); String stdOut = testForR8(Backend.DEX) - .addInnerClasses(LibrarySubclassInterfaceTest.class) + .addInnerClasses(GetGenericInterfaceTest.class) .addKeepMainRule(Executor.class) .noMinification() .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java new file mode 100644 index 0000000..7938465 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java
@@ -0,0 +1,77 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.desugar.desugaredlibrary.gson; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.BooleanUtils; +import java.util.List; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class GsonAllMapsTest extends GsonDesugaredLibraryTestBase { + private final TestParameters parameters; + private final boolean shrinkDesugaredLibrary; + private static final String[] EXPECTED_RESULT = + new String[] { + "true", "true", "true", "true", "true", "true", "true", "true", "true", "true", "true", + "true" + }; + + @Parameters(name = "shrinkDesugaredLibrary: {0}, runtime: {1}") + public static List<Object[]> data() { + return buildParameters( + BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build()); + } + + public GsonAllMapsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; + this.parameters = parameters; + } + + @Test + public void testGsonMapD8() throws Exception { + Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters)); + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForD8(parameters.getBackend()) + .addProgramClassesAndInnerClasses(AllMapsTestClass.class) + .addProgramFiles(GSON_2_8_1_JAR) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .setMinApi(parameters.getApiLevel()) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get() + GSON_LIBRARY_KEEP_RULES, + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), AllMapsTestClass.class) + .assertSuccessWithOutputLines(EXPECTED_RESULT); + } + + @Test + public void testGsonMapR8() throws Exception { + Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters)); + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForR8(parameters.getBackend()) + .addProgramClassesAndInnerClasses(AllMapsTestClass.class) + .addProgramFiles(GSON_2_8_1_JAR) + .addKeepMainRule(AllMapsTestClass.class) + .addKeepRuleFiles(GSON_CONFIGURATION) + .allowUnusedProguardConfigurationRules() + .allowDiagnosticMessages() + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .setMinApi(parameters.getApiLevel()) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get() + GSON_LIBRARY_KEEP_RULES, + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), AllMapsTestClass.class) + .assertSuccessWithOutputLines(EXPECTED_RESULT); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonDesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonDesugaredLibraryTestBase.java new file mode 100644 index 0000000..e761248 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonDesugaredLibraryTestBase.java
@@ -0,0 +1,20 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.desugar.desugaredlibrary.gson; + +import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; +import java.nio.file.Path; +import java.nio.file.Paths; + +public abstract class GsonDesugaredLibraryTestBase extends DesugaredLibraryTestBase { + protected static final Path GSON_CONFIGURATION = + Paths.get("src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg"); + protected static final Path GSON_2_8_1_JAR = Paths.get("third_party/iosched_2019/gson-2.8.1.jar"); + // We only need here, for all subclasses of java.util.Collection and java.util.Map, + // to keep the signature attribute. + protected static final String GSON_LIBRARY_KEEP_RULES = + "-keepattributes Signature\n" + + "-keepattributes EnclosingMethod\n" + + "-keepattributes InnerClasses\n"; +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.java new file mode 100644 index 0000000..6b75eb9 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.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.desugar.desugaredlibrary.gson; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.BooleanUtils; +import java.util.List; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class GsonOptionalTest extends GsonDesugaredLibraryTestBase { + private final TestParameters parameters; + private final boolean shrinkDesugaredLibrary; + + @Parameterized.Parameters(name = "shrinkDesugaredLibrary: {0}, runtime: {1}") + public static List<Object[]> data() { + return buildParameters( + BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build()); + } + + public GsonOptionalTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; + this.parameters = parameters; + } + + @Test + public void testGsonOptionalD8() throws Exception { + Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters)); + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForD8(parameters.getBackend()) + .addProgramClassesAndInnerClasses(OptionalTestClass.class) + .addProgramFiles(GSON_2_8_1_JAR) + .addOptionsModification(opt -> opt.ignoreMissingClasses = true) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .setMinApi(parameters.getApiLevel()) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get() + GSON_LIBRARY_KEEP_RULES, + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), OptionalTestClass.class) + .assertSuccessWithOutputLines("true", "true"); + } + + @Test + public void testGsonOptionalR8() throws Exception { + Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters)); + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + testForR8(parameters.getBackend()) + .addProgramClassesAndInnerClasses(OptionalTestClass.class) + .addProgramFiles(GSON_2_8_1_JAR) + .addKeepMainRule(OptionalTestClass.class) + .addKeepRuleFiles(GSON_CONFIGURATION) + .allowUnusedProguardConfigurationRules() + .addOptionsModification(opt -> opt.ignoreMissingClasses = true) + .allowDiagnosticMessages() + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .setMinApi(parameters.getApiLevel()) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get() + GSON_LIBRARY_KEEP_RULES, + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), OptionalTestClass.class) + .assertSuccessWithOutputLines("true", "true"); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/OptionalTestClass.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/OptionalTestClass.java new file mode 100644 index 0000000..fbc2c31 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/OptionalTestClass.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.desugar.desugaredlibrary.gson; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Objects; +import java.util.Optional; + +public class OptionalTestClass { + static class Data { + final int id; + final String name; + + Data(int id, String name) { + this.id = id; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Data)) return false; + Data data = (Data) o; + return id == data.id && name.equals(data.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @Override + public String toString() { + return "Data{" + "id=" + id + ", name='" + name + '\'' + '}'; + } + } + + static class OptionalAdapter<T> extends TypeAdapter<Optional<T>> { + private final TypeAdapter<T> delegate; + + public OptionalAdapter(TypeAdapter<T> delegate) { + this.delegate = delegate; + } + + @Override + public void write(JsonWriter out, Optional<T> value) throws IOException { + if (!value.isPresent()) { + out.nullValue(); + return; + } + delegate.write(out, value.get()); + } + + @Override + public Optional<T> read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return Optional.empty(); + } + return Optional.of(delegate.read(in)); + } + + @SuppressWarnings("unchecked") + public static OptionalAdapter getInstance(TypeToken typeToken) { + TypeAdapter delegate; + Type type = typeToken.getType(); + assert type instanceof ParameterizedType; + Type innerType = ((ParameterizedType) type).getActualTypeArguments()[0]; + delegate = new Gson().getAdapter(TypeToken.get(innerType)); + return new OptionalAdapter<>(delegate); + } + } + + public static void main(String[] args) { + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapter( + Optional.class, OptionalAdapter.getInstance(new TypeToken<Optional<Data>>() {})); + Gson gson = builder.create(); + Optional<Data> optionalData = Optional.of(new Data(1, "a")); + String optionalDataSerialized = gson.toJson(optionalData); + Optional<Data> optionalDataDeserialized = gson.fromJson(optionalDataSerialized, Optional.class); + System.out.println(optionalData.getClass() == optionalDataDeserialized.getClass()); + System.out.println(optionalData.equals(optionalDataDeserialized)); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg new file mode 100644 index 0000000..da7bcba --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg
@@ -0,0 +1,34 @@ +# 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. +# Gson uses generic type information stored in a class file when working with fields. +# R8 removes such information by default, so configure it to keep all of it. +-keepattributes Signature +-keepattributes EnclosingMethod +-keepattributes InnerClasses + +# For using GSON @Expose annotation +-keepattributes AnnotationDefault,RuntimeVisibleAnnotations + +# Gson specific classes +-dontwarn sun.misc.Unsafe + +# Application classes that will be serialized/deserialized over Gson +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$Data { <fields>; } +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableConcurrentHashMap +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableHashMap +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableMap +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableConcurrentMap +-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.OptionalTestClass$Data { <fields>; } + +# Prevent R8 from stripping interface information from TypeAdapter, TypeAdapterFactory, +# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) +-keep class * extends com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer + +# Prevent R8 from leaving Data object members always null +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName <fields>; +}
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java new file mode 100644 index 0000000..b94f484 --- /dev/null +++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java
@@ -0,0 +1,91 @@ +// 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.graph; + +import static org.junit.Assert.assertEquals; +import static org.objectweb.asm.Opcodes.ASM7; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.graph.GenericSignature.ClassSignature; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.Reporter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; + +@RunWith(Parameterized.class) +public class GenericSignatureIdentityTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public GenericSignatureIdentityTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testAllClassSignature() throws Exception { + testParseSignaturesInJar(ToolHelper.R8_WITH_DEPS_JAR); + } + + public static void testParseSignaturesInJar(Path jar) throws Exception { + GenericSignatureReader genericSignatureReader = new GenericSignatureReader(); + ZipInputStream inputStream = new ZipInputStream(Files.newInputStream(jar)); + ZipEntry next = inputStream.getNextEntry(); + while (next != null) { + if (next.getName().endsWith(".class")) { + ClassReader classReader = new ClassReader(inputStream); + classReader.accept(genericSignatureReader, 0); + } + next = inputStream.getNextEntry(); + } + } + + private static class GenericSignatureReader extends ClassVisitor { + + public final Set<String> signatures = new HashSet<>(); + private final DexItemFactory factory = new DexItemFactory(); + + private GenericSignatureReader() { + super(ASM7); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + if (signature == null) { + return; + } + TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl(); + ClassSignature classSignature = + GenericSignature.parseClassSignature( + name, signature, Origin.unknown(), factory, new Reporter(testDiagnosticMessages)); + assertEquals(signature, classSignature.toString()); + testDiagnosticMessages.assertNoMessages(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java index 5001645..366e98b 100644 --- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java +++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -19,11 +19,11 @@ import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature; import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter; import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; -import com.android.tools.r8.graph.GenericSignature.Parser; import com.android.tools.r8.graph.GenericSignature.ReturnType; import com.android.tools.r8.graph.GenericSignature.TypeSignature; import com.android.tools.r8.graph.GenericSignature.WildcardIndicator; import com.android.tools.r8.graph.GenericSignatureTestClassA.I; +import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.DescriptorUtils; @@ -103,7 +103,13 @@ // class <T:GenericSignatureTestClassA<T>.Y>CYY<T extends A<T>.Y> extends CY<T> DexClass clazz = cyy.getDexProgramClass(); assertNotNull(clazz); - classSignature = Parser.toClassSignature(clazz, appView); + classSignature = + GenericSignature.parseClassSignature( + clazz.getType().getName(), + getGenericSignature(clazz, appView), + clazz.origin, + appView.dexItemFactory(), + appView.options().reporter); assertNotNull(classSignature); assertEquals(1, classSignature.formalTypeParameters.size()); @@ -135,7 +141,13 @@ DexEncodedField field = yyInZZ.getField(); assertNotNull(field); - fieldTypeSignature = Parser.toFieldTypeSignature(field, appView); + fieldTypeSignature = + GenericSignature.parseFieldTypeSignature( + field.field.qualifiedName(), + getGenericSignature(field, appView), + Origin.unknown(), + appView.dexItemFactory(), + appView.options().reporter); assertNotNull(fieldTypeSignature); // field type: A$Y$YY @@ -152,7 +164,13 @@ method = newYY.getMethod(); assertNotNull(method); - methodTypeSignature = Parser.toMethodTypeSignature(method, appView); + methodTypeSignature = + GenericSignature.parseMethodSignature( + method.qualifiedName(), + getGenericSignature(method, appView), + Origin.unknown(), + appView.dexItemFactory(), + appView.options().reporter); assertNotNull(methodTypeSignature); assertEquals(1, methodTypeSignature.formalTypeParameters.size()); @@ -194,7 +212,13 @@ method = convertToYY.getMethod(); assertNotNull(method); - methodTypeSignature = GenericSignature.Parser.toMethodTypeSignature(method, appView); + methodTypeSignature = + GenericSignature.parseMethodSignature( + method.qualifiedName(), + getGenericSignature(method, appView), + Origin.unknown(), + appView.dexItemFactory(), + appView.options().reporter); assertNotNull(methodTypeSignature); // return type: Function<A$Y$ZZ<TT>, A$Y$YY> @@ -231,7 +255,13 @@ assertNotNull(method); // return type: void - methodTypeSignature = Parser.toMethodTypeSignature(method, appView); + methodTypeSignature = + GenericSignature.parseMethodSignature( + method.qualifiedName(), + getGenericSignature(method, appView), + Origin.unknown(), + appView.dexItemFactory(), + appView.options().reporter); assertNotNull(methodTypeSignature); returnType = methodTypeSignature.returnType(); assertTrue(returnType.isVoidDescriptor()); @@ -304,7 +334,12 @@ AppView<AppInfoWithLiveness> appView, Consumer<FieldTypeSignature> fieldTypeConsumer) { MethodTypeSignature methodTypeSignature = - Parser.toMethodTypeSignature(methodSubject.getMethod(), appView); + GenericSignature.parseMethodSignature( + methodSubject.getOriginalName(), + getGenericSignature(methodSubject.getMethod(), appView), + Origin.unknown(), + appView.dexItemFactory(), + appView.options().reporter); TypeSignature typeSignature = methodTypeSignature.returnType.typeSignature; FieldTypeSignature fieldTypeSignature = typeSignature.asFieldTypeSignature(); assertTrue(fieldTypeSignature.isClassTypeSignature()); @@ -334,6 +369,22 @@ assertTrue(typeArgument.isClassTypeSignature()); check_A_Y_ZZ(a, y, zz, typeArgument.asClassTypeSignature()); } + + private static String getGenericSignature( + DexDefinition definition, AppView<AppInfoWithLiveness> appView) { + DexAnnotationSet annotations = definition.annotations(); + if (annotations.annotations.length == 0) { + return null; + } + for (int i = 0; i < annotations.annotations.length; i++) { + DexAnnotation annotation = annotations.annotations[i]; + if (!DexAnnotation.isSignatureAnnotation(annotation, appView.dexItemFactory())) { + continue; + } + return DexAnnotation.getSignature(annotation); + } + return null; + } } //
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java new file mode 100644 index 0000000..cd057d2 --- /dev/null +++ b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
@@ -0,0 +1,177 @@ +// 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.graph.genericsignature; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.graph.GenericSignature.ClassSignature.NO_CLASS_SIGNATURE; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessages; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.GenericSignature; +import com.android.tools.r8.graph.GenericSignature.ClassSignature; +import com.android.tools.r8.graph.GenericSignaturePrinter; +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.Reporter; +import com.google.common.base.Predicates; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ClassSignatureTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public ClassSignatureTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testSuperClass() { + testParsingAndPrintingEqual("Lfoo/bar/baz;"); + } + + @Test + public void testSuperClassWithInnerClasses() { + testParsingAndPrintingEqual("Lfoo/bar/baz.Foo.Bar;"); + } + + @Test + public void testSuperClassWithInterfaces1() { + testParsingAndPrintingEqual("Lfoo/bar/baz;Lfoo/bar/qux;"); + } + + @Test + public void testSuperClassWithInterfaces2() { + testParsingAndPrintingEqual("Lfoo/bar/baz;Lfoo/bar/qux;Lfoo/bar/quux;"); + } + + @Test + public void testSuperClassWithInterfacesInnerClassesSeparatedByDollar() { + testParsingAndPrintingEqual("Lfoo/bar/baz$Foo$Bar;Lfoo/bar/qux;Lfoo/bar/quux$Foo$Bar;"); + } + + @Test + public void testSuperClassWithInterfacesInnerClassesSeparatedByPeriod() { + testParsingAndPrintingEqual("Lfoo/bar/baz.Foo.Bar;Lfoo/bar/qux;Lfoo/bar/quux.Foo.Bar;"); + } + + @Test + public void testInnerClassesWithSeparatorInName1() { + testParsingAndPrintingEqual("Lfoo/bar/baz<*>.Foo$$$<*>.Bar;"); + } + + @Test + public void testInnerClassesWithSeparatorInName2() { + testParsingAndPrintingEqual("Lfoo/bar/baz<*>.Foo$Bar$$<*>.Qux;"); + } + + @Test + public void testSuperClassWithInnerClassesGenericArguments2() { + testParsingAndPrintingEqual("Lfoo/bar/baz<LFoo;>.Foo<LFoo;>.Bar<LFoo;>;"); + } + + @Test + public void testSuperClassWithInterfacesInnerClassesGenericArguments2() { + testParsingAndPrintingEqual( + "Lfoo/bar/baz<LFoo;>.Foo<LFoo;>.Bar<LFoo;>;Lfoo/bar/qux<LFoo;>;Lfoo/bar/quux<LFoo;>.Foo<LFoo;>.Bar<LFoo;>;"); + } + + @Test + public void testSuperClassWithInterfacesInnerClassesGenericArguments3() { + testParsingAndPrintingEqual( + "Lfoo/bar/baz<LFoo;[[I[[[LBar<LFoo;>;>.Foo<LFoo;>.Bar<LFoo;>;Lfoo/bar/qux<LFoo;>;Lfoo/bar/quux<LFoo;>.Foo<LFoo;>.Bar<LFoo;>;"); + } + + @Test + public void testWildCards() { + testParsingAndPrintingEqual("Lfoo/Bar<*>;"); + } + + @Test + public void testWildCardsPositiveNegative() { + testParsingAndPrintingEqual("Lfoo/Bar<*+[I-LFoo<+LBar;>;>;"); + } + + @Test + public void testSuperClassError() { + TestDiagnosticMessages testDiagnosticMessages = testParsingAndPrintingError("Lfoo/bar/baz"); + testDiagnosticMessages.assertAllWarningsMatch( + diagnosticMessage(containsString("Invalid signature 'Lfoo/bar/baz'"))); + } + + @Test + public void testReferenceToTypeVariable() { + testParsingAndPrintingEqual("Lfoo/bar/baz<TT;>;"); + } + + @Test + public void testFormalTypeParameters() { + testParsingAndPrintingEqual("<T:Ljava/lang/Object;>Lfoo/bar/baz<TT;>;"); + } + + @Test + public void testFormalTypeWithEmpty() { + testParsingAndPrintingEqual("<T:>Lfoo/bar/baz<TT;>;"); + } + + @Test + public void testFormalTypeParametersWithInterfaces() { + testParsingAndPrintingEqual("<T:Ljava/lang/Object;:LI;>Lfoo/bar/baz<TT;>;"); + } + + @Test + public void testFormalTypeParametersArguments() { + testParsingAndPrintingEqual( + "<T:Ljava/lang/Object;:LI;R:LFoo<TT;[Lfoo/bar<TT;>.Baz<TT;[I>;>;>Lfoo/bar/baz<TT;>;"); + } + + @Test + public void testFormalTypeParameters2() {} + + @Test + public void testFormalTypeParametersEmptyError() { + // TODO(b/169716723): This should throw an error + assertThrows(AssertionError.class, () -> testParsingAndPrintingError("<>Lfoo/bar/baz<TT;>;")); + } + + private void testParsingAndPrintingEqual(String signature) { + ClassSignature parsed = + GenericSignature.parseClassSignature( + "A", signature, Origin.unknown(), new DexItemFactory(), new Reporter()); + GenericSignaturePrinter genericSignaturePrinter = + new GenericSignaturePrinter(NamingLens.getIdentityLens(), Predicates.alwaysFalse()); + genericSignaturePrinter.visitClassSignature(parsed); + String outSignature = genericSignaturePrinter.toString(); + assertEquals(signature, outSignature); + } + + private TestDiagnosticMessages testParsingAndPrintingError(String signature) { + TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl(); + ClassSignature parsed = + GenericSignature.parseClassSignature( + "A", + signature, + Origin.unknown(), + new DexItemFactory(), + new Reporter(testDiagnosticMessages)); + assertEquals(NO_CLASS_SIGNATURE, parsed); + return testDiagnosticMessages; + } +}