blob: dda63d7c564a254b0294252874e813850480f110 [file] [log] [blame]
package com.android.class2nonsdklist;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
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.Map;
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} 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_PROPERTY} properties will be validated against the given
* set of valid values, then passed through to the greylist consumer.
*/
public class UnsupportedAppUsageAnnotationHandler extends AnnotationHandler {
// properties of greylist annotations:
private static final String EXPECTED_SIGNATURE_PROPERTY = "expectedSignature";
private static final String MAX_TARGET_SDK_PROPERTY = "maxTargetSdk";
private static final String IMPLICIT_MEMBER_PROPERTY = "implicitMember";
private static final String PUBLIC_ALTERNATIVES_PROPERTY = "publicAlternatives";
private static final String TRACKING_BUG_PROPERTY = "trackingBug";
// APIs with this tracking bug are exempted from public alternatives requirements
private static final Long RESTRICT_UNUSED_APIS_BUG = 170729553L;
private static final Integer SDK_VERSION_R = 30;
private final Status mStatus;
private final Predicate<ClassMember> mClassMemberFilter;
private final Map<Integer, String> mSdkVersionToFlagMap;
private final AnnotationConsumer mAnnotationConsumer;
private ApiResolver mApiResolver;
/**
* Represents a member of a class file (a field or method).
*/
@VisibleForTesting
public static class ClassMember {
/**
* Signature of this class member.
*/
public final String signature;
/**
* Indicates if this is a synthetic bridge method.
*/
public final boolean isBridgeMethod;
public ClassMember(String signature, boolean isBridgeMethod) {
this.signature = signature;
this.isBridgeMethod = isBridgeMethod;
}
}
public UnsupportedAppUsageAnnotationHandler(Status status,
AnnotationConsumer annotationConsumer, Set<String> publicApis,
Map<Integer, String> sdkVersionToFlagMap) {
this(status, annotationConsumer,
member -> !(member.isBridgeMethod && publicApis.contains(member.signature)),
sdkVersionToFlagMap);
mApiResolver = new ApiResolver(publicApis);
}
@VisibleForTesting
public UnsupportedAppUsageAnnotationHandler(Status status,
AnnotationConsumer annotationConsumer, Predicate<ClassMember> memberFilter,
Map<Integer, String> sdkVersionToFlagMap) {
mStatus = status;
mAnnotationConsumer = annotationConsumer;
mClassMemberFilter = memberFilter;
mSdkVersionToFlagMap = sdkVersionToFlagMap;
mApiResolver = new ApiResolver();
}
@Override
public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
boolean isBridgeMethod = false;
if (context instanceof AnnotatedMemberContext) {
AnnotatedMemberContext memberContext = (AnnotatedMemberContext) context;
FieldOrMethod member = memberContext.member;
isBridgeMethod = (member instanceof Method) &&
(member.getAccessFlags() & Const.ACC_BRIDGE) != 0;
if (isBridgeMethod) {
mStatus.debug("Member is a bridge method");
}
}
String signature = context.getMemberDescriptor();
Integer maxTargetSdk = null;
String implicitMemberSignature = null;
String publicAlternativesString = null;
Long trackingBug = null;
for (ElementValuePair property : annotation.getElementValuePairs()) {
switch (property.getNameString()) {
case EXPECTED_SIGNATURE_PROPERTY:
String expected = property.getValue().stringifyValue();
// Don't enforce for bridge methods; they're generated so won't match.
if (!isBridgeMethod && !signature.equals(expected)) {
context.reportError("Expected signature does not match generated:\n"
+ "Expected: %s\n"
+ "Generated: %s", expected, signature);
return;
}
break;
case MAX_TARGET_SDK_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());
return;
}
maxTargetSdk = ((SimpleElementValue) property.getValue()).getValueInt();
break;
case IMPLICIT_MEMBER_PROPERTY:
implicitMemberSignature = property.getValue().stringifyValue();
if (context instanceof AnnotatedClassContext) {
signature = String.format("L%s;->%s",
context.getClassDescriptor(), implicitMemberSignature);
} else {
context.reportError(
"Expected annotation with an %s property to be on a class but is "
+ "on %s",
IMPLICIT_MEMBER_PROPERTY,
signature);
return;
}
break;
case PUBLIC_ALTERNATIVES_PROPERTY:
publicAlternativesString = property.getValue().stringifyValue();
break;
case TRACKING_BUG_PROPERTY:
if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_LONG) {
context.reportError("Expected property %s to be of type long; got %d",
property.getNameString(),
property.getValue().getElementValueType());
return;
}
trackingBug = ((SimpleElementValue) property.getValue()).getValueLong();
break;
}
}
// Is this API exempted from the public alternatives enforcement?
boolean isSpecialTrackingBug = RESTRICT_UNUSED_APIS_BUG.equals(trackingBug) &&
SDK_VERSION_R.equals(maxTargetSdk);
if (context instanceof AnnotatedClassContext && implicitMemberSignature == null) {
context.reportError(
"Missing property %s on annotation on class %s",
IMPLICIT_MEMBER_PROPERTY,
signature);
return;
}
// Verify that maxTargetSdk is valid.
if (!mSdkVersionToFlagMap.containsKey(maxTargetSdk)) {
context.reportError("Invalid value for %s: got %d, expected one of [%s]",
MAX_TARGET_SDK_PROPERTY,
maxTargetSdk,
mSdkVersionToFlagMap.keySet());
return;
}
try {
mApiResolver.resolvePublicAlternatives(publicAlternativesString, signature,
maxTargetSdk);
} catch (MultipleAlternativesFoundWarning e) {
context.reportWarning(e.toString());
} catch (JavadocLinkSyntaxError | AlternativeNotFoundError e) {
context.reportError(e.toString());
} catch (RequiredAlternativeNotSpecifiedError e) {
if (!isSpecialTrackingBug) {
context.reportError(
"Signature %s moved to %s without specifying public alternatives; "
+ "Refer to go/unsupportedappusage-public-alternatives for details.",
signature, mSdkVersionToFlagMap.get(maxTargetSdk));
}
}
// Consume this annotation if it matches the predicate.
if (mClassMemberFilter.test(new ClassMember(signature, isBridgeMethod))) {
mAnnotationConsumer.consume(signature, stringifyAnnotationProperties(annotation),
ImmutableSet.of(mSdkVersionToFlagMap.get(maxTargetSdk)));
}
}
}