summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mathew Inwood <mathewi@google.com> 2018-08-09 11:11:22 +0100
committer Mathew Inwood <mathewi@google.com> 2018-08-20 15:04:08 +0100
commit3866641884c1661bbaae4864cbda2b61b8caaa86 (patch)
tree8cee2b12b4bd27fe426eb43e46a71099080845eb
parentf345404c725330914b8313d2c1af17226c5b92ca (diff)
Support for maxTargetSdk on @UnsupportedAppUsage.
class2greylist now supports writing to multiple output greylist files specified on the command line like so: class2greylist --write-greylist lightgrey.txt \ --write-greylist 26:p-blacklist.txt \ --write-greylist 28:q-blacklist.txt If no --write-greylist parameter is specified, then all annotated interfaces will simply be written to standard out as before. This maintains the previous behaviour, and de-couples this change from the corresponding build changes. Any maxTargetSdk parameters will be validated against the set of valid values given in the --write-greylist parameters. If no such parameters are given, the valid values are: 26 (O), 28 (P) or none. Test: atest class2greylisttest Bug: 110868826 Change-Id: Id001d5427b1d399f3af98444b501e8405531a66d
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java85
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java119
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java48
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java13
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/Status.java8
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java12
-rw-r--r--tools/class2greylist/test/src/com/android/class2greylist/AnnotationVisitorTest.java (renamed from tools/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java)165
-rw-r--r--tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java73
8 files changed, 428 insertions, 95 deletions
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java
index 5914b26331..1838575582 100644
--- a/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java
+++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java
@@ -17,16 +17,19 @@
package com.android.class2greylist;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.DescendingVisitor;
+import org.apache.bcel.classfile.ElementValue;
import org.apache.bcel.classfile.ElementValuePair;
import org.apache.bcel.classfile.EmptyVisitor;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.FieldOrMethod;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
+import org.apache.bcel.classfile.SimpleElementValue;
import java.util.Locale;
import java.util.Set;
@@ -35,8 +38,8 @@ import java.util.function.Predicate;
/**
* Visits a JavaClass instance and pulls out all members annotated with a
* specific annotation. The signatures of such members are passed to {@link
- * Status#greylistEntry(String)}. Any errors result in a call to {@link
- * Status#error(String)}.
+ * GreylistConsumer#greylistEntry(String, Integer)}. Any errors result in a
+ * call to {@link Status#error(String, Object...)}.
*
* If the annotation has a property "expectedSignature" the generated signature
* will be verified against the one specified there. If it differs, an error
@@ -45,10 +48,13 @@ import java.util.function.Predicate;
public class AnnotationVisitor extends EmptyVisitor {
private static final String EXPECTED_SIGNATURE = "expectedSignature";
+ private static final String MAX_TARGET_SDK = "maxTargetSdk";
private final JavaClass mClass;
private final String mAnnotationType;
private final Predicate<Member> mMemberFilter;
+ private final Set<Integer> mValidMaxTargetSdkValues;
+ private final GreylistConsumer mConsumer;
private final Status mStatus;
private final DescendingVisitor mDescendingVisitor;
@@ -66,27 +72,43 @@ public class AnnotationVisitor extends EmptyVisitor {
* Indicates if this is a synthetic bridge method.
*/
public final boolean bridge;
+ /**
+ * Max target SDK of property this member, if it is set, else null.
+ *
+ * Note: even though the annotation itself specified a default value,
+ * that default value is not encoded into instances of the annotation
+ * in class files. So when no value is specified in source, it will
+ * result in null appearing in here.
+ */
+ public final Integer maxTargetSdk;
- public Member(String signature, boolean bridge) {
+ public Member(String signature, boolean bridge, Integer maxTargetSdk) {
this.signature = signature;
this.bridge = bridge;
+ this.maxTargetSdk = maxTargetSdk;
}
}
- public AnnotationVisitor(
- JavaClass clazz, String annotation, Set<String> publicApis, Status status) {
+ public AnnotationVisitor(JavaClass clazz, String annotation, Set<String> publicApis,
+ Set<Integer> validMaxTargetSdkValues, GreylistConsumer consumer,
+ Status status) {
this(clazz,
annotation,
member -> !(member.bridge && publicApis.contains(member.signature)),
+ validMaxTargetSdkValues,
+ consumer,
status);
}
@VisibleForTesting
- public AnnotationVisitor(
- JavaClass clazz, String annotation, Predicate<Member> memberFilter, Status status) {
+ public AnnotationVisitor(JavaClass clazz, String annotation, Predicate<Member> memberFilter,
+ Set<Integer> validMaxTargetSdkValues, GreylistConsumer consumer,
+ Status status) {
mClass = clazz;
mAnnotationType = annotation;
mMemberFilter = memberFilter;
+ mValidMaxTargetSdkValues = validMaxTargetSdkValues;
+ mConsumer = consumer;
mStatus = status;
mDescendingVisitor = new DescendingVisitor(clazz, this);
}
@@ -127,27 +149,56 @@ public class AnnotationVisitor extends EmptyVisitor {
}
String signature = String.format(Locale.US, signatureFormatString,
getClassDescriptor(definingClass), member.getName(), member.getSignature());
+ Integer maxTargetSdk = null;
for (ElementValuePair property : a.getElementValuePairs()) {
switch (property.getNameString()) {
case EXPECTED_SIGNATURE:
- String expected = property.getValue().stringifyValue();
- // Don't enforce for bridge methods; they're generated so won't match.
- if (!bridge && !signature.equals(expected)) {
- error(definingClass, member,
- "Expected signature does not match generated:\n"
- + "Expected: %s\n"
- + "Generated: %s", expected, signature);
- }
+ verifyExpectedSignature(
+ property, signature, definingClass, member, bridge);
+ break;
+ case MAX_TARGET_SDK:
+ maxTargetSdk = verifyAndGetMaxTargetSdk(
+ property, definingClass, member);
break;
}
}
- if (mMemberFilter.test(new Member(signature, bridge))) {
- mStatus.greylistEntry(signature);
+ if (mMemberFilter.test(new Member(signature, bridge, maxTargetSdk))) {
+ mConsumer.greylistEntry(signature, maxTargetSdk);
}
}
}
}
+ private void verifyExpectedSignature(ElementValuePair property, String signature,
+ JavaClass definingClass, FieldOrMethod member, boolean isBridge) {
+ String expected = property.getValue().stringifyValue();
+ // Don't enforce for bridge methods; they're generated so won't match.
+ if (!isBridge && !signature.equals(expected)) {
+ error(definingClass, member,
+ "Expected signature does not match generated:\n"
+ + "Expected: %s\n"
+ + "Generated: %s", expected, signature);
+ }
+ }
+
+ private Integer verifyAndGetMaxTargetSdk(
+ ElementValuePair property, JavaClass definingClass, FieldOrMethod member) {
+ if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_INT) {
+ error(definingClass, member, "Expected property %s to be of type int; got %d",
+ property.getNameString(), property.getValue().getElementValueType());
+ }
+ int value = ((SimpleElementValue) property.getValue()).getValueInt();
+ if (!mValidMaxTargetSdkValues.contains(value)) {
+ error(definingClass, member,
+ "Invalid value for %s: got %d, expected one of [%s]",
+ property.getNameString(),
+ value,
+ Joiner.on(",").join(mValidMaxTargetSdkValues));
+ return null;
+ }
+ return value;
+ }
+
private void error(JavaClass clazz, FieldOrMethod member, String message, Object... args) {
StringBuilder error = new StringBuilder();
error.append(clazz.getSourceFileName())
diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
index abc9421e65..64a0357e13 100644
--- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
+++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
@@ -16,6 +16,7 @@
package com.android.class2greylist;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
@@ -30,7 +31,11 @@ import org.apache.commons.cli.ParseException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -41,6 +46,11 @@ public class Class2Greylist {
private static final String ANNOTATION_TYPE = "Landroid/annotation/UnsupportedAppUsage;";
+ private final Status mStatus;
+ private final String mPublicApiListFile;
+ private final String[] mPerSdkOutputFiles;
+ private final String[] mJarFiles;
+
public static void main(String[] args) {
Options options = new Options();
options.addOption(OptionBuilder
@@ -49,6 +59,15 @@ 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:filename\". If an integer is " +
+ "given, members with a matching maxTargetSdk are written to the file; if " +
+ "no integer is given, members with no maxTargetSdk are written.")
+ .create("g"));
+ options.addOption(OptionBuilder
.withLongOpt("debug")
.hasArgs(0)
.withDescription("Enable debug")
@@ -72,7 +91,7 @@ public class Class2Greylist {
if (cmd.hasOption('h')) {
help(options);
}
- String publicApiFilename = cmd.getOptionValue('p', null);
+
String[] jarFiles = cmd.getArgs();
if (jarFiles.length == 0) {
@@ -81,38 +100,98 @@ public class Class2Greylist {
}
Status status = new Status(cmd.hasOption('d'));
+ Class2Greylist c2gl = new Class2Greylist(
+ status, cmd.getOptionValue('p', null), cmd.getOptionValues('g'), jarFiles);
+ try {
+ c2gl.main();
+ } catch (IOException e) {
+ status.error(e);
+ }
+
+ if (status.ok()) {
+ System.exit(0);
+ } else {
+ System.exit(1);
+ }
+
+ }
+
+ @VisibleForTesting
+ Class2Greylist(Status status, String publicApiListFile, String[] perSdkLevelOutputFiles,
+ String[] jarFiles) {
+ mStatus = status;
+ mPublicApiListFile = publicApiListFile;
+ mPerSdkOutputFiles = perSdkLevelOutputFiles;
+ mJarFiles = jarFiles;
+ }
+
+ private void main() throws IOException {
+ GreylistConsumer output;
+ Set<Integer> allowedSdkVersions;
+ if (mPerSdkOutputFiles != null) {
+ Map<Integer, String> outputFiles = readGreylistMap(mPerSdkOutputFiles);
+ output = new FileWritingGreylistConsumer(mStatus, outputFiles);
+ allowedSdkVersions = outputFiles.keySet();
+ } else {
+ // TODO remove this once per-SDK greylist support integrated into the build.
+ // Right now, mPerSdkOutputFiles is always null as the build never passes the
+ // corresponding command lind flags. Once the build is updated, can remove this.
+ output = new SystemOutGreylistConsumer();
+ allowedSdkVersions = new HashSet<>(Arrays.asList(null, 26, 28));
+ }
Set<String> publicApis;
- if (publicApiFilename != null) {
- try {
- publicApis = Sets.newHashSet(
- Files.readLines(new File(publicApiFilename), Charset.forName("UTF-8")));
- } catch (IOException e) {
- status.error(e);
- System.exit(1);
- return;
- }
+ if (mPublicApiListFile != null) {
+ publicApis = Sets.newHashSet(
+ Files.readLines(new File(mPublicApiListFile), Charset.forName("UTF-8")));
} else {
publicApis = Collections.emptySet();
}
- for (String jarFile : jarFiles) {
- status.debug("Processing jar file %s", jarFile);
+ for (String jarFile : mJarFiles) {
+ mStatus.debug("Processing jar file %s", jarFile);
try {
- JarReader reader = new JarReader(status, jarFile);
+ JarReader reader = new JarReader(mStatus, jarFile);
reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, ANNOTATION_TYPE,
- publicApis, status).visit());
+ publicApis, allowedSdkVersions, output, mStatus).visit());
reader.close();
} catch (IOException e) {
- status.error(e);
+ mStatus.error(e);
}
}
- if (status.ok()) {
- System.exit(0);
- } else {
- System.exit(1);
- }
+ output.close();
+ }
+ @VisibleForTesting
+ Map<Integer, String> readGreylistMap(String[] argValues) {
+ Map<Integer, String> map = new HashMap<>();
+ for (String sdkFile : argValues) {
+ Integer maxTargetSdk = null;
+ String filename;
+ int colonPos = sdkFile.indexOf(':');
+ if (colonPos != -1) {
+ try {
+ maxTargetSdk = Integer.valueOf(sdkFile.substring(0, colonPos));
+ } catch (NumberFormatException nfe) {
+ mStatus.error("Not a valid integer: %s from argument value '%s'",
+ sdkFile.substring(0, colonPos), sdkFile);
+ }
+ filename = sdkFile.substring(colonPos + 1);
+ if (filename.length() == 0) {
+ mStatus.error("Not a valid file name: %s from argument value '%s'",
+ filename, sdkFile);
+ }
+ } else {
+ maxTargetSdk = null;
+ filename = sdkFile;
+ }
+ if (map.containsKey(maxTargetSdk)) {
+ mStatus.error("Multiple output files for maxTargetSdk %s", maxTargetSdk);
+ } else {
+ map.put(maxTargetSdk, filename);
+ }
+ }
+ return map;
}
private static void help(Options options) {
diff --git a/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java
new file mode 100644
index 0000000000..86eeeffbb5
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java
@@ -0,0 +1,48 @@
+package com.android.class2greylist;
+
+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 static Map<Integer, PrintStream> openFiles(
+ Map<Integer, String> filenames) throws FileNotFoundException {
+ Map<Integer, PrintStream> streams = new HashMap<>();
+ for (Map.Entry<Integer, String> entry : filenames.entrySet()) {
+ streams.put(entry.getKey(),
+ new PrintStream(new FileOutputStream(new File(entry.getValue()))));
+ }
+ return streams;
+ }
+
+ public FileWritingGreylistConsumer(Status status, Map<Integer, String> sdkToFilenameMap)
+ throws FileNotFoundException {
+ mStatus = status;
+ mSdkToPrintStreamMap = openFiles(sdkToFilenameMap);
+ }
+
+ @Override
+ public void greylistEntry(String signature, Integer maxTargetSdk) {
+ 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 close() {
+ for (PrintStream p : mSdkToPrintStreamMap.values()) {
+ p.close();
+ }
+ }
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java
new file mode 100644
index 0000000000..debc21d139
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java
@@ -0,0 +1,13 @@
+package com.android.class2greylist;
+
+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);
+
+ void close();
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/Status.java b/tools/class2greylist/src/com/android/class2greylist/Status.java
index d7078986d9..b5ee9f138f 100644
--- a/tools/class2greylist/src/com/android/class2greylist/Status.java
+++ b/tools/class2greylist/src/com/android/class2greylist/Status.java
@@ -42,16 +42,12 @@ public class Status {
mHasErrors = true;
}
- public void error(String message) {
+ public void error(String message, Object... args) {
System.err.print(ERROR);
- System.err.println(message);
+ System.err.println(String.format(Locale.US, message, args));
mHasErrors = true;
}
- public void greylistEntry(String signature) {
- System.out.println(signature);
- }
-
public boolean ok() {
return !mHasErrors;
}
diff --git a/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java
new file mode 100644
index 0000000000..8e12759f2b
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java
@@ -0,0 +1,12 @@
+package com.android.class2greylist;
+
+public class SystemOutGreylistConsumer implements GreylistConsumer {
+ @Override
+ public void greylistEntry(String signature, Integer maxTargetSdk) {
+ System.out.println(signature);
+ }
+
+ @Override
+ public void close() {
+ }
+}
diff --git a/tools/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationVisitorTest.java
index 20c959db12..994fe89a61 100644
--- a/tools/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java
+++ b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationVisitorTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.javac;
+package com.android.class2greylist;
import static com.google.common.truth.Truth.assertThat;
@@ -25,10 +25,12 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.withSettings;
-import com.android.class2greylist.AnnotationVisitor;
-import com.android.class2greylist.Status;
+import static java.util.Collections.emptySet;
+
+import com.android.javac.Javac;
import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.junit.Before;
@@ -48,28 +50,30 @@ public class AnnotationVisitorTest {
public TestName mTestName = new TestName();
private Javac mJavac;
+ private GreylistConsumer mConsumer;
private Status mStatus;
@Before
public void setup() throws IOException {
System.out.println(String.format("\n============== STARTING TEST: %s ==============\n",
mTestName.getMethodName()));
+ mConsumer = mock(GreylistConsumer.class);
mStatus = mock(Status.class, withSettings().verboseLogging());
mJavac = new Javac();
mJavac.addSource("annotation.Anno", Joiner.on('\n').join(
"package annotation;",
- "import static java.lang.annotation.RetentionPolicy.CLASS;",
- "import java.lang.annotation.Retention;",
- "import java.lang.annotation.Target;",
+ "import static java.lang.annotation.RetentionPolicy.CLASS;",
+ "import java.lang.annotation.Retention;",
"@Retention(CLASS)",
"public @interface Anno {",
" String expectedSignature() default \"\";",
+ " int maxTargetSdk() default Integer.MAX_VALUE;",
"}"));
}
private void assertNoErrors() {
verify(mStatus, never()).error(any(Throwable.class));
- verify(mStatus, never()).error(any(String.class));
+ verify(mStatus, never()).error(any(), any());
}
@Test
@@ -83,12 +87,12 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
- verify(mStatus, times(1)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
}
@@ -103,12 +107,12 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
- verify(mStatus, times(1)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;-><init>()V");
}
@@ -123,12 +127,12 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
- verify(mStatus, times(1)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->i:I");
}
@@ -143,12 +147,12 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
- verify(mStatus, times(1)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
}
@@ -163,10 +167,10 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
- verify(mStatus, times(1)).error(any(String.class));
+ verify(mStatus, times(1)).error(any(), any());
}
@Test
@@ -183,11 +187,11 @@ public class AnnotationVisitorTest {
assertThat(mJavac.compile()).isTrue();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), ANNOTATION, x -> true,
- mStatus).visit();
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
- verify(mStatus, times(1)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class$Inner;->method()V");
}
@@ -200,11 +204,11 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
- verify(mStatus, never()).greylistEntry(any(String.class));
+ verify(mConsumer, never()).greylistEntry(any(String.class), any());
}
@Test
@@ -218,12 +222,12 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
- verify(mStatus, times(1)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
}
@@ -245,15 +249,15 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true, mStatus)
- .visit();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
// A bridge method is generated for the above, so we expect 2 greylist entries.
- verify(mStatus, times(2)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getAllValues()).containsExactly(
"La/b/Class;->method(Ljava/lang/Object;)V",
"La/b/Class;->method(Ljava/lang/String;)V");
@@ -277,15 +281,15 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true, mStatus)
- .visit();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus)
- .visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
// A bridge method is generated for the above, so we expect 2 greylist entries.
- verify(mStatus, times(2)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getAllValues()).containsExactly(
"La/b/Class;->method(Ljava/lang/Object;)V",
"La/b/Class;->method(Ljava/lang/String;)V");
@@ -314,16 +318,19 @@ public class AnnotationVisitorTest {
assertThat(mJavac.compile()).isTrue();
new AnnotationVisitor(
- mJavac.getCompiledClass("a.b.Interface"), ANNOTATION, x -> true, mStatus).visit();
+ mJavac.getCompiledClass("a.b.Interface"), ANNOTATION, x -> true, emptySet(),
+ mConsumer, mStatus).visit();
new AnnotationVisitor(
- mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true, mStatus).visit();
+ mJavac.getCompiledClass("a.b.Base"), ANNOTATION, x -> true, emptySet(), mConsumer,
+ mStatus).visit();
new AnnotationVisitor(
- mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, mStatus).visit();
+ mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true, emptySet(), mConsumer,
+ mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
// A bridge method is generated for the above, so we expect 2 greylist entries.
- verify(mStatus, times(2)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getAllValues()).containsExactly(
"La/b/Class;->method(Ljava/lang/Object;)V",
"La/b/Base;->method(Ljava/lang/Object;)V");
@@ -351,14 +358,14 @@ public class AnnotationVisitorTest {
"La/b/Base;->method(Ljava/lang/Object;)V",
"La/b/Class;->method(Ljava/lang/Object;)V");
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, publicApis,
- mStatus).visit();
+ emptySet(), mConsumer, mStatus).visit();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, publicApis,
- mStatus).visit();
+ emptySet(), mConsumer, mStatus).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(mStatus, times(1)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
}
@@ -375,10 +382,10 @@ public class AnnotationVisitorTest {
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION,
member -> !member.bridge, // exclude bridge methods
- mStatus).visit();
+ emptySet(), mConsumer, mStatus).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
- verify(mStatus, times(1)).greylistEntry(greylist.capture());
+ verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->field:I");
}
@@ -393,8 +400,62 @@ public class AnnotationVisitorTest {
"}"));
assertThat(mJavac.compile()).isTrue();
- new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION,
- x -> true, mStatus).visit();
- verify(mStatus, times(1)).error(any(String.class));
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ emptySet(), mConsumer, mStatus).visit();
+ verify(mStatus, times(1)).error(any(), any());
+ }
+
+ @Test
+ public void testMethodMaxTargetSdk() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Anno;",
+ "public class Class {",
+ " @Anno(maxTargetSdk=1)",
+ " public int field;",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ ImmutableSet.of(1), mConsumer, mStatus).visit();
+ assertNoErrors();
+ ArgumentCaptor<Integer> maxTargetSdk = ArgumentCaptor.forClass(Integer.class);
+ verify(mConsumer, times(1)).greylistEntry(any(), maxTargetSdk.capture());
+ assertThat(maxTargetSdk.getValue()).isEqualTo(1);
+ }
+
+ @Test
+ public void testMethodNoMaxTargetSdk() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Anno;",
+ "public class Class {",
+ " @Anno",
+ " public int field;",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ ImmutableSet.of(1), mConsumer, mStatus).visit();
+ assertNoErrors();
+ ArgumentCaptor<Integer> maxTargetSdk = ArgumentCaptor.forClass(Integer.class);
+ verify(mConsumer, times(1)).greylistEntry(any(), maxTargetSdk.capture());
+ assertThat(maxTargetSdk.getValue()).isEqualTo(null);
+ }
+
+ @Test
+ public void testMethodMaxTargetSdkOutOfRange() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Anno;",
+ "public class Class {",
+ " @Anno(maxTargetSdk=2)",
+ " public int field;",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, x -> true,
+ ImmutableSet.of(1), mConsumer, mStatus).visit();
+ verify(mStatus, times(1)).error(any(), any());
}
}
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java b/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java
new file mode 100644
index 0000000000..979044b909
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java
@@ -0,0 +1,73 @@
+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() {
+ Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null);
+ Map<Integer, String> map = c2gl.readGreylistMap(
+ new String[]{"noApi", "1:apiOne", "3:apiThree"});
+ verifyZeroInteractions(mStatus);
+ assertThat(map).containsExactly(null, "noApi", 1, "apiOne", 3, "apiThree");
+ }
+
+ @Test
+ public void testReadGreylistMapDuplicate() {
+ Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null);
+ Map<Integer, String> map = c2gl.readGreylistMap(
+ new String[]{"noApi", "1:apiOne", "1:anotherOne"});
+ verify(mStatus, atLeastOnce()).error(any(), any());
+ }
+
+ @Test
+ public void testReadGreylistMapDuplicateNoApi() {
+ Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null);
+ Map<Integer, String> map = c2gl.readGreylistMap(
+ new String[]{"noApi", "anotherNoApi", "1:apiOne"});
+ verify(mStatus, atLeastOnce()).error(any(), any());
+ }
+
+ @Test
+ public void testReadGreylistMapInvalidInt() {
+ Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null);
+ Map<Integer, String> map = c2gl.readGreylistMap(new String[]{"noApi", "a:apiOne"});
+ verify(mStatus, atLeastOnce()).error(any(), any());
+ }
+
+ @Test
+ public void testReadGreylistMapNoFilename() {
+ Class2Greylist c2gl = new Class2Greylist(mStatus, null, null, null);
+ Map<Integer, String> map = c2gl.readGreylistMap(new String[]{"noApi", "1:"});
+ verify(mStatus, atLeastOnce()).error(any(), any());
+ }
+}
+