diff options
author | Jeff Sharkey <jsharkey@android.com> | 2021-04-08 23:29:40 -0600 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2021-04-14 17:23:01 -0600 |
commit | 71463a4cb133c1344ab0921561e11b7d5565e617 (patch) | |
tree | 99ae33ffd9965de6ad642907968bd0c682fd7dc4 | |
parent | 4d139ff936d44680d92c56f5b0f8cf338d6d83a8 (diff) |
Error Prone for RequiresPermission across AIDL.
We've had @RequiresPermission annotations across public APIs for many
years, but we've never built out the tooling to validate that the
service implementations actually enforced those permissions.
This change adds an Error Prone checker that does bi-directional
validation of these annotations, confirming that AIDL implementations
enforce the permissions, and that AIDL callers carry those
annotations through any indirect call-paths.
Currently, enforcement validation is best-effort, since it assumes
that any enforcement referencing the annotated permissions is enough
to pass; it doesn't attempt any code flow analysis. It also doesn't
understand concepts like Binder.clearCallingIdentity().
To begin using this checker, simply begin annotating your AIDL files
using a strategy like this:
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
void aidlMethod();
Bug: 183626724
Test: atest error_prone_android_framework_test:RequiresPermissionCheckerTest
Change-Id: I26a872f07ab13931c241cbb02ff7228edf7dc3b9
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 { +} |