summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--errorprone/java/android/annotation/SuppressLint.java38
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java328
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java325
-rw-r--r--errorprone/tests/res/android/annotation/RequiresPermission.java35
-rw-r--r--errorprone/tests/res/android/annotation/SuppressLint.java34
-rw-r--r--errorprone/tests/res/android/content/BroadcastReceiver.java23
-rw-r--r--errorprone/tests/res/android/content/Context.java3
-rw-r--r--errorprone/tests/res/android/database/ContentObserver.java23
-rw-r--r--errorprone/tests/res/android/foo/IColorService.java32
-rw-r--r--errorprone/tests/res/android/os/Handler.java23
-rw-r--r--errorprone/tests/res/android/os/IBinder.java23
-rw-r--r--errorprone/tests/res/android/os/Message.java20
12 files changed, 907 insertions, 0 deletions
diff --git a/errorprone/java/android/annotation/SuppressLint.java b/errorprone/java/android/annotation/SuppressLint.java
new file mode 100644
index 000000000000..2d3456b0ea46
--- /dev/null
+++ b/errorprone/java/android/annotation/SuppressLint.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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 android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+ /**
+ * The set of warnings (identified by the lint issue id) that should be
+ * ignored by lint. It is not an error to specify an unrecognized name.
+ */
+ String[] value();
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
new file mode 100644
index 000000000000..3b5a58c46f71
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2021 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.allOf;
+import static com.google.errorprone.matchers.Matchers.anyOf;
+import static com.google.errorprone.matchers.Matchers.enclosingClass;
+import static com.google.errorprone.matchers.Matchers.instanceMethod;
+import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.methodIsNamed;
+import static com.google.errorprone.matchers.Matchers.staticMethod;
+
+import android.annotation.SuppressLint;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.TreeScanner;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ClassType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+
+/**
+ * Inspects both the client and server side of AIDL interfaces to ensure that
+ * any {@code RequiresPermission} annotations are consistently declared and
+ * enforced.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "AndroidFrameworkRequiresPermission",
+ summary = "Verifies that @RequiresPermission annotations are consistent across AIDL",
+ severity = WARNING)
+public final class RequiresPermissionChecker extends BugChecker implements MethodTreeMatcher {
+ private static final String ANNOTATION_REQUIRES_PERMISSION = "RequiresPermission";
+
+ private static final Matcher<ExpressionTree> ENFORCE_VIA_CONTEXT = methodInvocation(
+ instanceMethod()
+ .onDescendantOf("android.content.Context")
+ .withNameMatching(
+ Pattern.compile("^(enforce|check)(Calling)?(OrSelf)?Permission$")));
+ private static final Matcher<ExpressionTree> ENFORCE_VIA_CHECKER = methodInvocation(
+ staticMethod()
+ .onClass("android.content.PermissionChecker")
+ .withNameMatching(Pattern.compile("^check.*")));
+
+ private static final Matcher<MethodTree> BINDER_INTERNALS = allOf(
+ enclosingClass(isSubtypeOf("android.os.IInterface")),
+ anyOf(
+ methodIsNamed("onTransact"),
+ methodIsNamed("dump"),
+ enclosingClass(simpleNameMatches(Pattern.compile("^(Stub|Default|Proxy)$")))));
+ private static final Matcher<MethodTree> LOCAL_INTERNALS = anyOf(
+ methodIsNamed("finalize"),
+ allOf(
+ enclosingClass(isSubtypeOf("android.content.BroadcastReceiver")),
+ methodIsNamed("onReceive")),
+ allOf(
+ enclosingClass(isSubtypeOf("android.database.ContentObserver")),
+ methodIsNamed("onChange")),
+ allOf(
+ enclosingClass(isSubtypeOf("android.os.Handler")),
+ methodIsNamed("handleMessage")),
+ allOf(
+ enclosingClass(isSubtypeOf("android.os.IBinder.DeathRecipient")),
+ methodIsNamed("binderDied")));
+
+ private static final Matcher<ExpressionTree> CLEAR_CALL = methodInvocation(staticMethod()
+ .onClass("android.os.Binder").withSignature("clearCallingIdentity()"));
+ private static final Matcher<ExpressionTree> RESTORE_CALL = methodInvocation(staticMethod()
+ .onClass("android.os.Binder").withSignature("restoreCallingIdentity(long)"));
+
+ @Override
+ public Description matchMethod(MethodTree tree, VisitorState state) {
+ // Ignore methods without an implementation
+ if (tree.getBody() == null) return Description.NO_MATCH;
+
+ // Ignore certain types of Binder generated code
+ if (BINDER_INTERNALS.matches(tree, state)) return Description.NO_MATCH;
+
+ // Ignore known-local methods which don't need to propagate
+ if (LOCAL_INTERNALS.matches(tree, state)) return Description.NO_MATCH;
+
+ // Ignore when suppressed via superclass
+ final MethodSymbol method = ASTHelpers.getSymbol(tree);
+ if (isSuppressedRecursively(method, state)) return Description.NO_MATCH;
+
+ // First, look at all outgoing method invocations to ensure that we
+ // carry those annotations forward; yell if we're too narrow
+ final ParsedRequiresPermission expectedPerm = parseRequiresPermissionRecursively(
+ method, state);
+ final ParsedRequiresPermission actualPerm = new ParsedRequiresPermission();
+ final Description desc = tree.accept(new TreeScanner<Description, Void>() {
+ private boolean clearedCallingIdentity = false;
+
+ @Override
+ public Description visitMethodInvocation(MethodInvocationTree node, Void param) {
+ if (CLEAR_CALL.matches(node, state)) {
+ clearedCallingIdentity = true;
+ } else if (RESTORE_CALL.matches(node, state)) {
+ clearedCallingIdentity = false;
+ } else if (!clearedCallingIdentity) {
+ final ParsedRequiresPermission nodePerm = parseRequiresPermissionRecursively(
+ node, state);
+ if (!expectedPerm.containsAll(nodePerm)) {
+ return buildDescription(node).setMessage("Annotated " + expectedPerm
+ + " but too narrow; invokes method requiring " + nodePerm).build();
+ } else {
+ actualPerm.addAll(nodePerm);
+ }
+ }
+ return super.visitMethodInvocation(node, param);
+ }
+
+ @Override
+ public Description reduce(Description r1, Description r2) {
+ return (r1 != null) ? r1 : r2;
+ }
+ }, null);
+ if (desc != null) return desc;
+
+ // Second, determine if we actually used all permissions that we claim
+ // to require; yell if we're too broad
+ if (!actualPerm.containsAll(expectedPerm)) {
+ return buildDescription(tree).setMessage("Annotated " + expectedPerm
+ + " but too wide; only invokes methods requiring " + actualPerm).build();
+ }
+
+ return Description.NO_MATCH;
+ }
+
+ static class ParsedRequiresPermission {
+ final Set<String> allOf = new HashSet<>();
+ final Set<String> anyOf = new HashSet<>();
+
+ public boolean isEmpty() {
+ return allOf.isEmpty() && anyOf.isEmpty();
+ }
+
+ /**
+ * Validate that this annotation effectively "contains" the given
+ * annotation. This is typically used to ensure that a method carries
+ * along all relevant annotations for the methods it invokes.
+ */
+ public boolean containsAll(ParsedRequiresPermission perm) {
+ boolean allMet = allOf.containsAll(perm.allOf);
+ boolean anyMet = false;
+ if (perm.anyOf.isEmpty()) {
+ anyMet = true;
+ } else {
+ for (String anyPerm : perm.anyOf) {
+ if (allOf.contains(anyPerm) || anyOf.contains(anyPerm)) {
+ anyMet = true;
+ }
+ }
+ }
+ return allMet && anyMet;
+ }
+
+ @Override
+ public String toString() {
+ if (isEmpty()) {
+ return "[none]";
+ }
+ String res = "{allOf=" + allOf;
+ if (!anyOf.isEmpty()) {
+ res += " anyOf=" + anyOf;
+ }
+ res += "}";
+ return res;
+ }
+
+ public void addAll(ParsedRequiresPermission perm) {
+ this.allOf.addAll(perm.allOf);
+ this.anyOf.addAll(perm.anyOf);
+ }
+
+ public void addAll(AnnotationMirror a) {
+ for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : a
+ .getElementValues().entrySet()) {
+ if (entry.getKey().getSimpleName().contentEquals("value")) {
+ maybeAdd(allOf, entry.getValue());
+ } else if (entry.getKey().getSimpleName().contentEquals("allOf")) {
+ maybeAdd(allOf, entry.getValue());
+ } else if (entry.getKey().getSimpleName().contentEquals("anyOf")) {
+ maybeAdd(anyOf, entry.getValue());
+ }
+ }
+ }
+
+ private static void maybeAdd(Set<String> set, Object value) {
+ if (value instanceof AnnotationValue) {
+ maybeAdd(set, ((AnnotationValue) value).getValue());
+ } else if (value instanceof String) {
+ set.add((String) value);
+ } else if (value instanceof Collection) {
+ for (Object o : (Collection) value) {
+ maybeAdd(set, o);
+ }
+ } else {
+ throw new RuntimeException(String.valueOf(value.getClass()));
+ }
+ }
+ }
+
+ private static ParsedRequiresPermission parseRequiresPermissionRecursively(
+ MethodInvocationTree tree, VisitorState state) {
+ if (ENFORCE_VIA_CONTEXT.matches(tree, state)) {
+ final ParsedRequiresPermission res = new ParsedRequiresPermission();
+ res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(0))));
+ return res;
+ } else if (ENFORCE_VIA_CHECKER.matches(tree, state)) {
+ final ParsedRequiresPermission res = new ParsedRequiresPermission();
+ res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(1))));
+ return res;
+ } else {
+ final MethodSymbol method = ASTHelpers.getSymbol(tree);
+ return parseRequiresPermissionRecursively(method, state);
+ }
+ }
+
+ /**
+ * Parse any {@code RequiresPermission} annotations associated with the
+ * given method, defined either directly on the method or by any superclass.
+ */
+ private static ParsedRequiresPermission parseRequiresPermissionRecursively(
+ MethodSymbol method, VisitorState state) {
+ final List<MethodSymbol> symbols = new ArrayList<>();
+ symbols.add(method);
+ symbols.addAll(ASTHelpers.findSuperMethods(method, state.getTypes()));
+
+ final ParsedRequiresPermission res = new ParsedRequiresPermission();
+ for (MethodSymbol symbol : symbols) {
+ for (AnnotationMirror a : symbol.getAnnotationMirrors()) {
+ if (a.getAnnotationType().asElement().getSimpleName()
+ .contentEquals(ANNOTATION_REQUIRES_PERMISSION)) {
+ res.addAll(a);
+ }
+ }
+ }
+ return res;
+ }
+
+ private boolean isSuppressedRecursively(MethodSymbol method, VisitorState state) {
+ // Is method suppressed anywhere?
+ if (isSuppressed(method)) return true;
+ for (MethodSymbol symbol : ASTHelpers.findSuperMethods(method, state.getTypes())) {
+ if (isSuppressed(symbol)) return true;
+ }
+
+ // Is class suppressed anywhere?
+ final ClassSymbol clazz = ASTHelpers.enclosingClass(method);
+ if (isSuppressed(clazz)) return true;
+ Type type = clazz.getSuperclass();
+ while (type != null) {
+ if (isSuppressed(type.tsym)) return true;
+ if (type instanceof ClassType) {
+ type = ((ClassType) type).supertype_field;
+ } else {
+ type = null;
+ }
+ }
+ return false;
+ }
+
+ public boolean isSuppressed(Symbol symbol) {
+ return isSuppressed(ASTHelpers.getAnnotation(symbol, SuppressWarnings.class))
+ || isSuppressed(ASTHelpers.getAnnotation(symbol, SuppressLint.class));
+ }
+
+ private boolean isSuppressed(SuppressWarnings anno) {
+ return (anno != null) && !Collections.disjoint(Arrays.asList(anno.value()), allNames());
+ }
+
+ private boolean isSuppressed(SuppressLint anno) {
+ return (anno != null) && !Collections.disjoint(Arrays.asList(anno.value()), allNames());
+ }
+
+ private static Matcher<ClassTree> simpleNameMatches(Pattern pattern) {
+ return new Matcher<ClassTree>() {
+ @Override
+ public boolean matches(ClassTree tree, VisitorState state) {
+ final CharSequence name = tree.getSimpleName().toString();
+ return pattern.matcher(name).matches();
+ }
+ };
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
new file mode 100644
index 000000000000..771258d7d265
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2020 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.google.errorprone.bugpatterns.android;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.errorprone.CompilationTestHelper;
+import com.google.errorprone.bugpatterns.android.RequiresPermissionChecker.ParsedRequiresPermission;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(JUnit4.class)
+public class RequiresPermissionCheckerTest {
+ private CompilationTestHelper compilationHelper;
+
+ private static final String RED = "red";
+ private static final String BLUE = "blue";
+
+ @Before
+ public void setUp() {
+ compilationHelper = CompilationTestHelper.newInstance(
+ RequiresPermissionChecker.class, getClass());
+ }
+
+ private static ParsedRequiresPermission build(Collection<String> allOf,
+ Collection<String> anyOf) {
+ ParsedRequiresPermission res = new ParsedRequiresPermission();
+ res.allOf.addAll(allOf);
+ res.anyOf.addAll(anyOf);
+ return res;
+ }
+
+ @Test
+ public void testParser_AllOf() {
+ final ParsedRequiresPermission a = build(Arrays.asList(RED, BLUE), Arrays.asList());
+ final ParsedRequiresPermission b = build(Arrays.asList(RED), Arrays.asList());
+ assertTrue(a.containsAll(b));
+ assertFalse(b.containsAll(a));
+ }
+
+ @Test
+ public void testParser_AnyOf() {
+ final ParsedRequiresPermission a = build(Arrays.asList(), Arrays.asList(RED, BLUE));
+ final ParsedRequiresPermission b = build(Arrays.asList(), Arrays.asList(RED));
+ assertTrue(a.containsAll(b));
+ assertTrue(b.containsAll(a));
+ }
+
+ @Test
+ public void testParser_AnyOf_AllOf() {
+ final ParsedRequiresPermission a = build(Arrays.asList(RED, BLUE), Arrays.asList());
+ final ParsedRequiresPermission b = build(Arrays.asList(), Arrays.asList(RED));
+ assertTrue(a.containsAll(b));
+ assertFalse(b.containsAll(a));
+ }
+
+ @Test
+ public void testSimple() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.content.Context;",
+ "public abstract class ColorManager extends Context {",
+ " private static final String RED = \"red\";",
+ " private static final String BLUE = \"blue\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " @RequiresPermission(BLUE) abstract int blue();",
+ " @RequiresPermission(allOf={RED, BLUE}) abstract int all();",
+ " @RequiresPermission(anyOf={RED, BLUE}) abstract int any();",
+ " @RequiresPermission(allOf={RED, BLUE})",
+ " int redPlusBlue() { return red() + blue(); }",
+ " @RequiresPermission(allOf={RED, BLUE})",
+ " int allPlusRed() { return all() + red(); }",
+ " @RequiresPermission(allOf={RED})",
+ " int anyPlusRed() { return any() + red(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testManager() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/foo/IColorService.java")
+ .addSourceFile("/android/os/IInterface.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.foo.IColorService;",
+ "public class ColorManager {",
+ " IColorService mService;",
+ " @RequiresPermission(IColorService.RED)",
+ " void redValid() {",
+ " mService.red();",
+ " }",
+ " @RequiresPermission(allOf={IColorService.RED, IColorService.BLUE})",
+ " // BUG: Diagnostic contains:",
+ " void redOverbroad() {",
+ " mService.red();",
+ " }",
+ " @RequiresPermission(IColorService.BLUE)",
+ " void redInvalid() {",
+ " // BUG: Diagnostic contains:",
+ " mService.red();",
+ " }",
+ " void redMissing() {",
+ " // BUG: Diagnostic contains:",
+ " mService.red();",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testService() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/foo/IColorService.java")
+ .addSourceFile("/android/os/IInterface.java")
+ .addSourceLines("ColorService.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.content.Context;",
+ "import android.foo.IColorService;",
+ "class ColorService extends Context implements IColorService {",
+ " public void none() {}",
+ " // BUG: Diagnostic contains:",
+ " public void red() {}",
+ " // BUG: Diagnostic contains:",
+ " public void redAndBlue() {}",
+ " // BUG: Diagnostic contains:",
+ " public void redOrBlue() {}",
+ " void onTransact(int code) {",
+ " red();",
+ " }",
+ "}",
+ "class ValidService extends ColorService {",
+ " public void red() {",
+ " ((Context) this).enforceCallingOrSelfPermission(RED, null);",
+ " }",
+ "}",
+ "class InvalidService extends ColorService {",
+ " public void red() {",
+ " // BUG: Diagnostic contains:",
+ " ((Context) this).enforceCallingOrSelfPermission(BLUE, null);",
+ " }",
+ "}",
+ "class NestedService extends ColorService {",
+ " public void red() {",
+ " enforceRed();",
+ " }",
+ " @RequiresPermission(RED)",
+ " public void enforceRed() {",
+ " ((Context) this).enforceCallingOrSelfPermission(RED, null);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testBroadcastReceiver() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/content/BroadcastReceiver.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.content.BroadcastReceiver;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public abstract class ColorManager extends BroadcastReceiver {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " // BUG: Diagnostic contains:",
+ " public void onSend() { red(); }",
+ " public void onReceive(Context context, Intent intent) { red(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ @Ignore
+ public void testContentObserver() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/database/ContentObserver.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.database.ContentObserver;",
+ "public abstract class ColorManager {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " public void example() {",
+ " ContentObserver ob = new ContentObserver() {",
+ " public void onChange(boolean selfChange) {",
+ " red();",
+ " }",
+ " };",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testHandler() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/os/Handler.java")
+ .addSourceFile("/android/os/Message.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.os.Handler;",
+ "import android.os.Message;",
+ "public abstract class ColorManager extends Handler {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " // BUG: Diagnostic contains:",
+ " public void sendMessage() { red(); }",
+ " public void handleMessage(Message msg) { red(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testDeathRecipient() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/os/IBinder.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.os.IBinder;",
+ "public abstract class ColorManager implements IBinder.DeathRecipient {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " // BUG: Diagnostic contains:",
+ " public void binderAlive() { red(); }",
+ " public void binderDied() { red(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testClearCallingIdentity() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/os/Binder.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.os.Binder;",
+ "public abstract class ColorManager {",
+ " private static final String RED = \"red\";",
+ " private static final String BLUE = \"blue\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " @RequiresPermission(BLUE) abstract int blue();",
+ " @RequiresPermission(BLUE)",
+ " public void half() {",
+ " final long token = Binder.clearCallingIdentity();",
+ " try {",
+ " red();",
+ " } finally {",
+ " Binder.restoreCallingIdentity(token);",
+ " }",
+ " blue();",
+ " }",
+ " public void full() {",
+ " final long token = Binder.clearCallingIdentity();",
+ " red();",
+ " blue();",
+ " }",
+ " @RequiresPermission(allOf={RED, BLUE})",
+ " public void none() {",
+ " red();",
+ " blue();",
+ " final long token = Binder.clearCallingIdentity();",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testSuppressLint() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/annotation/SuppressLint.java")
+ .addSourceLines("Example.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.annotation.SuppressLint;",
+ "@SuppressLint(\"AndroidFrameworkRequiresPermission\")",
+ "abstract class Parent {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ "}",
+ "abstract class Child extends Parent {",
+ " private static final String BLUE = \"blue\";",
+ " @RequiresPermission(BLUE) abstract int blue();",
+ " public void toParent() { red(); }",
+ " public void toSibling() { blue(); }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/errorprone/tests/res/android/annotation/RequiresPermission.java b/errorprone/tests/res/android/annotation/RequiresPermission.java
new file mode 100644
index 000000000000..670eb3b619ce
--- /dev/null
+++ b/errorprone/tests/res/android/annotation/RequiresPermission.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 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 android.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
+public @interface RequiresPermission {
+ String value() default "";
+ String[] allOf() default {};
+ String[] anyOf() default {};
+}
diff --git a/errorprone/tests/res/android/annotation/SuppressLint.java b/errorprone/tests/res/android/annotation/SuppressLint.java
new file mode 100644
index 000000000000..4150c478cc69
--- /dev/null
+++ b/errorprone/tests/res/android/annotation/SuppressLint.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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 android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+ String[] value();
+}
diff --git a/errorprone/tests/res/android/content/BroadcastReceiver.java b/errorprone/tests/res/android/content/BroadcastReceiver.java
new file mode 100644
index 000000000000..9d066b768015
--- /dev/null
+++ b/errorprone/tests/res/android/content/BroadcastReceiver.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 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 android.content;
+
+public class BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/errorprone/tests/res/android/content/Context.java b/errorprone/tests/res/android/content/Context.java
index 7ba3fbb56245..323b8dd46e8f 100644
--- a/errorprone/tests/res/android/content/Context.java
+++ b/errorprone/tests/res/android/content/Context.java
@@ -20,4 +20,7 @@ public class Context {
public int getUserId() {
return 0;
}
+
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ }
}
diff --git a/errorprone/tests/res/android/database/ContentObserver.java b/errorprone/tests/res/android/database/ContentObserver.java
new file mode 100644
index 000000000000..4c73a10cad26
--- /dev/null
+++ b/errorprone/tests/res/android/database/ContentObserver.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 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 android.database;
+
+public abstract class ContentObserver {
+ public void onChange(boolean selfChange) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/errorprone/tests/res/android/foo/IColorService.java b/errorprone/tests/res/android/foo/IColorService.java
new file mode 100644
index 000000000000..20c8e95832e0
--- /dev/null
+++ b/errorprone/tests/res/android/foo/IColorService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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 android.foo;
+
+import android.annotation.RequiresPermission;
+
+public interface IColorService extends android.os.IInterface {
+ public static final String RED = "red";
+ public static final String BLUE = "blue";
+
+ public void none();
+ @RequiresPermission(RED)
+ public void red();
+ @RequiresPermission(allOf = { RED, BLUE })
+ public void redAndBlue();
+ @RequiresPermission(anyOf = { RED, BLUE })
+ public void redOrBlue();
+}
diff --git a/errorprone/tests/res/android/os/Handler.java b/errorprone/tests/res/android/os/Handler.java
new file mode 100644
index 000000000000..f001896cc497
--- /dev/null
+++ b/errorprone/tests/res/android/os/Handler.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 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 android.os;
+
+public class Handler {
+ public void handleMessage(Message msg) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/errorprone/tests/res/android/os/IBinder.java b/errorprone/tests/res/android/os/IBinder.java
new file mode 100644
index 000000000000..214a396d4fde
--- /dev/null
+++ b/errorprone/tests/res/android/os/IBinder.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 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 android.os;
+
+public interface IBinder {
+ public interface DeathRecipient {
+ public void binderDied();
+ }
+}
diff --git a/errorprone/tests/res/android/os/Message.java b/errorprone/tests/res/android/os/Message.java
new file mode 100644
index 000000000000..2421263969e9
--- /dev/null
+++ b/errorprone/tests/res/android/os/Message.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 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 android.os;
+
+public class Message {
+}