blob: 66857525aa0c92d9dd60f74d5b83c7fee00c4d62 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.class2greylist;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.DescendingVisitor;
import org.apache.bcel.classfile.ElementValuePair;
import org.apache.bcel.classfile.EmptyVisitor;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.FieldOrMethod;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import java.util.Locale;
/**
* Visits a JavaClass instance and pulls out all members annotated with a
* specific annotation. The signatures of such members are passed to {@link
* Status#greylistEntry(String)}. Any errors result in a call to {@link
* Status#error(String)}.
*
* If the annotation has a property "expectedSignature" the generated signature
* will be verified against the one specified there. If it differs, an error
* will be generated.
*/
public class AnnotationVisitor extends EmptyVisitor {
private static final String EXPECTED_SIGNATURE = "expectedSignature";
private final JavaClass mClass;
private final String mAnnotationType;
private final Status mStatus;
private final DescendingVisitor mDescendingVisitor;
public AnnotationVisitor(JavaClass clazz, String annotation, Status d) {
mClass = clazz;
mAnnotationType = annotation;
mStatus = d;
mDescendingVisitor = new DescendingVisitor(clazz, this);
}
public void visit() {
mStatus.debug("Visit class %s", mClass.getClassName());
mDescendingVisitor.visit();
}
private static String getClassDescriptor(JavaClass clazz) {
// JavaClass.getName() returns the Java-style name (with . not /), so we must fetch
// the original class name from the constant pool.
return clazz.getConstantPool().getConstantString(
clazz.getClassNameIndex(), Const.CONSTANT_Class);
}
@Override
public void visitMethod(Method method) {
visitMember(method, "L%s;->%s%s");
}
@Override
public void visitField(Field field) {
visitMember(field, "L%s;->%s:%s");
}
private void visitMember(FieldOrMethod member, String signatureFormatString) {
JavaClass definingClass = (JavaClass) mDescendingVisitor.predecessor();
mStatus.debug("Visit member %s : %s", member.getName(), member.getSignature());
for (AnnotationEntry a : member.getAnnotationEntries()) {
if (mAnnotationType.equals(a.getAnnotationType())) {
mStatus.debug("Method has annotation %s", mAnnotationType);
String signature = String.format(Locale.US, signatureFormatString,
getClassDescriptor(definingClass), member.getName(), member.getSignature());
for (ElementValuePair property : a.getElementValuePairs()) {
switch (property.getNameString()) {
case EXPECTED_SIGNATURE:
String expected = property.getValue().stringifyValue();
if (!signature.equals(expected)) {
error(definingClass, member,
"Expected signature does not match generated:\n"
+ "Expected: %s\n"
+ "Generated: %s", expected, signature);
}
break;
}
}
mStatus.greylistEntry(signature);
}
}
}
private void error(JavaClass clazz, FieldOrMethod member, String message, Object... args) {
StringBuilder error = new StringBuilder();
error.append(clazz.getSourceFileName())
.append(": ")
.append(clazz.getClassName())
.append(".")
.append(member.getName())
.append(": ")
.append(String.format(Locale.US, message, args));
mStatus.error(error.toString());
}
}