| package com.android.class2greylist; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Joiner; |
| |
| import org.apache.bcel.Const; |
| import org.apache.bcel.classfile.AnnotationEntry; |
| import org.apache.bcel.classfile.ElementValue; |
| import org.apache.bcel.classfile.ElementValuePair; |
| import org.apache.bcel.classfile.FieldOrMethod; |
| import org.apache.bcel.classfile.Method; |
| import org.apache.bcel.classfile.SimpleElementValue; |
| |
| import java.util.Set; |
| import java.util.function.Predicate; |
| |
| /** |
| * Processes {@code UnsupportedAppUsage} annotations to generate greylist |
| * entries. |
| * |
| * Any annotations with a {@link #EXPECTED_SIGNATURE} property will have their |
| * generated signature verified against this, and an error will be reported if |
| * it does not match. Exclusions are made for bridge methods. |
| * |
| * Any {@link #MAX_TARGET_SDK} properties will be validated against the given |
| * set of valid values, then passed through to the greylist consumer. |
| */ |
| public class GreylistAnnotationHandler implements AnnotationHandler { |
| |
| public static final String ANNOTATION_NAME = "Landroid/annotation/UnsupportedAppUsage;"; |
| |
| // properties of greylist annotations: |
| private static final String EXPECTED_SIGNATURE = "expectedSignature"; |
| private static final String MAX_TARGET_SDK = "maxTargetSdk"; |
| |
| private final Status mStatus; |
| private final Predicate<GreylistMember> mGreylistFilter; |
| private final GreylistConsumer mGreylistConsumer; |
| private final Set<Integer> mValidMaxTargetSdkValues; |
| |
| /** |
| * Represents a member of a class file (a field or method). |
| */ |
| @VisibleForTesting |
| public static class GreylistMember { |
| |
| /** |
| * Signature of this member. |
| */ |
| public final String signature; |
| /** |
| * Indicates if this is a synthetic bridge method. |
| */ |
| public final boolean bridge; |
| /** |
| * Max target SDK of property this member, if it is set, else null. |
| * |
| * Note: even though the annotation itself specified a default value, |
| * that default value is not encoded into instances of the annotation |
| * in class files. So when no value is specified in source, it will |
| * result in null appearing in here. |
| */ |
| public final Integer maxTargetSdk; |
| |
| public GreylistMember(String signature, boolean bridge, Integer maxTargetSdk) { |
| this.signature = signature; |
| this.bridge = bridge; |
| this.maxTargetSdk = maxTargetSdk; |
| } |
| } |
| |
| public GreylistAnnotationHandler( |
| Status status, |
| GreylistConsumer greylistConsumer, |
| Set<String> publicApis, |
| Set<Integer> validMaxTargetSdkValues) { |
| this(status, greylistConsumer, |
| member -> !(member.bridge && publicApis.contains(member.signature)), |
| validMaxTargetSdkValues); |
| } |
| |
| @VisibleForTesting |
| public GreylistAnnotationHandler( |
| Status status, |
| GreylistConsumer greylistConsumer, |
| Predicate<GreylistMember> greylistFilter, |
| Set<Integer> validMaxTargetSdkValues) { |
| mStatus = status; |
| mGreylistConsumer = greylistConsumer; |
| mGreylistFilter = greylistFilter; |
| mValidMaxTargetSdkValues = validMaxTargetSdkValues; |
| } |
| |
| @Override |
| public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) { |
| FieldOrMethod member = context.member; |
| boolean bridge = (member instanceof Method) |
| && (member.getAccessFlags() & Const.ACC_BRIDGE) != 0; |
| if (bridge) { |
| mStatus.debug("Member is a bridge"); |
| } |
| String signature = context.getMemberDescriptor(); |
| Integer maxTargetSdk = null; |
| for (ElementValuePair property : annotation.getElementValuePairs()) { |
| switch (property.getNameString()) { |
| case EXPECTED_SIGNATURE: |
| verifyExpectedSignature(context, property, signature, bridge); |
| break; |
| case MAX_TARGET_SDK: |
| maxTargetSdk = verifyAndGetMaxTargetSdk(context, property); |
| break; |
| } |
| } |
| if (mGreylistFilter.test(new GreylistMember(signature, bridge, maxTargetSdk))) { |
| mGreylistConsumer.greylistEntry(signature, maxTargetSdk); |
| } |
| } |
| |
| private void verifyExpectedSignature(AnnotationContext context, ElementValuePair property, |
| String signature, boolean isBridge) { |
| String expected = property.getValue().stringifyValue(); |
| // Don't enforce for bridge methods; they're generated so won't match. |
| if (!isBridge && !signature.equals(expected)) { |
| context.reportError("Expected signature does not match generated:\n" |
| + "Expected: %s\n" |
| + "Generated: %s", expected, signature); |
| } |
| } |
| |
| private Integer verifyAndGetMaxTargetSdk(AnnotationContext context, ElementValuePair property) { |
| if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_INT) { |
| context.reportError("Expected property %s to be of type int; got %d", |
| property.getNameString(), property.getValue().getElementValueType()); |
| } |
| int value = ((SimpleElementValue) property.getValue()).getValueInt(); |
| if (!mValidMaxTargetSdkValues.contains(value)) { |
| context.reportError("Invalid value for %s: got %d, expected one of [%s]", |
| property.getNameString(), |
| value, |
| Joiner.on(",").join(mValidMaxTargetSdkValues)); |
| return null; |
| } |
| return value; |
| } |
| |
| } |