diff options
20 files changed, 392 insertions, 710 deletions
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationConsumer.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationConsumer.java new file mode 100644 index 0000000000..0f5f413476 --- /dev/null +++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationConsumer.java @@ -0,0 +1,18 @@ +package com.android.class2greylist; + +import java.util.Map; +import java.util.Set; + +public interface AnnotationConsumer { + /** + * Handle a parsed annotation for a class member. + * + * @param apiSignature Signature of the class member. + * @param annotationProperties Map of stringified properties of this annotation. + * @param parsedFlags Array of flags parsed from the annotation for this member. + */ + public void consume(String apiSignature, Map<String, String> annotationProperties, + Set<String> parsedFlags); + + public void close(); +} diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java index 92d2ab6d79..ba1f583707 100644 --- a/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java +++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java @@ -1,11 +1,27 @@ package com.android.class2greylist; +import java.util.Map; +import java.util.HashMap; + import org.apache.bcel.classfile.AnnotationEntry; +import org.apache.bcel.classfile.ElementValuePair; + /** - * Interface for an annotation handler, which handle individual annotations on + * Base class for an annotation handler, which handle individual annotations on * class members. */ -public interface AnnotationHandler { - void handleAnnotation(AnnotationEntry annotation, AnnotationContext context); +public abstract class AnnotationHandler { + abstract void handleAnnotation(AnnotationEntry annotation, AnnotationContext context); + + protected Map<String, String> stringifyAnnotationProperties(AnnotationEntry annotation) { + Map<String, String> content = new HashMap<String, String>(); + + // Stringify all annotation properties. + for (ElementValuePair prop : annotation.getElementValuePairs()) { + content.put(prop.getNameString(), prop.getValue().stringifyValue()); + } + + return content; + } } diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java new file mode 100644 index 0000000000..aacd9639cd --- /dev/null +++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java @@ -0,0 +1,56 @@ +package com.android.class2greylist; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class AnnotationPropertyWriter implements AnnotationConsumer { + + private final PrintStream mOutput; + private final List<Map<String, String>> mContents; + private final Set<String> mColumns; + + public AnnotationPropertyWriter(String csvFile) throws FileNotFoundException { + mOutput = new PrintStream(new FileOutputStream(new File(csvFile))); + mContents = new ArrayList<>(); + mColumns = new HashSet<>(); + } + + public void consume(String apiSignature, Map<String, String> annotationProperties, + Set<String> parsedFlags) { + // Clone properties map. + Map<String, String> contents = new HashMap(annotationProperties); + + // Append the member signature. + contents.put("signature", apiSignature); + + // Store data. + mColumns.addAll(contents.keySet()); + mContents.add(contents); + } + + public void close() { + // Sort columns by name and print header row. + List<String> columns = new ArrayList<>(mColumns); + columns.sort(Comparator.naturalOrder()); + mOutput.println(columns.stream().collect(Collectors.joining(","))); + + // Sort contents according to columns and print. + for (Map<String, String> row : mContents) { + mOutput.println(columns.stream().map(column -> row.getOrDefault(column, "")) + .collect(Collectors.joining(","))); + } + + // Close output. + mOutput.close(); + } +} diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java index 870f85a2c3..621ee11fa7 100644 --- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java +++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java @@ -42,6 +42,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.HashMap; import java.util.Set; import java.util.function.Predicate; @@ -57,17 +58,28 @@ public class Class2Greylist { "Ldalvik/annotation/compat/UnsupportedAppUsage;"); private static final Set<String> WHITELIST_ANNOTATIONS = ImmutableSet.of(); + public static final String FLAG_WHITELIST = "whitelist"; + public static final String FLAG_GREYLIST = "greylist"; + public static final String FLAG_BLACKLIST = "blacklist"; + public static final String FLAG_GREYLIST_MAX_O = "greylist-max-o"; + + private static final Map<Integer, String> TARGET_SDK_TO_LIST_MAP; + static { + Map<Integer, String> map = new HashMap<>(); + map.put(null, FLAG_GREYLIST); + map.put(26, FLAG_GREYLIST_MAX_O); + map.put(28, FLAG_GREYLIST); + TARGET_SDK_TO_LIST_MAP = Collections.unmodifiableMap(map); + } + private final Status mStatus; private final String mPublicApiListFile; - private final String[] mPerSdkOutputFiles; - private final String mWhitelistFile; + private final String mCsvFlagsFile; private final String mCsvMetadataFile; private final String[] mJarFiles; - private final GreylistConsumer mOutput; - private final Predicate<Integer> mAllowedSdkVersions; + private final AnnotationConsumer mOutput; private final Set<String> mPublicApis; - public static void main(String[] args) { Options options = new Options(); options.addOption(OptionBuilder @@ -76,19 +88,9 @@ public class Class2Greylist { .withDescription("Public API list file. Used to de-dupe bridge methods.") .create("p")); options.addOption(OptionBuilder - .withLongOpt("write-greylist") - .hasArgs() - .withDescription( - "Specify file to write greylist to. Can be specified multiple times. " + - "Format is either just a filename, or \"int[,int,...]:filename\". If " + - "integers are given, members with matching maxTargetSdk values are " + - "written to the file; if no integer or \"none\" is given, members with " + - "no maxTargetSdk are written.") - .create("g")); - options.addOption(OptionBuilder - .withLongOpt("write-whitelist") + .withLongOpt("write-flags-csv") .hasArgs(1) - .withDescription("Specify file to write whitelist to.") + .withDescription("Specify file to write hiddenapi flags to.") .create('w')); options.addOption(OptionBuilder .withLongOpt("debug") @@ -106,7 +108,7 @@ public class Class2Greylist { .hasArgs(1) .withDescription("Specify a file to write API metaadata to. This is a CSV file " + "containing any annotation properties for all members. Do not use in " + - "conjunction with --write-greylist or --write-whitelist.") + "conjunction with --write-flags-csv.") .create('c')); options.addOption(OptionBuilder .withLongOpt("help") @@ -144,7 +146,6 @@ public class Class2Greylist { Class2Greylist c2gl = new Class2Greylist( status, cmd.getOptionValue('p', null), - cmd.getOptionValues('g'), cmd.getOptionValue('w', null), cmd.getOptionValue('c', null), jarFiles); @@ -163,34 +164,18 @@ public class Class2Greylist { } @VisibleForTesting - Class2Greylist(Status status, String publicApiListFile, String[] perSdkLevelOutputFiles, - String whitelistOutputFile, String csvMetadataFile, String[] jarFiles) + Class2Greylist(Status status, String publicApiListFile, String csvFlagsFile, + String csvMetadataFile, String[] jarFiles) throws IOException { mStatus = status; mPublicApiListFile = publicApiListFile; - mPerSdkOutputFiles = perSdkLevelOutputFiles; - mWhitelistFile = whitelistOutputFile; + mCsvFlagsFile = csvFlagsFile; mCsvMetadataFile = csvMetadataFile; mJarFiles = jarFiles; if (mCsvMetadataFile != null) { - mOutput = new CsvGreylistConsumer(mStatus, mCsvMetadataFile); - mAllowedSdkVersions = x -> true; + mOutput = new AnnotationPropertyWriter(mCsvMetadataFile); } else { - Map<Integer, String> outputFiles = readGreylistMap(mStatus, mPerSdkOutputFiles); - mOutput = new FileWritingGreylistConsumer(mStatus, outputFiles, mWhitelistFile); - mAllowedSdkVersions = new Predicate<Integer>(){ - @Override - public boolean test(Integer i) { - return outputFiles.keySet().contains(i); - } - - @Override - public String toString() { - // we reply on this toString behaviour for readable error messages in - // GreylistAnnotationHandler - return Joiner.on(",").join(outputFiles.keySet()); - } - }; + mOutput = new HiddenapiFlagsWriter(mCsvFlagsFile); } if (mPublicApiListFile != null) { @@ -203,14 +188,15 @@ public class Class2Greylist { private Map<String, AnnotationHandler> createAnnotationHandlers() { Builder<String, AnnotationHandler> builder = ImmutableMap.builder(); - GreylistAnnotationHandler greylistAnnotationHandler = new GreylistAnnotationHandler( - mStatus, mOutput, mPublicApis, mAllowedSdkVersions); + UnsupportedAppUsageAnnotationHandler greylistAnnotationHandler = + new UnsupportedAppUsageAnnotationHandler( + mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP); GREYLIST_ANNOTATIONS.forEach(a -> builder.put(a, greylistAnnotationHandler)); return builder .put(CovariantReturnTypeHandler.ANNOTATION_NAME, - new CovariantReturnTypeHandler(mOutput, mPublicApis)) + new CovariantReturnTypeHandler(mOutput, mPublicApis, FLAG_WHITELIST)) .put(CovariantReturnTypeMultiHandler.ANNOTATION_NAME, - new CovariantReturnTypeMultiHandler(mOutput, mPublicApis)) + new CovariantReturnTypeMultiHandler(mOutput, mPublicApis, FLAG_WHITELIST)) .build(); } @@ -230,48 +216,6 @@ public class Class2Greylist { mOutput.close(); } - @VisibleForTesting - static Map<Integer, String> readGreylistMap(Status status, String[] argValues) { - Map<Integer, String> map = new HashMap<>(); - for (String sdkFile : argValues) { - List<Integer> maxTargetSdks = new ArrayList<>(); - String filename; - int colonPos = sdkFile.indexOf(':'); - if (colonPos != -1) { - String[] targets = sdkFile.substring(0, colonPos).split(","); - for (String target : targets) { - if ("none".equals(target)) { - maxTargetSdks.add(null); - } else { - try { - maxTargetSdks.add(Integer.valueOf(target)); - } catch (NumberFormatException nfe) { - 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) { - status.error("Not a valid file name: %s from argument value '%s'", - filename, sdkFile); - } - } else { - maxTargetSdks.add(null); - filename = sdkFile; - } - for (Integer maxTargetSdk : maxTargetSdks) { - if (map.containsKey(maxTargetSdk)) { - status.error("Multiple output files for maxTargetSdk %s", - maxTargetSdk == null ? "none" : maxTargetSdk); - } else { - map.put(maxTargetSdk, filename); - } - } - } - return map; - } - private static void dumpAllMembers(Status status, String[] jarFiles) { for (String jarFile : jarFiles) { status.debug("Processing jar file %s", jarFile); diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java index afd15b4c59..b8de7e92fa 100644 --- a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java +++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java @@ -1,12 +1,18 @@ 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.Constant; +import org.apache.bcel.classfile.ConstantPool; +import org.apache.bcel.classfile.ElementValue; import org.apache.bcel.classfile.ElementValuePair; import org.apache.bcel.classfile.Method; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; /** @@ -20,19 +26,22 @@ import java.util.Set; * <p>Methods are also validated against the public API list, to assert that * the annotated method is already a public API. */ -public class CovariantReturnTypeHandler implements AnnotationHandler { +public class CovariantReturnTypeHandler extends AnnotationHandler { private static final String SHORT_NAME = "CovariantReturnType"; public static final String ANNOTATION_NAME = "Ldalvik/annotation/codegen/CovariantReturnType;"; private static final String RETURN_TYPE = "returnType"; - private final GreylistConsumer mConsumer; + private final AnnotationConsumer mAnnotationConsumer; private final Set<String> mPublicApis; + private final String mHiddenapiFlag; - public CovariantReturnTypeHandler(GreylistConsumer consumer, Set<String> publicApis) { - mConsumer = consumer; + public CovariantReturnTypeHandler(AnnotationConsumer consumer, Set<String> publicApis, + String hiddenapiFlag) { + mAnnotationConsumer = consumer; mPublicApis = publicApis; + mHiddenapiFlag = hiddenapiFlag; } @Override @@ -74,7 +83,9 @@ public class CovariantReturnTypeHandler implements AnnotationHandler { signature, SHORT_NAME); return; } - mConsumer.whitelistEntry(signature); + + mAnnotationConsumer.consume(signature, stringifyAnnotationProperties(annotation), + ImmutableSet.of(mHiddenapiFlag)); } private String findReturnType(AnnotationEntry a) { diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java index bd0bf79169..f2bc5254fa 100644 --- a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java +++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java @@ -18,7 +18,7 @@ import java.util.Set; * * <p>The enclosed annotations are passed to {@link CovariantReturnTypeHandler}. */ -public class CovariantReturnTypeMultiHandler implements AnnotationHandler { +public class CovariantReturnTypeMultiHandler extends AnnotationHandler { public static final String ANNOTATION_NAME = "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;"; @@ -28,14 +28,15 @@ public class CovariantReturnTypeMultiHandler implements AnnotationHandler { private final CovariantReturnTypeHandler mWrappedHandler; private final String mInnerAnnotationName; - public CovariantReturnTypeMultiHandler(GreylistConsumer consumer, Set<String> publicApis) { - this(consumer, publicApis, CovariantReturnTypeHandler.ANNOTATION_NAME); + public CovariantReturnTypeMultiHandler(AnnotationConsumer consumer, Set<String> publicApis, + String hiddenapiFlag) { + this(consumer, publicApis, hiddenapiFlag, CovariantReturnTypeHandler.ANNOTATION_NAME); } @VisibleForTesting - public CovariantReturnTypeMultiHandler(GreylistConsumer consumer, Set<String> publicApis, - String innerAnnotationName) { - mWrappedHandler = new CovariantReturnTypeHandler(consumer, publicApis); + public CovariantReturnTypeMultiHandler(AnnotationConsumer consumer, Set<String> publicApis, + String hiddenapiFlag, String innerAnnotationName) { + mWrappedHandler = new CovariantReturnTypeHandler(consumer, publicApis, hiddenapiFlag); mInnerAnnotationName = innerAnnotationName; } diff --git a/tools/class2greylist/src/com/android/class2greylist/CsvGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/CsvGreylistConsumer.java deleted file mode 100644 index 7d28b317f0..0000000000 --- a/tools/class2greylist/src/com/android/class2greylist/CsvGreylistConsumer.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.android.class2greylist; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.PrintStream; -import java.util.Map; - -public class CsvGreylistConsumer implements GreylistConsumer { - - private final Status mStatus; - private final CsvWriter mCsvWriter; - - public CsvGreylistConsumer(Status status, String csvMetadataFile) throws FileNotFoundException { - mStatus = status; - mCsvWriter = new CsvWriter( - new PrintStream(new FileOutputStream(new File(csvMetadataFile)))); - } - - @Override - public void greylistEntry(String signature, Integer maxTargetSdk, - Map<String, String> annotationProperties) { - annotationProperties.put("signature", signature); - mCsvWriter.addRow(annotationProperties); - } - - @Override - public void whitelistEntry(String signature) { - } - - @Override - public void close() { - mCsvWriter.close(); - } -} diff --git a/tools/class2greylist/src/com/android/class2greylist/CsvWriter.java b/tools/class2greylist/src/com/android/class2greylist/CsvWriter.java deleted file mode 100644 index 3cfec30f23..0000000000 --- a/tools/class2greylist/src/com/android/class2greylist/CsvWriter.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.android.class2greylist; - -import com.google.common.base.Joiner; - -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Helper class for writing data to a CSV file. - * - * This class does not write anything to its output until it is closed, so it can gather a set of - * all columns before writing the header row. - */ -public class CsvWriter { - - private final PrintStream mOutput; - private final ArrayList<Map<String, String>> mContents; - private final Set<String> mColumns; - - public CsvWriter(PrintStream out) { - mOutput = out; - mContents = new ArrayList<>(); - mColumns = new HashSet<>(); - } - - public void addRow(Map<String, String> values) { - mColumns.addAll(values.keySet()); - mContents.add(values); - } - - public void close() { - List<String> columns = new ArrayList<>(mColumns); - columns.sort(Comparator.naturalOrder()); - mOutput.println(columns.stream().collect(Collectors.joining(","))); - for (Map<String, String> row : mContents) { - mOutput.println(columns.stream().map(column -> row.getOrDefault(column, "")).collect( - Collectors.joining(","))); - } - mOutput.close(); - } - - -} diff --git a/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java deleted file mode 100644 index b3ed1b16f9..0000000000 --- a/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.android.class2greylist; - -import com.google.common.annotations.VisibleForTesting; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.PrintStream; -import java.util.HashMap; -import java.util.Map; - -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))); - } - - @VisibleForTesting - public static Map<Integer, PrintStream> openFiles( - Map<Integer, String> filenames) throws FileNotFoundException { - Map<String, PrintStream> streamsByName = new HashMap<>(); - Map<Integer, PrintStream> streams = new HashMap<>(); - for (Map.Entry<Integer, String> entry : filenames.entrySet()) { - if (!streamsByName.containsKey(entry.getValue())) { - streamsByName.put(entry.getValue(), openFile(entry.getValue())); - } - streams.put(entry.getKey(), streamsByName.get(entry.getValue())); - } - return streams; - } - - public FileWritingGreylistConsumer(Status status, Map<Integer, String> sdkToFilenameMap, - String whitelistFile) throws FileNotFoundException { - mStatus = status; - mSdkToPrintStreamMap = openFiles(sdkToFilenameMap); - mWhitelistStream = openFile(whitelistFile); - } - - @Override - public void greylistEntry( - String signature, Integer maxTargetSdk, Map<String, String> annotationProperties) { - PrintStream p = mSdkToPrintStreamMap.get(maxTargetSdk); - if (p == null) { - mStatus.error("No output file for signature %s with maxTargetSdk of %d", signature, - maxTargetSdk == null ? "<absent>" : maxTargetSdk.toString()); - return; - } - p.println(signature); - } - - @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 deleted file mode 100644 index 72c0ea4206..0000000000 --- a/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java +++ /dev/null @@ -1,149 +0,0 @@ -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.HashMap; -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 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 { - - // 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 Predicate<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, - Predicate<Integer> validMaxTargetSdkValues) { - this(status, greylistConsumer, - member -> !(member.bridge && publicApis.contains(member.signature)), - validMaxTargetSdkValues); - } - - @VisibleForTesting - public GreylistAnnotationHandler( - Status status, - GreylistConsumer greylistConsumer, - Predicate<GreylistMember> greylistFilter, - Predicate<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; - Map<String, String> allValues = new HashMap<String, String>(); - 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; - } - allValues.put(property.getNameString(), property.getValue().stringifyValue()); - } - if (mGreylistFilter.test(new GreylistMember(signature, bridge, maxTargetSdk))) { - mGreylistConsumer.greylistEntry(signature, maxTargetSdk, allValues); - } - } - - 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()); - return null; - } - int value = ((SimpleElementValue) property.getValue()).getValueInt(); - if (!mValidMaxTargetSdkValues.test(value)) { - context.reportError("Invalid value for %s: got %d, expected one of [%s]", - property.getNameString(), - value, - 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 deleted file mode 100644 index afded37e66..0000000000 --- a/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.android.class2greylist; - -import java.util.Map; - -public interface GreylistConsumer { - /** - * Handle a new greylist entry. - * - * @param signature Signature of the member. - * @param maxTargetSdk maxTargetSdk value from the annotation, or null if none set. - */ - void greylistEntry( - String signature, Integer maxTargetSdk, Map<String, String> annotationProperties); - - /** - * 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/HiddenapiFlagsWriter.java b/tools/class2greylist/src/com/android/class2greylist/HiddenapiFlagsWriter.java new file mode 100644 index 0000000000..54ca17cbc7 --- /dev/null +++ b/tools/class2greylist/src/com/android/class2greylist/HiddenapiFlagsWriter.java @@ -0,0 +1,40 @@ +package com.android.class2greylist; + +import com.google.common.annotations.VisibleForTesting; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class HiddenapiFlagsWriter implements AnnotationConsumer { + + private final PrintStream mOutput; + + public HiddenapiFlagsWriter(String csvFile) throws FileNotFoundException { + mOutput = new PrintStream(new FileOutputStream(new File(csvFile))); + } + + public void consume(String apiSignature, Map<String, String> annotationProperties, + Set<String> parsedFlags) { + if (parsedFlags.size() > 0) { + mOutput.println(apiSignature + "," + String.join(",", asSortedList(parsedFlags))); + } + } + + public void close() { + mOutput.close(); + } + + private static List<String> asSortedList(Set<String> s) { + List<String> list = new ArrayList<>(s); + Collections.sort(list); + return list; + } + +} diff --git a/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java deleted file mode 100644 index f86ac6ec68..0000000000 --- a/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.android.class2greylist; - -import java.util.Map; - -public class SystemOutGreylistConsumer implements GreylistConsumer { - @Override - public void greylistEntry( - String signature, Integer maxTargetSdk, Map<String, String> annotationValues) { - System.out.println(signature); - } - - @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/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandler.java new file mode 100644 index 0000000000..d1f3e77091 --- /dev/null +++ b/tools/class2greylist/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandler.java @@ -0,0 +1,134 @@ +package com.android.class2greylist; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +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 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 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 final Status mStatus; + private final Predicate<ClassMember> mClassMemberFilter; + private final Map<Integer, String> mSdkVersionToFlagMap; + private final AnnotationConsumer mAnnotationConsumer; + + /** + * 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); + } + + @VisibleForTesting + public UnsupportedAppUsageAnnotationHandler(Status status, + AnnotationConsumer annotationConsumer, Predicate<ClassMember> memberFilter, + Map<Integer, String> sdkVersionToFlagMap) { + mStatus = status; + mAnnotationConsumer = annotationConsumer; + mClassMemberFilter = memberFilter; + mSdkVersionToFlagMap = sdkVersionToFlagMap; + } + + @Override + public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) { + FieldOrMethod member = context.member; + + boolean 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; + + 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; + } + } + + // 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; + } + + // 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))); + } + } +} diff --git a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java index 8f4a76f765..65ebbf0181 100644 --- a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java +++ b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java @@ -36,14 +36,14 @@ public class AnnotationHandlerTestBase { public TestName mTestName = new TestName(); protected Javac mJavac; - protected GreylistConsumer mConsumer; + protected AnnotationConsumer 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); + mConsumer = mock(AnnotationConsumer.class); mStatus = mock(Status.class, withSettings().verboseLogging()); mJavac = new Javac(); } diff --git a/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java b/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java deleted file mode 100644 index b87a5b1c3f..0000000000 --- a/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.MockitoAnnotations.initMocks; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.mockito.Mock; - -import java.io.IOException; -import java.util.Map; - -public class Class2GreylistTest { - - @Mock - Status mStatus; - @Rule - public TestName mTestName = new TestName(); - - @Before - public void setup() throws IOException { - System.out.println(String.format("\n============== STARTING TEST: %s ==============\n", - mTestName.getMethodName())); - initMocks(this); - } - - @Test - 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 testReadGreylistMapNone() throws IOException { - Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus, - new String[]{"none:noApi"}); - verifyZeroInteractions(mStatus); - assertThat(map).containsExactly(null, "noApi"); - } - - @Test - public void testReadGreylistMapMulti() throws IOException { - Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus, - new String[]{"1,none:noOr1Api", "3:apiThree"}); - verifyZeroInteractions(mStatus); - assertThat(map).containsExactly(null, "noOr1Api", 1, "noOr1Api", 3, "apiThree"); - } - - @Test - public void testReadGreylistMapMulti2() throws IOException { - Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus, - new String[]{"1,none,2,3,4:allApi"}); - verifyZeroInteractions(mStatus); - assertThat(map).containsExactly( - null, "allApi", 1, "allApi", 2, "allApi", 3, "allApi", 4, "allApi"); - } - - @Test - 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.readGreylistMap(mStatus, - new String[]{"noApi", "anotherNoApi", "1:apiOne"}); - verify(mStatus, atLeastOnce()).error(any(), any()); - } - - @Test - public void testReadGreylistMapInvalidInt() throws IOException { - Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "a:apiOne"}); - verify(mStatus, atLeastOnce()).error(any(), any()); - } - - @Test - 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/CovariantReturnTypeHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java index 10fae9b11c..9d2f014e4e 100644 --- a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java +++ b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java @@ -40,6 +40,7 @@ import java.util.Map; public class CovariantReturnTypeHandlerTest extends AnnotationHandlerTestBase { private static final String ANNOTATION = "Lannotation/Annotation;"; + private static final String FLAG = "test-flag"; @Before public void setup() throws IOException { @@ -72,11 +73,13 @@ public class CovariantReturnTypeHandlerTest extends AnnotationHandlerTestBase { ImmutableMap.of(ANNOTATION, new CovariantReturnTypeHandler( mConsumer, - ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;"))); + ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;"), + FLAG)); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); - verify(mConsumer, times(1)).whitelistEntry(eq("La/b/Class;->method()Ljava/lang/Integer;")); + verify(mConsumer, times(1)).consume( + eq("La/b/Class;->method()Ljava/lang/Integer;"), any(), eq(ImmutableSet.of(FLAG))); } @Test @@ -94,7 +97,8 @@ public class CovariantReturnTypeHandlerTest extends AnnotationHandlerTestBase { ImmutableMap.of(ANNOTATION, new CovariantReturnTypeHandler( mConsumer, - emptySet())); + emptySet(), + FLAG)); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); verify(mStatus, atLeastOnce()).error(any(), any()); @@ -118,7 +122,8 @@ public class CovariantReturnTypeHandlerTest extends AnnotationHandlerTestBase { ImmutableSet.of( "La/b/Class;->method()Ljava/lang/String;", "La/b/Class;->method()Ljava/lang/Integer;" - ))); + ), + FLAG)); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); verify(mStatus, atLeastOnce()).error(any(), any()); @@ -139,7 +144,8 @@ public class CovariantReturnTypeHandlerTest extends AnnotationHandlerTestBase { ImmutableMap.of(ANNOTATION, new CovariantReturnTypeHandler( mConsumer, - emptySet())); + emptySet(), + FLAG)); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); verify(mStatus, atLeastOnce()).error(any(), any()); diff --git a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java index 7f4ce62002..1202564948 100644 --- a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java +++ b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java @@ -19,6 +19,7 @@ package com.android.class2greylist; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -38,6 +39,7 @@ import java.util.Map; public class CovariantReturnTypeMultiHandlerTest extends AnnotationHandlerTestBase { + private static final String FLAG = "test-flag"; @Before public void setup() throws IOException { @@ -79,16 +81,17 @@ public class CovariantReturnTypeMultiHandlerTest extends AnnotationHandlerTestBa new CovariantReturnTypeMultiHandler( mConsumer, ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;"), + FLAG, "Lannotation/Annotation;")); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); assertNoErrors(); ArgumentCaptor<String> whitelist = ArgumentCaptor.forClass(String.class); - verify(mConsumer, times(2)).whitelistEntry(whitelist.capture()); + verify(mConsumer, times(2)).consume(whitelist.capture(), any(), + eq(ImmutableSet.of(FLAG))); assertThat(whitelist.getAllValues()).containsExactly( "La/b/Class;->method()Ljava/lang/Integer;", - "La/b/Class;->method()Ljava/lang/Long;" - ); + "La/b/Class;->method()Ljava/lang/Long;"); } @Test @@ -108,6 +111,7 @@ public class CovariantReturnTypeMultiHandlerTest extends AnnotationHandlerTestBa new CovariantReturnTypeMultiHandler( mConsumer, emptySet(), + FLAG, "Lannotation/Annotation;")); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); diff --git a/tools/class2greylist/test/src/com/android/class2greylist/FileWritingGreylistConsumerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/FileWritingGreylistConsumerTest.java deleted file mode 100644 index 1e1b1df910..0000000000 --- a/tools/class2greylist/test/src/com/android/class2greylist/FileWritingGreylistConsumerTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.MockitoAnnotations.initMocks; - -import com.google.common.collect.ImmutableMap; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.mockito.Mock; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -public class FileWritingGreylistConsumerTest { - - @Mock - Status mStatus; - @Rule - public TestName mTestName = new TestName(); - private int mFileNameSeq = 0; - private final List<String> mTempFiles = new ArrayList<>(); - - @Before - public void setup() throws IOException { - System.out.println(String.format("\n============== STARTING TEST: %s ==============\n", - mTestName.getMethodName())); - initMocks(this); - } - - @After - public void removeTempFiles() { - for (String name : mTempFiles) { - new File(name).delete(); - } - } - - private String tempFileName() { - String name = String.format(Locale.US, "/tmp/test-%s-%d", - mTestName.getMethodName(), mFileNameSeq++); - mTempFiles.add(name); - return name; - } - - @Test - public void testSimpleMap() throws FileNotFoundException { - Map<Integer, PrintStream> streams = FileWritingGreylistConsumer.openFiles( - ImmutableMap.of(1, tempFileName(), 2, tempFileName())); - assertThat(streams.keySet()).containsExactly(1, 2); - assertThat(streams.get(1)).isNotNull(); - assertThat(streams.get(2)).isNotNull(); - assertThat(streams.get(2)).isNotSameAs(streams.get(1)); - } - - @Test - public void testCommonMappings() throws FileNotFoundException { - String name = tempFileName(); - Map<Integer, PrintStream> streams = FileWritingGreylistConsumer.openFiles( - ImmutableMap.of(1, name, 2, name)); - assertThat(streams.keySet()).containsExactly(1, 2); - assertThat(streams.get(1)).isNotNull(); - assertThat(streams.get(2)).isSameAs(streams.get(1)); - } -} diff --git a/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandlerTest.java index edf2ecd84d..cdf01afe7c 100644 --- a/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java +++ b/tools/class2greylist/test/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandlerTest.java @@ -19,11 +19,12 @@ package com.android.class2greylist; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static java.util.Collections.emptySet; +import static java.util.Collections.emptyMap; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; @@ -35,14 +36,23 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.Predicate; -public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { +public class UnsupportedAppUsageAnnotationHandlerTest extends AnnotationHandlerTestBase { private static final String ANNOTATION = "Lannotation/Anno;"; + private static final Map<Integer, String> NULL_SDK_MAP; + static { + Map<Integer, String> map = new HashMap<>(); + map.put(null, "flag-null"); + NULL_SDK_MAP = Collections.unmodifiableMap(map); + } + @Before public void setup() throws IOException { mJavac.addSource("annotation.Anno", Joiner.on('\n').join( @@ -56,11 +66,11 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { "}")); } - private GreylistAnnotationHandler createGreylistHandler( - Predicate<GreylistAnnotationHandler.GreylistMember> greylistFilter, - Set<Integer> validMaxTargetSdkValues) { - return new GreylistAnnotationHandler( - mStatus, mConsumer, greylistFilter, x -> validMaxTargetSdkValues.contains(x)); + private UnsupportedAppUsageAnnotationHandler createGreylistHandler( + Predicate<UnsupportedAppUsageAnnotationHandler.ClassMember> greylistFilter, + Map<Integer, String> validMaxTargetSdkValues) { + return new UnsupportedAppUsageAnnotationHandler( + mStatus, mConsumer, greylistFilter, validMaxTargetSdkValues); } @Test @@ -75,12 +85,12 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)) ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); - verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(1)).consume(greylist.capture(), any(), any()); assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V"); } @@ -96,12 +106,12 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)) ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); - verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(1)).consume(greylist.capture(), any(), any()); assertThat(greylist.getValue()).isEqualTo("La/b/Class;-><init>()V"); } @@ -117,12 +127,12 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)) ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); - verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(1)).consume(greylist.capture(), any(), any()); assertThat(greylist.getValue()).isEqualTo("La/b/Class;->i:I"); } @@ -138,12 +148,12 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)) ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); - verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(1)).consume(greylist.capture(), any(), any()); assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V"); } @@ -159,7 +169,7 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)) ).visit(); verify(mStatus, times(1)).error(any(), any()); @@ -179,12 +189,12 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), mStatus, - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)) ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); - verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(1)).consume(greylist.capture(), any(), any()); assertThat(greylist.getValue()).isEqualTo("La/b/Class$Inner;->method()V"); } @@ -198,11 +208,11 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)) ).visit(); assertNoErrors(); - verify(mConsumer, never()).greylistEntry(any(String.class), any(), any()); + verify(mConsumer, never()).consume(any(String.class), any(), any()); } @Test @@ -217,12 +227,12 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())) + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)) ).visit(); assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); - verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(1)).consume(greylist.capture(), any(), any()); assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V"); } @@ -245,14 +255,14 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); Map<String, AnnotationHandler> handlerMap = - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())); + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)); 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); // A bridge method is generated for the above, so we expect 2 greylist entries. - verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(2)).consume(greylist.capture(), any(), any()); assertThat(greylist.getAllValues()).containsExactly( "La/b/Class;->method(Ljava/lang/Object;)V", "La/b/Class;->method(Ljava/lang/String;)V"); @@ -277,14 +287,14 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); Map<String, AnnotationHandler> handlerMap = - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())); + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)); 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); // A bridge method is generated for the above, so we expect 2 greylist entries. - verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(2)).consume(greylist.capture(), any(), any()); assertThat(greylist.getAllValues()).containsExactly( "La/b/Class;->method(Ljava/lang/Object;)V", "La/b/Class;->method(Ljava/lang/String;)V"); @@ -313,7 +323,7 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); Map<String, AnnotationHandler> handlerMap = - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())); + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Interface"), mStatus, handlerMap) .visit(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit(); @@ -322,7 +332,7 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertNoErrors(); ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); // A bridge method is generated for the above, so we expect 2 greylist entries. - verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(2)).consume(greylist.capture(), any(), any()); assertThat(greylist.getAllValues()).containsExactly( "La/b/Class;->method(Ljava/lang/Object;)V", "La/b/Base;->method(Ljava/lang/Object;)V"); @@ -351,18 +361,18 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { "La/b/Class;->method(Ljava/lang/Object;)V"); Map<String, AnnotationHandler> handlerMap = ImmutableMap.of(ANNOTATION, - new GreylistAnnotationHandler( + new UnsupportedAppUsageAnnotationHandler( mStatus, mConsumer, publicApis, - x -> false)); + NULL_SDK_MAP)); 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); // The bridge method generated for the above, is a public API so should be excluded - verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any(), any()); + verify(mConsumer, times(1)).consume(greylist.capture(), any(), any()); assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V"); } @@ -379,12 +389,12 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { Map<String, AnnotationHandler> handlerMap = ImmutableMap.of(ANNOTATION, createGreylistHandler( - member -> !member.bridge, // exclude bridge methods - emptySet())); + member -> !member.isBridgeMethod, // exclude bridge methods + NULL_SDK_MAP)); 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(), any()); + verify(mConsumer, times(1)).consume(greylist.capture(), any(), any()); assertThat(greylist.getValue()).isEqualTo("La/b/Class;->field:I"); } @@ -400,7 +410,7 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); Map<String, AnnotationHandler> handlerMap = - ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet())); + ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP)); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); verify(mStatus, times(1)).error(any(), any()); } @@ -419,12 +429,10 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { Map<String, AnnotationHandler> handlerMap = ImmutableMap.of(ANNOTATION, createGreylistHandler( x -> true, - ImmutableSet.of(1))); + ImmutableMap.of(1, "flag1"))); 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(), any()); - assertThat(maxTargetSdk.getValue()).isEqualTo(1); + verify(mConsumer, times(1)).consume(any(), any(), eq(ImmutableSet.of("flag1"))); } @Test @@ -441,12 +449,10 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { Map<String, AnnotationHandler> handlerMap = ImmutableMap.of(ANNOTATION, createGreylistHandler( x -> true, - ImmutableSet.of(1))); + NULL_SDK_MAP)); 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(), any()); - assertThat(maxTargetSdk.getValue()).isEqualTo(null); + verify(mConsumer, times(1)).consume(any(), any(), eq(ImmutableSet.of("flag-null"))); } @Test @@ -463,7 +469,7 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { Map<String, AnnotationHandler> handlerMap = ImmutableMap.of(ANNOTATION, createGreylistHandler( x -> true, - ImmutableSet.of(1))); + NULL_SDK_MAP)); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit(); verify(mStatus, times(1)).error(any(), any()); } @@ -490,12 +496,12 @@ public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase { assertThat(mJavac.compile()).isTrue(); new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, ImmutableMap.of("Lannotation/Anno2;", createGreylistHandler(x -> true, - ImmutableSet.of(2))) + ImmutableMap.of(2, "flag2"))) ).visit(); assertNoErrors(); ArgumentCaptor<Map<String, String>> properties = ArgumentCaptor.forClass(Map.class); - verify(mConsumer, times(1)).greylistEntry(any(), any(), properties.capture()); + verify(mConsumer, times(1)).consume(any(), properties.capture(), any()); assertThat(properties.getValue()).containsExactly( "maxTargetSdk", "2", "trackingBug", "123456789"); |