diff options
11 files changed, 484 insertions, 263 deletions
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationContext.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationContext.java new file mode 100644 index 0000000000..eb54a332eb --- /dev/null +++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationContext.java @@ -0,0 +1,66 @@ +package com.android.class2greylist; + +import org.apache.bcel.Const; +import org.apache.bcel.classfile.FieldOrMethod; +import org.apache.bcel.classfile.JavaClass; + +import java.util.Locale; + +/** + * Encapsulates context for a single annotation on a class member. + */ +public class AnnotationContext { + + public final Status status; + public final FieldOrMethod member; + public final JavaClass definingClass; + public final String signatureFormatString; + + public AnnotationContext( + Status status, + FieldOrMethod member, + JavaClass definingClass, + String signatureFormatString) { + this.status = status; + this.member = member; + this.definingClass = definingClass; + this.signatureFormatString = signatureFormatString; + } + + /** + * @return the full descriptor of enclosing class. + */ + public String getClassDescriptor() { + // JavaClass.getName() returns the Java-style name (with . not /), so we must fetch + // the original class name from the constant pool. + return definingClass.getConstantPool().getConstantString( + definingClass.getClassNameIndex(), Const.CONSTANT_Class); + } + + /** + * @return the full descriptor of this member, in the format expected in + * the greylist. + */ + public String getMemberDescriptor() { + return String.format(Locale.US, signatureFormatString, + getClassDescriptor(), member.getName(), member.getSignature()); + } + + /** + * Report an error in this context. The final error message will include + * the class and member names, and the source file name. + */ + public void reportError(String message, Object... args) { + StringBuilder error = new StringBuilder(); + error.append(definingClass.getSourceFileName()) + .append(": ") + .append(definingClass.getClassName()) + .append(".") + .append(member.getName()) + .append(": ") + .append(String.format(Locale.US, message, args)); + + status.error(error.toString()); + } + +} diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java new file mode 100644 index 0000000000..92d2ab6d79 --- /dev/null +++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java @@ -0,0 +1,11 @@ +package com.android.class2greylist; + +import org.apache.bcel.classfile.AnnotationEntry; + +/** + * Interface for an annotation handler, which handle individual annotations on + * class members. + */ +public interface AnnotationHandler { + void handleAnnotation(AnnotationEntry annotation, AnnotationContext context); +} diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java index 1838575582..b805b307a3 100644 --- a/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java +++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java @@ -16,100 +16,40 @@ 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.DescendingVisitor; -import org.apache.bcel.classfile.ElementValue; -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 org.apache.bcel.classfile.SimpleElementValue; -import java.util.Locale; -import java.util.Set; -import java.util.function.Predicate; +import java.util.Map; /** - * Visits a JavaClass instance and pulls out all members annotated with a - * specific annotation. The signatures of such members are passed to {@link - * GreylistConsumer#greylistEntry(String, Integer)}. Any errors result in a - * call to {@link Status#error(String, Object...)}. - * - * 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. + * Visits a JavaClass instance and passes any annotated members to a {@link AnnotationHandler} + * according to the map provided. */ public class AnnotationVisitor extends EmptyVisitor { - private static final String EXPECTED_SIGNATURE = "expectedSignature"; - private static final String MAX_TARGET_SDK = "maxTargetSdk"; - private final JavaClass mClass; - private final String mAnnotationType; - private final Predicate<Member> mMemberFilter; - private final Set<Integer> mValidMaxTargetSdkValues; - private final GreylistConsumer mConsumer; private final Status mStatus; private final DescendingVisitor mDescendingVisitor; + private final Map<String, AnnotationHandler> mAnnotationHandlers; /** - * Represents a member of a class file (a field or method). + * Creates a visitor for a class. + * + * @param clazz Class to visit + * @param status For reporting debug information + * @param handlers Map of {@link AnnotationHandler}. The keys should be annotation names, as + * class descriptors. */ - @VisibleForTesting - public static class Member { - - /** - * 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 Member(String signature, boolean bridge, Integer maxTargetSdk) { - this.signature = signature; - this.bridge = bridge; - this.maxTargetSdk = maxTargetSdk; - } - } - - public AnnotationVisitor(JavaClass clazz, String annotation, Set<String> publicApis, - Set<Integer> validMaxTargetSdkValues, GreylistConsumer consumer, - Status status) { - this(clazz, - annotation, - member -> !(member.bridge && publicApis.contains(member.signature)), - validMaxTargetSdkValues, - consumer, - status); - } - - @VisibleForTesting - public AnnotationVisitor(JavaClass clazz, String annotation, Predicate<Member> memberFilter, - Set<Integer> validMaxTargetSdkValues, GreylistConsumer consumer, - Status status) { + public AnnotationVisitor(JavaClass clazz, Status status, + Map<String, AnnotationHandler> handlers) { mClass = clazz; - mAnnotationType = annotation; - mMemberFilter = memberFilter; - mValidMaxTargetSdkValues = validMaxTargetSdkValues; - mConsumer = consumer; mStatus = status; + mAnnotationHandlers = handlers; mDescendingVisitor = new DescendingVisitor(clazz, this); } @@ -118,13 +58,6 @@ public class AnnotationVisitor extends EmptyVisitor { 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"); @@ -136,80 +69,15 @@ public class AnnotationVisitor extends EmptyVisitor { } private void visitMember(FieldOrMethod member, String signatureFormatString) { - JavaClass definingClass = (JavaClass) mDescendingVisitor.predecessor(); mStatus.debug("Visit member %s : %s", member.getName(), member.getSignature()); + AnnotationContext context = new AnnotationContext(mStatus, member, + (JavaClass) mDescendingVisitor.predecessor(), signatureFormatString); for (AnnotationEntry a : member.getAnnotationEntries()) { - if (mAnnotationType.equals(a.getAnnotationType())) { - mStatus.debug("Member has annotation %s", mAnnotationType); - // For fields, the same access flag means volatile, so only check for methods. - boolean bridge = (member instanceof Method) - && (member.getAccessFlags() & Const.ACC_BRIDGE) != 0; - if (bridge) { - mStatus.debug("Member is a bridge", mAnnotationType); - } - String signature = String.format(Locale.US, signatureFormatString, - getClassDescriptor(definingClass), member.getName(), member.getSignature()); - Integer maxTargetSdk = null; - for (ElementValuePair property : a.getElementValuePairs()) { - switch (property.getNameString()) { - case EXPECTED_SIGNATURE: - verifyExpectedSignature( - property, signature, definingClass, member, bridge); - break; - case MAX_TARGET_SDK: - maxTargetSdk = verifyAndGetMaxTargetSdk( - property, definingClass, member); - break; - } - } - if (mMemberFilter.test(new Member(signature, bridge, maxTargetSdk))) { - mConsumer.greylistEntry(signature, maxTargetSdk); - } + if (mAnnotationHandlers.containsKey(a.getAnnotationType())) { + mStatus.debug("Member has annotation %s for which we have a handler", + a.getAnnotationType()); + mAnnotationHandlers.get(a.getAnnotationType()).handleAnnotation(a, context); } } } - - private void verifyExpectedSignature(ElementValuePair property, String signature, - JavaClass definingClass, FieldOrMethod member, 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)) { - error(definingClass, member, - "Expected signature does not match generated:\n" - + "Expected: %s\n" - + "Generated: %s", expected, signature); - } - } - - private Integer verifyAndGetMaxTargetSdk( - ElementValuePair property, JavaClass definingClass, FieldOrMethod member) { - if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_INT) { - error(definingClass, member, "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)) { - error(definingClass, member, - "Invalid value for %s: got %d, expected one of [%s]", - property.getNameString(), - value, - Joiner.on(",").join(mValidMaxTargetSdkValues)); - return null; - } - return value; - } - - 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()); - } - } diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java index 64a0357e13..c0a3160a88 100644 --- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java +++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java @@ -17,6 +17,8 @@ package com.android.class2greylist; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.io.Files; @@ -44,12 +46,18 @@ import java.util.Set; */ public class Class2Greylist { - private static final String ANNOTATION_TYPE = "Landroid/annotation/UnsupportedAppUsage;"; + private static final String GREYLIST_ANNOTATION = "Landroid/annotation/UnsupportedAppUsage;"; + private static final Set<String> WHITELIST_ANNOTATIONS = ImmutableSet.of(); private final Status mStatus; private final String mPublicApiListFile; private final String[] mPerSdkOutputFiles; + private final String mWhitelistFile; private final String[] mJarFiles; + private final GreylistConsumer mOutput; + private final Set<Integer> mAllowedSdkVersions; + private final Set<String> mPublicApis; + public static void main(String[] args) { Options options = new Options(); @@ -68,6 +76,11 @@ public class Class2Greylist { "no integer is given, members with no maxTargetSdk are written.") .create("g")); options.addOption(OptionBuilder + .withLongOpt("write-whitelist") + .hasArgs(1) + .withDescription("Specify file to write whitelist to.") + .create('w')); + options.addOption(OptionBuilder .withLongOpt("debug") .hasArgs(0) .withDescription("Enable debug") @@ -100,9 +113,13 @@ public class Class2Greylist { } Status status = new Status(cmd.hasOption('d')); - Class2Greylist c2gl = new Class2Greylist( - status, cmd.getOptionValue('p', null), cmd.getOptionValues('g'), jarFiles); try { + Class2Greylist c2gl = new Class2Greylist( + status, + cmd.getOptionValue('p', null), + cmd.getOptionValues('g'), + cmd.getOptionValue('w', null), + jarFiles); c2gl.main(); } catch (IOException e) { status.error(e); @@ -118,52 +135,58 @@ public class Class2Greylist { @VisibleForTesting Class2Greylist(Status status, String publicApiListFile, String[] perSdkLevelOutputFiles, - String[] jarFiles) { + String whitelistOutputFile, String[] jarFiles) throws IOException { mStatus = status; mPublicApiListFile = publicApiListFile; mPerSdkOutputFiles = perSdkLevelOutputFiles; + mWhitelistFile = whitelistOutputFile; mJarFiles = jarFiles; - } - - private void main() throws IOException { - GreylistConsumer output; - Set<Integer> allowedSdkVersions; if (mPerSdkOutputFiles != null) { - Map<Integer, String> outputFiles = readGreylistMap(mPerSdkOutputFiles); - output = new FileWritingGreylistConsumer(mStatus, outputFiles); - allowedSdkVersions = outputFiles.keySet(); + Map<Integer, String> outputFiles = readGreylistMap(mStatus, mPerSdkOutputFiles); + mOutput = new FileWritingGreylistConsumer(mStatus, outputFiles, mWhitelistFile); + mAllowedSdkVersions = outputFiles.keySet(); } else { // TODO remove this once per-SDK greylist support integrated into the build. // Right now, mPerSdkOutputFiles is always null as the build never passes the // corresponding command lind flags. Once the build is updated, can remove this. - output = new SystemOutGreylistConsumer(); - allowedSdkVersions = new HashSet<>(Arrays.asList(null, 26, 28)); + mOutput = new SystemOutGreylistConsumer(); + mAllowedSdkVersions = new HashSet<>(Arrays.asList(null, 26, 28)); } - Set<String> publicApis; if (mPublicApiListFile != null) { - publicApis = Sets.newHashSet( + mPublicApis = Sets.newHashSet( Files.readLines(new File(mPublicApiListFile), Charset.forName("UTF-8"))); } else { - publicApis = Collections.emptySet(); + mPublicApis = Collections.emptySet(); } + } + private Map<String, AnnotationHandler> createAnnotationHandlers() { + return ImmutableMap.<String, AnnotationHandler>builder() + .put(GreylistAnnotationHandler.ANNOTATION_NAME, + new GreylistAnnotationHandler( + mStatus, mOutput, mPublicApis, mAllowedSdkVersions)) + .build(); + } + + private void main() throws IOException { + Map<String, AnnotationHandler> handlers = createAnnotationHandlers(); for (String jarFile : mJarFiles) { mStatus.debug("Processing jar file %s", jarFile); try { JarReader reader = new JarReader(mStatus, jarFile); - reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, ANNOTATION_TYPE, - publicApis, allowedSdkVersions, output, mStatus).visit()); + reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, mStatus, handlers) + .visit()); reader.close(); } catch (IOException e) { mStatus.error(e); } } - output.close(); + mOutput.close(); } @VisibleForTesting - Map<Integer, String> readGreylistMap(String[] argValues) { + static Map<Integer, String> readGreylistMap(Status status, String[] argValues) { Map<Integer, String> map = new HashMap<>(); for (String sdkFile : argValues) { Integer maxTargetSdk = null; @@ -173,12 +196,12 @@ public class Class2Greylist { try { maxTargetSdk = Integer.valueOf(sdkFile.substring(0, colonPos)); } catch (NumberFormatException nfe) { - mStatus.error("Not a valid integer: %s from argument value '%s'", + status.error("Not a valid integer: %s from argument value '%s'", sdkFile.substring(0, colonPos), sdkFile); } filename = sdkFile.substring(colonPos + 1); if (filename.length() == 0) { - mStatus.error("Not a valid file name: %s from argument value '%s'", + status.error("Not a valid file name: %s from argument value '%s'", filename, sdkFile); } } else { @@ -186,7 +209,7 @@ public class Class2Greylist { filename = sdkFile; } if (map.containsKey(maxTargetSdk)) { - mStatus.error("Multiple output files for maxTargetSdk %s", maxTargetSdk); + status.error("Multiple output files for maxTargetSdk %s", maxTargetSdk); } else { map.put(maxTargetSdk, filename); } diff --git a/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java index 86eeeffbb5..9f334677c3 100644 --- a/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java +++ b/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java @@ -11,21 +11,29 @@ public class FileWritingGreylistConsumer implements GreylistConsumer { private final Status mStatus; private final Map<Integer, PrintStream> mSdkToPrintStreamMap; + private final PrintStream mWhitelistStream; + + private static PrintStream openFile(String filename) throws FileNotFoundException { + if (filename == null) { + return null; + } + return new PrintStream(new FileOutputStream(new File(filename))); + } private static Map<Integer, PrintStream> openFiles( Map<Integer, String> filenames) throws FileNotFoundException { Map<Integer, PrintStream> streams = new HashMap<>(); for (Map.Entry<Integer, String> entry : filenames.entrySet()) { - streams.put(entry.getKey(), - new PrintStream(new FileOutputStream(new File(entry.getValue())))); + streams.put(entry.getKey(), openFile(entry.getValue())); } return streams; } - public FileWritingGreylistConsumer(Status status, Map<Integer, String> sdkToFilenameMap) - throws FileNotFoundException { + public FileWritingGreylistConsumer(Status status, Map<Integer, String> sdkToFilenameMap, + String whitelistFile) throws FileNotFoundException { mStatus = status; mSdkToPrintStreamMap = openFiles(sdkToFilenameMap); + mWhitelistStream = openFile(whitelistFile); } @Override @@ -40,9 +48,19 @@ public class FileWritingGreylistConsumer implements GreylistConsumer { } @Override + public void whitelistEntry(String signature) { + if (mWhitelistStream != null) { + mWhitelistStream.println(signature); + } + } + + @Override public void close() { for (PrintStream p : mSdkToPrintStreamMap.values()) { p.close(); } + if (mWhitelistStream != null) { + mWhitelistStream.close(); + } } } diff --git a/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java new file mode 100644 index 0000000000..460f2c3c22 --- /dev/null +++ b/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java @@ -0,0 +1,146 @@ +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; + } + +} diff --git a/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java index debc21d139..fd855e88ed 100644 --- a/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java +++ b/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java @@ -9,5 +9,12 @@ public interface GreylistConsumer { */ void greylistEntry(String signature, Integer maxTargetSdk); + /** + * Handle a new whitelist entry. + * + * @param signature Signature of the member. + */ + void whitelistEntry(String signature); + void close(); } diff --git a/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java index 8e12759f2b..ad5ad705b4 100644 --- a/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java +++ b/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java @@ -7,6 +7,12 @@ public class SystemOutGreylistConsumer implements GreylistConsumer { } @Override + public void whitelistEntry(String signature) { + // Ignore. This class is only used when no grey/white lists are + // specified, so we have nowhere to write whitelist entries. + } + + @Override public void close() { } } diff --git a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java new file mode 100644 index 0000000000..8f4a76f765 --- /dev/null +++ b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java @@ -0,0 +1,55 @@ +/* + * 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.withSettings; + +import com.android.javac.Javac; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestName; + +import java.io.IOException; + +public class AnnotationHandlerTestBase { + + @Rule + public TestName mTestName = new TestName(); + + protected Javac mJavac; + protected GreylistConsumer mConsumer; + protected Status mStatus; + + @Before + public void baseSetup() throws IOException { + System.out.println(String.format("\n============== STARTING TEST: %s ==============\n", + mTestName.getMethodName())); + mConsumer = mock(GreylistConsumer.class); + mStatus = mock(Status.class, withSettings().verboseLogging()); + mJavac = new Javac(); + } + + protected void assertNoErrors() { + verify(mStatus, never()).error(any(Throwable.class)); + verify(mStatus, never()).error(any(), any()); + } +} diff --git a/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java b/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java index 979044b909..cb75dd30ed 100644 --- a/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java +++ b/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java @@ -1,3 +1,19 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; @@ -32,41 +48,36 @@ public class Class2GreylistTest { } @Test - public void testReadGreylistMap() { - Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null); - Map<Integer, String> map = c2gl.readGreylistMap( + public void testReadGreylistMap() throws IOException { + Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "1:apiOne", "3:apiThree"}); verifyZeroInteractions(mStatus); assertThat(map).containsExactly(null, "noApi", 1, "apiOne", 3, "apiThree"); } @Test - public void testReadGreylistMapDuplicate() { - Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null); - Map<Integer, String> map = c2gl.readGreylistMap( + public void testReadGreylistMapDuplicate() throws IOException { + Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "1:apiOne", "1:anotherOne"}); verify(mStatus, atLeastOnce()).error(any(), any()); } @Test public void testReadGreylistMapDuplicateNoApi() { - Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null); - Map<Integer, String> map = c2gl.readGreylistMap( + Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "anotherNoApi", "1:apiOne"}); verify(mStatus, atLeastOnce()).error(any(), any()); } @Test - public void testReadGreylistMapInvalidInt() { - Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null); - Map<Integer, String> map = c2gl.readGreylistMap(new String[]{"noApi", "a:apiOne"}); + public void testReadGreylistMapInvalidInt() throws IOException { + Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "a:apiOne"}); verify(mStatus, atLeastOnce()).error(any(), any()); } @Test - public void testReadGreylistMapNoFilename() { - Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null); - Map<Integer, String> map = c2gl.readGreylistMap(new String[]{"noApi", "1:"}); + public void testReadGreylistMapNoFilename() throws IOException { + Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "1:"}); verify(mStatus, atLeastOnce()).error(any(), any()); } } diff --git a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationVisitorTest.java b/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java index 994fe89a61..1a4bfb8283 100644 --- a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationVisitorTest.java +++ b/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java @@ -19,47 +19,32 @@ package com.android.class2greylist; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.withSettings; import static java.util.Collections.emptySet; -import com.android.javac.Javac; - import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; import org.mockito.ArgumentCaptor; import java.io.IOException; +import java.util.Map; import java.util.Set; +import java.util.function.Predicate; -public class AnnotationVisitorTest { +public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { private static final String ANNOTATION = "Lannotation/Anno;"; - @Rule - public TestName mTestName = new TestName(); - - private Javac mJavac; - private GreylistConsumer mConsumer; - private Status mStatus; - @Before public void setup() throws IOException { - System.out.println(String.format("\n============== STARTING TEST: %s ==============\n", - mTestName.getMethodName())); - mConsumer = mock(GreylistConsumer.class); - mStatus = mock(Status.class, withSettings().verboseLogging()); - mJavac = new Javac(); mJavac.addSource("annotation.Anno", Joiner.on('\n').join( "package annotation;", "import static java.lang.annotation.RetentionPolicy.CLASS;", @@ -71,9 +56,11 @@ public class AnnotationVisitorTest { "}")); } - private void assertNoErrors() { - verify(mStatus, never()).error(any(Throwable.class)); - verify(mStatus, never()).error(any(), any()); + private GreylistAnnotationHandler createGreylistHandler( + Predicate<GreylistAnnotationHandler.GreylistMember> greylistFilter, + Set<Integer> validMaxTargetSdkValues) { + return new GreylistAnnotationHandler( + mStatus, mConsumer, greylistFilter, validMaxTargetSdkValues); } @Test @@ -87,8 +74,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -107,8 +95,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -127,8 +116,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -147,8 +137,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -167,8 +158,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ).visit(); verify(mStatus, times(1)).error(any(), any()); } @@ -186,8 +178,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), mStatus, + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -204,8 +197,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ).visit(); assertNoErrors(); verify(mConsumer, never()).greylistEntry(any(String.class), any()); @@ -222,8 +216,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -249,10 +244,10 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -281,10 +276,10 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -317,15 +312,12 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor( - mJavac.getCompiledClass("a.b.Interface"), ANNOTATION, x -> true, emptySet(), - mConsumer, mStatus).visit(); - new AnnotationVisitor( - mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true, emptySet(), mConsumer, - mStatus).visit(); - new AnnotationVisitor( - mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, emptySet(), mConsumer, - mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Interface"), mStatus, handlerMap) + .visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -357,10 +349,15 @@ public class AnnotationVisitorTest { Set<String> publicApis = Sets.newHashSet( "La/b/Base;->method(Ljava/lang/Object;)V", "La/b/Class;->method(Ljava/lang/Object;)V"); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, publicApis, - emptySet(), mConsumer, mStatus).visit(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, publicApis, - emptySet(), mConsumer, mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, + new GreylistAnnotationHandler( + mStatus, + mConsumer, + publicApis, + emptySet())); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit(); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); @@ -380,9 +377,11 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, - member -> !member.bridge, // exclude bridge methods - emptySet(), mConsumer, mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, createGreylistHandler( + member -> !member.bridge, // exclude bridge methods + emptySet())); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any()); @@ -400,8 +399,9 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - emptySet(), mConsumer, mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); verify(mStatus, times(1)).error(any(), any()); } @@ -416,8 +416,11 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - ImmutableSet.of(1), mConsumer, mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, createGreylistHandler( + x -> true, + ImmutableSet.of(1))); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); ArgumentCaptor<Integer> maxTargetSdk = ArgumentCaptor.forClass(Integer.class); verify(mConsumer, times(1)).greylistEntry(any(), maxTargetSdk.capture()); @@ -435,8 +438,11 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - ImmutableSet.of(1), mConsumer, mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, createGreylistHandler( + x -> true, + ImmutableSet.of(1))); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); ArgumentCaptor<Integer> maxTargetSdk = ArgumentCaptor.forClass(Integer.class); verify(mConsumer, times(1)).greylistEntry(any(), maxTargetSdk.capture()); @@ -454,8 +460,12 @@ public class AnnotationVisitorTest { "}")); assertThat(mJavac.compile()).isTrue(); - new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, - ImmutableSet.of(1), mConsumer, mStatus).visit(); + Map<String, AnnotationHandler> handlerMap = + ImmutableMap.of(ANNOTATION, createGreylistHandler( + x -> true, + ImmutableSet.of(1))); + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); verify(mStatus, times(1)).error(any(), any()); } + } |