Support CSV hiddenapi flags in class2greylist
Access flags for hiddenapi are being converted from one file per flag
to a single CSV file in the format:
<api signature>,<flag1>,...,<flagN>
Adjust `class2greylist` to produce such a CSV instead of text files.
Test: m, phone boots
Test: atest art/tools/class2greylist/test
Change-Id: I6592027256e42d87589512ada1a8ce4bd1c03e63
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 0000000..0f5f413
--- /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 92d2ab6..ba1f583 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 0000000..aacd963
--- /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 870f85a..621ee11 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.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 @@
"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 @@
.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 @@
.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 @@
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 @@
}
@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 @@
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 @@
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 afd15b4..b8de7e9 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 @@
* <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 @@
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 bd0bf79..f2bc525 100644
--- a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
+++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
@@ -18,7 +18,7 @@
*
* <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 @@
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 7d28b31..0000000
--- 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 3cfec30..0000000
--- 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 b3ed1b1..0000000
--- 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 72c0ea4..0000000
--- 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 afded37..0000000
--- 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 0000000..54ca17c
--- /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 f86ac6e..0000000
--- 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 0000000..d1f3e77
--- /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 8f4a76f..65ebbf0 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 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 b87a5b1..0000000
--- 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 10fae9b..9d2f014 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 @@
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 @@
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 @@
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 @@
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 @@
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 7f4ce62..1202564 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 @@
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 @@
public class CovariantReturnTypeMultiHandlerTest extends AnnotationHandlerTestBase {
+ private static final String FLAG = "test-flag";
@Before
public void setup() throws IOException {
@@ -79,16 +81,17 @@
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 @@
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 1e1b1df..0000000
--- 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
similarity index 87%
rename from tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java
rename to tools/class2greylist/test/src/com/android/class2greylist/UnsupportedAppUsageAnnotationHandlerTest.java
index edf2ecd..cdf01af 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 @@
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.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 @@
"}"));
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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");