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