• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import com.android.annotationvisitor.Status;
9 
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.collect.ImmutableSet;
12 
13 import org.apache.bcel.Const;
14 import org.apache.bcel.classfile.AnnotationEntry;
15 import org.apache.bcel.classfile.ElementValue;
16 import org.apache.bcel.classfile.ElementValuePair;
17 import org.apache.bcel.classfile.FieldOrMethod;
18 import org.apache.bcel.classfile.Method;
19 import org.apache.bcel.classfile.SimpleElementValue;
20 
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.function.Predicate;
24 
25 /**
26  * Processes {@code UnsupportedAppUsage} annotations to generate greylist
27  * entries.
28  *
29  * Any annotations with a {@link #EXPECTED_SIGNATURE_PROPERTY} property will have their
30  * generated signature verified against this, and an error will be reported if
31  * it does not match. Exclusions are made for bridge methods.
32  *
33  * Any {@link #MAX_TARGET_SDK_PROPERTY} properties will be validated against the given
34  * set of valid values, then passed through to the greylist consumer.
35  */
36 public class UnsupportedAppUsageAnnotationHandler extends AnnotationHandler {
37 
38     // properties of greylist annotations:
39     private static final String EXPECTED_SIGNATURE_PROPERTY = "expectedSignature";
40     private static final String MAX_TARGET_SDK_PROPERTY = "maxTargetSdk";
41     private static final String IMPLICIT_MEMBER_PROPERTY = "implicitMember";
42     private static final String PUBLIC_ALTERNATIVES_PROPERTY = "publicAlternatives";
43     private static final String TRACKING_BUG_PROPERTY = "trackingBug";
44     // APIs with this tracking bug are exempted from public alternatives requirements
45     private static final Long RESTRICT_UNUSED_APIS_BUG = 170729553L;
46     private static final Integer SDK_VERSION_R = 30;
47 
48     private final Status mStatus;
49     private final Predicate<ClassMember> mClassMemberFilter;
50     private final Map<Integer, String> mSdkVersionToFlagMap;
51     private final AnnotationConsumer mAnnotationConsumer;
52 
53     private ApiResolver mApiResolver;
54 
55     /**
56      * Represents a member of a class file (a field or method).
57      */
58     @VisibleForTesting
59     public static class ClassMember {
60 
61         /**
62          * Signature of this class member.
63          */
64         public final String signature;
65 
66         /**
67          * Indicates if this is a synthetic bridge method.
68          */
69         public final boolean isBridgeMethod;
70 
ClassMember(String signature, boolean isBridgeMethod)71         public ClassMember(String signature, boolean isBridgeMethod) {
72             this.signature = signature;
73             this.isBridgeMethod = isBridgeMethod;
74         }
75     }
76 
UnsupportedAppUsageAnnotationHandler(Status status, AnnotationConsumer annotationConsumer, Set<String> publicApis, Map<Integer, String> sdkVersionToFlagMap)77     public UnsupportedAppUsageAnnotationHandler(Status status,
78             AnnotationConsumer annotationConsumer, Set<String> publicApis,
79             Map<Integer, String> sdkVersionToFlagMap) {
80         this(status, annotationConsumer,
81                 member -> !(member.isBridgeMethod && publicApis.contains(member.signature)),
82                 sdkVersionToFlagMap);
83         mApiResolver = new ApiResolver(publicApis);
84     }
85 
86     @VisibleForTesting
UnsupportedAppUsageAnnotationHandler(Status status, AnnotationConsumer annotationConsumer, Predicate<ClassMember> memberFilter, Map<Integer, String> sdkVersionToFlagMap)87     public UnsupportedAppUsageAnnotationHandler(Status status,
88             AnnotationConsumer annotationConsumer, Predicate<ClassMember> memberFilter,
89             Map<Integer, String> sdkVersionToFlagMap) {
90         mStatus = status;
91         mAnnotationConsumer = annotationConsumer;
92         mClassMemberFilter = memberFilter;
93         mSdkVersionToFlagMap = sdkVersionToFlagMap;
94         mApiResolver = new ApiResolver();
95     }
96 
97     @Override
handleAnnotation(AnnotationEntry annotation, AnnotationContext context)98     public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
99         boolean isBridgeMethod = false;
100         if (context instanceof AnnotatedMemberContext) {
101             AnnotatedMemberContext memberContext = (AnnotatedMemberContext) context;
102             FieldOrMethod member = memberContext.member;
103             isBridgeMethod = (member instanceof Method) &&
104                     (member.getAccessFlags() & Const.ACC_BRIDGE) != 0;
105             if (isBridgeMethod) {
106                 mStatus.debug("Member is a bridge method");
107             }
108         }
109 
110         String signature = context.getMemberDescriptor();
111         Integer maxTargetSdk = null;
112         String implicitMemberSignature = null;
113         String publicAlternativesString = null;
114         Long trackingBug = null;
115 
116         for (ElementValuePair property : annotation.getElementValuePairs()) {
117             switch (property.getNameString()) {
118                 case EXPECTED_SIGNATURE_PROPERTY:
119                     String expected = property.getValue().stringifyValue();
120                     // Don't enforce for bridge methods; they're generated so won't match.
121                     if (!isBridgeMethod && !signature.equals(expected)) {
122                         context.reportError("Expected signature does not match generated:\n"
123                                 + "Expected:  %s\n"
124                                 + "Generated: %s", expected, signature);
125                         return;
126                     }
127                     break;
128                 case MAX_TARGET_SDK_PROPERTY:
129                     if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_INT) {
130                         context.reportError("Expected property %s to be of type int; got %d",
131                                 property.getNameString(),
132                                 property.getValue().getElementValueType());
133                         return;
134                     }
135 
136                     maxTargetSdk = ((SimpleElementValue) property.getValue()).getValueInt();
137                     break;
138                 case IMPLICIT_MEMBER_PROPERTY:
139                     implicitMemberSignature = property.getValue().stringifyValue();
140                     if (context instanceof AnnotatedClassContext) {
141                         signature = String.format("L%s;->%s",
142                                 context.getClassDescriptor(), implicitMemberSignature);
143                     } else {
144                         context.reportError(
145                                 "Expected annotation with an %s property to be on a class but is "
146                                         + "on %s",
147                                 IMPLICIT_MEMBER_PROPERTY,
148                                 signature);
149                         return;
150                     }
151                     break;
152                 case PUBLIC_ALTERNATIVES_PROPERTY:
153                     publicAlternativesString = property.getValue().stringifyValue();
154                     break;
155                 case TRACKING_BUG_PROPERTY:
156                     if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_LONG) {
157                         context.reportError("Expected property %s to be of type long; got %d",
158                                 property.getNameString(),
159                                 property.getValue().getElementValueType());
160                         return;
161                     }
162                     trackingBug = ((SimpleElementValue) property.getValue()).getValueLong();
163                     break;
164             }
165         }
166 
167         // Is this API exempted from the public alternatives enforcement?
168         boolean isSpecialTrackingBug = RESTRICT_UNUSED_APIS_BUG.equals(trackingBug) &&
169                 SDK_VERSION_R.equals(maxTargetSdk);
170 
171         if (context instanceof AnnotatedClassContext && implicitMemberSignature == null) {
172             context.reportError(
173                     "Missing property %s on annotation on class %s",
174                     IMPLICIT_MEMBER_PROPERTY,
175                     signature);
176             return;
177         }
178 
179         // Verify that maxTargetSdk is valid.
180         if (!mSdkVersionToFlagMap.containsKey(maxTargetSdk)) {
181             context.reportError("Invalid value for %s: got %d, expected one of [%s]",
182                     MAX_TARGET_SDK_PROPERTY,
183                     maxTargetSdk,
184                     mSdkVersionToFlagMap.keySet());
185             return;
186         }
187 
188         try {
189             mApiResolver.resolvePublicAlternatives(publicAlternativesString, signature,
190                     maxTargetSdk);
191         } catch (MultipleAlternativesFoundWarning e) {
192             context.reportWarning(e.toString());
193         } catch (JavadocLinkSyntaxError | AlternativeNotFoundError e) {
194             context.reportError(e.toString());
195         } catch (RequiredAlternativeNotSpecifiedError e) {
196             if (!isSpecialTrackingBug) {
197                 context.reportError(
198                         "Signature %s moved to %s without specifying public alternatives; "
199                         + "Refer to go/unsupportedappusage-public-alternatives for details.",
200                         signature, mSdkVersionToFlagMap.get(maxTargetSdk));
201             }
202         }
203 
204         // Consume this annotation if it matches the predicate.
205         if (mClassMemberFilter.test(new ClassMember(signature, isBridgeMethod))) {
206             mAnnotationConsumer.consume(signature, stringifyAnnotationProperties(annotation),
207                     ImmutableSet.of(mSdkVersionToFlagMap.get(maxTargetSdk)));
208         }
209     }
210 }
211