blob: eb2e42d6f15fe9763746eb1c57cbd16833fa72a7 [file] [log] [blame]
package com.android.class2greylist;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.ElementValuePair;
import org.apache.bcel.classfile.Method;
import java.util.Locale;
import java.util.Set;
/**
* Handles {@code CovariantReturnType} annotations, generating whitelist
* entries from them.
*
* <p>A whitelist entry is generated with the same descriptor as the original
* method, but with the return type replaced with than specified by the
* {@link #RETURN_TYPE} property.
*
* <p>Methods are also validated against the public API list, to assert that
* the annotated method is already a public API.
*/
public class CovariantReturnTypeHandler extends AnnotationHandler {
private static final String SHORT_NAME = "CovariantReturnType";
public static final String ANNOTATION_NAME = "Ldalvik/annotation/codegen/CovariantReturnType;";
public static final String REPEATED_ANNOTATION_NAME =
"Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;";
private static final String RETURN_TYPE = "returnType";
private final AnnotationConsumer mAnnotationConsumer;
private final Set<String> mPublicApis;
private final String mHiddenapiFlag;
public CovariantReturnTypeHandler(AnnotationConsumer consumer, Set<String> publicApis,
String hiddenapiFlag) {
mAnnotationConsumer = consumer;
mPublicApis = publicApis;
mHiddenapiFlag = hiddenapiFlag;
}
@Override
public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
if (context instanceof AnnotatedClassContext) {
return;
}
handleAnnotation(annotation, (AnnotatedMemberContext) context);
}
private void handleAnnotation(AnnotationEntry annotation, AnnotatedMemberContext context) {
// Verify that the annotation has been applied to what we expect, and
// has the right form. Note, this should not strictly be necessary, as
// the annotation has a target of just 'method' and the property
// returnType does not have a default value, but checking makes the code
// less brittle to future changes.
if (!(context.member instanceof Method)) {
context.reportError("Cannot specify %s on a field", RETURN_TYPE);
return;
}
String returnType = findReturnType(annotation);
if (returnType == null) {
context.reportError("No %s set on @%s", RETURN_TYPE, SHORT_NAME);
return;
}
if (!mPublicApis.contains(context.getMemberDescriptor())) {
context.reportError("Found @%s on non-SDK method", SHORT_NAME);
return;
}
// Generate the signature of overload that we expect the annotation will
// cause the platform dexer to create.
String typeSignature = context.member.getSignature();
int closingBrace = typeSignature.indexOf(')');
Preconditions.checkState(closingBrace != -1,
"No ) found in method type signature %s", typeSignature);
typeSignature = new StringBuilder()
.append(typeSignature.substring(0, closingBrace + 1))
.append(returnType)
.toString();
String signature = String.format(Locale.US, context.signatureFormatString,
context.getClassDescriptor(), context.member.getName(), typeSignature);
if (mPublicApis.contains(signature)) {
context.reportError("Signature %s generated from @%s already exists as a public API",
signature, SHORT_NAME);
return;
}
mAnnotationConsumer.consume(signature, stringifyAnnotationProperties(annotation),
ImmutableSet.of(mHiddenapiFlag));
}
private String findReturnType(AnnotationEntry a) {
for (ElementValuePair property : a.getElementValuePairs()) {
if (property.getNameString().equals(RETURN_TYPE)) {
return property.getValue().stringifyValue();
}
}
// not found
return null;
}
}