summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java4
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java89
-rw-r--r--tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java72
-rw-r--r--tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java147
-rw-r--r--tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java116
5 files changed, 428 insertions, 0 deletions
diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
index c0a3160a88..694a5f4a3f 100644
--- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
+++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
@@ -166,6 +166,10 @@ public class Class2Greylist {
.put(GreylistAnnotationHandler.ANNOTATION_NAME,
new GreylistAnnotationHandler(
mStatus, mOutput, mPublicApis, mAllowedSdkVersions))
+ .put(CovariantReturnTypeHandler.ANNOTATION_NAME,
+ new CovariantReturnTypeHandler(mOutput, mPublicApis))
+ .put(CovariantReturnTypeMultiHandler.ANNOTATION_NAME,
+ new CovariantReturnTypeMultiHandler(mOutput, mPublicApis))
.build();
}
diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java
new file mode 100644
index 0000000000..afd15b4c59
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java
@@ -0,0 +1,89 @@
+package com.android.class2greylist;
+
+import com.google.common.base.Preconditions;
+
+import org.apache.bcel.classfile.AnnotationEntry;
+import org.apache.bcel.classfile.ElementValuePair;
+import org.apache.bcel.classfile.Method;
+
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Handles {@code CovariantReturnType} annotations, generating whitelist
+ * entries from them.
+ *
+ * <p>A whitelist entry is generated with the same descriptor as the original
+ * method, but with the return type replaced with than specified by the
+ * {@link #RETURN_TYPE} property.
+ *
+ * <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 {
+
+ 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 Set<String> mPublicApis;
+
+ public CovariantReturnTypeHandler(GreylistConsumer consumer, Set<String> publicApis) {
+ mConsumer = consumer;
+ mPublicApis = publicApis;
+ }
+
+ @Override
+ public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
+ // Verify that the annotation has been applied to what we expect, and
+ // has the right form. Note, this should not strictly be necessary, as
+ // the annotation has a target of just 'method' and the property
+ // returnType does not have a default value, but checking makes the code
+ // less brittle to future changes.
+ if (!(context.member instanceof Method)) {
+ context.reportError("Cannot specify %s on a field", RETURN_TYPE);
+ return;
+ }
+ String returnType = findReturnType(annotation);
+ if (returnType == null) {
+ context.reportError("No %s set on @%s", RETURN_TYPE, SHORT_NAME);
+ return;
+ }
+ if (!mPublicApis.contains(context.getMemberDescriptor())) {
+ context.reportError("Found @%s on non-SDK method", SHORT_NAME);
+ return;
+ }
+
+ // Generate the signature of overload that we expect the annotation will
+ // cause the platform dexer to create.
+ String typeSignature = context.member.getSignature();
+ int closingBrace = typeSignature.indexOf(')');
+ Preconditions.checkState(closingBrace != -1,
+ "No ) found in method type signature %s", typeSignature);
+ typeSignature = new StringBuilder()
+ .append(typeSignature.substring(0, closingBrace + 1))
+ .append(returnType)
+ .toString();
+ String signature = String.format(Locale.US, context.signatureFormatString,
+ context.getClassDescriptor(), context.member.getName(), typeSignature);
+
+ if (mPublicApis.contains(signature)) {
+ context.reportError("Signature %s generated from @%s already exists as a public API",
+ signature, SHORT_NAME);
+ return;
+ }
+ mConsumer.whitelistEntry(signature);
+ }
+
+ private String findReturnType(AnnotationEntry a) {
+ for (ElementValuePair property : a.getElementValuePairs()) {
+ if (property.getNameString().equals(RETURN_TYPE)) {
+ return property.getValue().stringifyValue();
+ }
+ }
+ // not found
+ return null;
+ }
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
new file mode 100644
index 0000000000..bd0bf79169
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
@@ -0,0 +1,72 @@
+package com.android.class2greylist;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
+import org.apache.bcel.classfile.AnnotationElementValue;
+import org.apache.bcel.classfile.AnnotationEntry;
+import org.apache.bcel.classfile.ArrayElementValue;
+import org.apache.bcel.classfile.ElementValue;
+import org.apache.bcel.classfile.ElementValuePair;
+
+import java.util.Set;
+
+/**
+ * Handles {@code CovariantReturnType$CovariantReturnTypes} annotations, which
+ * are generated by the compiler when multiple {@code CovariantReturnType}
+ * annotations appear on a single method.
+ *
+ * <p>The enclosed annotations are passed to {@link CovariantReturnTypeHandler}.
+ */
+public class CovariantReturnTypeMultiHandler implements AnnotationHandler {
+
+ public static final String ANNOTATION_NAME =
+ "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;";
+
+ private static final String VALUE = "value";
+
+ private final CovariantReturnTypeHandler mWrappedHandler;
+ private final String mInnerAnnotationName;
+
+ public CovariantReturnTypeMultiHandler(GreylistConsumer consumer, Set<String> publicApis) {
+ this(consumer, publicApis, CovariantReturnTypeHandler.ANNOTATION_NAME);
+ }
+
+ @VisibleForTesting
+ public CovariantReturnTypeMultiHandler(GreylistConsumer consumer, Set<String> publicApis,
+ String innerAnnotationName) {
+ mWrappedHandler = new CovariantReturnTypeHandler(consumer, publicApis);
+ mInnerAnnotationName = innerAnnotationName;
+ }
+
+ @Override
+ public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
+ // Verify that the annotation has the form we expect
+ ElementValuePair value = findValue(annotation);
+ if (value == null) {
+ context.reportError("No value found on CovariantReturnType$CovariantReturnTypes");
+ return;
+ }
+ Preconditions.checkArgument(value.getValue() instanceof ArrayElementValue);
+ ArrayElementValue array = (ArrayElementValue) value.getValue();
+
+ // call wrapped handler on each enclosed annotation:
+ for (ElementValue v : array.getElementValuesArray()) {
+ Preconditions.checkArgument(v instanceof AnnotationElementValue);
+ AnnotationElementValue aev = (AnnotationElementValue) v;
+ Preconditions.checkArgument(
+ aev.getAnnotationEntry().getAnnotationType().equals(mInnerAnnotationName));
+ mWrappedHandler.handleAnnotation(aev.getAnnotationEntry(), context);
+ }
+ }
+
+ private ElementValuePair findValue(AnnotationEntry a) {
+ for (ElementValuePair property : a.getElementValuePairs()) {
+ if (property.getNameString().equals(VALUE)) {
+ return property;
+ }
+ }
+ // not found
+ return null;
+ }
+}
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java
new file mode 100644
index 0000000000..10fae9b11c
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static java.util.Collections.emptySet;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class CovariantReturnTypeHandlerTest extends AnnotationHandlerTestBase {
+
+ private static final String ANNOTATION = "Lannotation/Annotation;";
+
+ @Before
+ public void setup() throws IOException {
+ // To keep the test simpler and more concise, we don't use the real
+ // @CovariantReturnType annotation here, but use our own @Annotation.
+ // It doesn't have to match the real annotation, just have the same
+ // property (returnType).
+ mJavac.addSource("annotation.Annotation", Joiner.on('\n').join(
+ "package annotation;",
+ "import static java.lang.annotation.RetentionPolicy.CLASS;",
+ "import java.lang.annotation.Retention;",
+ "@Retention(CLASS)",
+ "public @interface Annotation {",
+ " Class<?> returnType();",
+ "}"));
+ }
+
+ @Test
+ public void testReturnTypeWhitelisted() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Annotation;",
+ "public class Class {",
+ " @Annotation(returnType=Integer.class)",
+ " public String method() {return null;}",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ Map<String, AnnotationHandler> handlerMap =
+ ImmutableMap.of(ANNOTATION,
+ new CovariantReturnTypeHandler(
+ mConsumer,
+ ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;")));
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+ assertNoErrors();
+ verify(mConsumer, times(1)).whitelistEntry(eq("La/b/Class;->method()Ljava/lang/Integer;"));
+ }
+
+ @Test
+ public void testAnnotatedMemberNotPublicApi() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Annotation;",
+ "public class Class {",
+ " @Annotation(returnType=Integer.class)",
+ " public String method() {return null;}",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ Map<String, AnnotationHandler> handlerMap =
+ ImmutableMap.of(ANNOTATION,
+ new CovariantReturnTypeHandler(
+ mConsumer,
+ emptySet()));
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+ verify(mStatus, atLeastOnce()).error(any(), any());
+ }
+
+ @Test
+ public void testReturnTypeAlreadyWhitelisted() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Annotation;",
+ "public class Class {",
+ " @Annotation(returnType=Integer.class)",
+ " public String method() {return null;}",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ Map<String, AnnotationHandler> handlerMap =
+ ImmutableMap.of(ANNOTATION,
+ new CovariantReturnTypeHandler(
+ mConsumer,
+ ImmutableSet.of(
+ "La/b/Class;->method()Ljava/lang/String;",
+ "La/b/Class;->method()Ljava/lang/Integer;"
+ )));
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+ verify(mStatus, atLeastOnce()).error(any(), any());
+ }
+
+ @Test
+ public void testAnnotationOnField() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Annotation;",
+ "public class Class {",
+ " @Annotation(returnType=Integer.class)",
+ " public String field;",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ Map<String, AnnotationHandler> handlerMap =
+ ImmutableMap.of(ANNOTATION,
+ new CovariantReturnTypeHandler(
+ mConsumer,
+ emptySet()));
+ 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
new file mode 100644
index 0000000000..7f4ce62002
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.times;
+import static org.mockito.Mockito.verify;
+
+import static java.util.Collections.emptySet;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class CovariantReturnTypeMultiHandlerTest extends AnnotationHandlerTestBase {
+
+
+ @Before
+ public void setup() throws IOException {
+ // To keep the test simpler and more concise, we don't use the real
+ // @CovariantReturnType annotation here, but use our own @Annotation
+ // and @Annotation.Multi that have the same semantics. It doesn't have
+ // to match the real annotation, just have the same properties
+ // (returnType and value).
+ mJavac.addSource("annotation.Annotation", Joiner.on('\n').join(
+ "package annotation;",
+ "import static java.lang.annotation.RetentionPolicy.CLASS;",
+ "import java.lang.annotation.Repeatable;",
+ "import java.lang.annotation.Retention;",
+ "@Repeatable(Annotation.Multi.class)",
+ "@Retention(CLASS)",
+ "public @interface Annotation {",
+ " Class<?> returnType();",
+ " @Retention(CLASS)",
+ " @interface Multi {",
+ " Annotation[] value();",
+ " }",
+ "}"));
+ }
+
+ @Test
+ public void testReturnTypeMulti() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Annotation;",
+ "public class Class {",
+ " @Annotation(returnType=Integer.class)",
+ " @Annotation(returnType=Long.class)",
+ " public String method() {return null;}",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ Map<String, AnnotationHandler> handlerMap =
+ ImmutableMap.of("Lannotation/Annotation$Multi;",
+ new CovariantReturnTypeMultiHandler(
+ mConsumer,
+ ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;"),
+ "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());
+ assertThat(whitelist.getAllValues()).containsExactly(
+ "La/b/Class;->method()Ljava/lang/Integer;",
+ "La/b/Class;->method()Ljava/lang/Long;"
+ );
+ }
+
+ @Test
+ public void testReturnTypeMultiNotPublicApi() throws IOException {
+ mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+ "package a.b;",
+ "import annotation.Annotation;",
+ "public class Class {",
+ " @Annotation(returnType=Integer.class)",
+ " @Annotation(returnType=Long.class)",
+ " public String method() {return null;}",
+ "}"));
+ assertThat(mJavac.compile()).isTrue();
+
+ Map<String, AnnotationHandler> handlerMap =
+ ImmutableMap.of("Lannotation/Annotation$Multi;",
+ new CovariantReturnTypeMultiHandler(
+ mConsumer,
+ emptySet(),
+ "Lannotation/Annotation;"));
+ new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+ verify(mStatus, atLeastOnce()).error(any(), any());
+ }
+}