1 package com.android.class2nonsdklist;
2 
3 import com.android.annotationvisitor.AnnotatedClassContext;
4 import com.android.annotationvisitor.AnnotatedMemberContext;
5 import com.android.annotationvisitor.AnnotationConsumer;
6 import com.android.annotationvisitor.AnnotationContext;
7 import com.android.annotationvisitor.AnnotationHandler;
8 
9 import com.google.common.base.Preconditions;
10 import com.google.common.collect.ImmutableSet;
11 
12 import org.apache.bcel.classfile.AnnotationEntry;
13 import org.apache.bcel.classfile.ElementValuePair;
14 import org.apache.bcel.classfile.Method;
15 
16 import java.util.Locale;
17 import java.util.Set;
18 
19 /**
20  * Handles {@code CovariantReturnType} annotations, generating whitelist
21  * entries from them.
22  *
23  * <p>A whitelist entry is generated with the same descriptor as the original
24  * method, but with the return type replaced with than specified by the
25  * {@link #RETURN_TYPE} property.
26  *
27  * <p>Methods are also validated against the public API list, to assert that
28  * the annotated method is already a public API.
29  */
30 public class CovariantReturnTypeHandler extends AnnotationHandler {
31 
32     private static final String SHORT_NAME = "CovariantReturnType";
33     public static final String ANNOTATION_NAME = "Ldalvik/annotation/codegen/CovariantReturnType;";
34     public static final String REPEATED_ANNOTATION_NAME =
35         "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;";
36 
37     private static final String RETURN_TYPE = "returnType";
38 
39     private final AnnotationConsumer mAnnotationConsumer;
40     private final Set<String> mPublicApis;
41     private final String mHiddenapiFlag;
42 
CovariantReturnTypeHandler(AnnotationConsumer consumer, Set<String> publicApis, String hiddenapiFlag)43     public CovariantReturnTypeHandler(AnnotationConsumer consumer, Set<String> publicApis,
44             String hiddenapiFlag) {
45         mAnnotationConsumer = consumer;
46         mPublicApis = publicApis;
47         mHiddenapiFlag = hiddenapiFlag;
48     }
49 
50     @Override
handleAnnotation(AnnotationEntry annotation, AnnotationContext context)51     public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
52         if (context instanceof AnnotatedClassContext) {
53             return;
54         }
55         handleAnnotation(annotation, (AnnotatedMemberContext) context);
56     }
57 
handleAnnotation(AnnotationEntry annotation, AnnotatedMemberContext context)58     private void handleAnnotation(AnnotationEntry annotation, AnnotatedMemberContext context) {
59         // Verify that the annotation has been applied to what we expect, and
60         // has the right form. Note, this should not strictly be necessary, as
61         // the annotation has a target of just 'method' and the property
62         // returnType does not have a default value, but checking makes the code
63         // less brittle to future changes.
64         if (!(context.member instanceof Method)) {
65             context.reportError("Cannot specify %s on a field", RETURN_TYPE);
66             return;
67         }
68         String returnType = findReturnType(annotation);
69         if (returnType == null) {
70             context.reportError("No %s set on @%s", RETURN_TYPE, SHORT_NAME);
71             return;
72         }
73         if (!mPublicApis.contains(context.getMemberDescriptor())) {
74             context.reportError("Found @%s on non-SDK method", SHORT_NAME);
75             return;
76         }
77 
78         // Generate the signature of overload that we expect the annotation will
79         // cause the platform dexer to create.
80         String typeSignature = context.member.getSignature();
81         int closingBrace = typeSignature.indexOf(')');
82         Preconditions.checkState(closingBrace != -1,
83                 "No ) found in method type signature %s", typeSignature);
84         typeSignature = new StringBuilder()
85                 .append(typeSignature.substring(0, closingBrace + 1))
86                 .append(returnType)
87                 .toString();
88         String signature = String.format(Locale.US, context.signatureFormatString,
89                 context.getClassDescriptor(), context.member.getName(), typeSignature);
90 
91         if (mPublicApis.contains(signature)) {
92             context.reportError("Signature %s generated from @%s already exists as a public API",
93                     signature, SHORT_NAME);
94             return;
95         }
96 
97         mAnnotationConsumer.consume(signature, stringifyAnnotationProperties(annotation),
98                 ImmutableSet.of(mHiddenapiFlag));
99     }
100 
findReturnType(AnnotationEntry a)101     private String findReturnType(AnnotationEntry a) {
102         for (ElementValuePair property : a.getElementValuePairs()) {
103             if (property.getNameString().equals(RETURN_TYPE)) {
104                 return property.getValue().stringifyValue();
105             }
106         }
107         // not found
108         return null;
109     }
110 }
111