diff options
Diffstat (limited to 'errorprone/java')
| -rw-r--r-- | errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java new file mode 100644 index 000000000000..7f2cce6ea7f0 --- /dev/null +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java @@ -0,0 +1,100 @@ +/* + * 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 com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.matchers.Matchers.enclosingClass; +import static com.google.errorprone.matchers.Matchers.hasAnnotation; +import static com.google.errorprone.matchers.Matchers.instanceMethod; +import static com.google.errorprone.matchers.Matchers.methodInvocation; + +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.MethodInvocationTreeMatcher; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.Symbol.VarSymbol; + +import java.util.List; +import java.util.Locale; +import java.util.function.Predicate; + +/** + * To avoid an explosion of {@code startActivityForUser} style methods, we've + * converged on recommending the use of {@code Context.createContextAsUser()}, + * and then ensuring that all system services pass {@link Context.getUserId()} + * for any {@code int userId} arguments across Binder interfaces. + * <p> + * This design allows developers to easily redirect all services obtained from a + * specific {@code Context} to a different user with no additional API surface. + */ +@AutoService(BugChecker.class) +@BugPattern( + name = "AndroidFrameworkContextUserId", + summary = "Verifies that system_server calls use Context.getUserId()", + severity = WARNING) +public final class ContextUserIdChecker extends BugChecker implements MethodInvocationTreeMatcher { + private static final Matcher<Tree> INSIDE_MANAGER = + enclosingClass(hasAnnotation("android.annotation.SystemService")); + + private static final Matcher<ExpressionTree> BINDER_CALL = methodInvocation( + instanceMethod().onDescendantOf("android.os.IInterface").withAnyName()); + private static final Matcher<ExpressionTree> GET_USER_ID_CALL = methodInvocation( + instanceMethod().onDescendantOf("android.content.Context").named("getUserId")); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + if (INSIDE_MANAGER.matches(tree, state) + && BINDER_CALL.matches(tree, state)) { + final List<VarSymbol> vars = ASTHelpers.getSymbol(tree).params(); + for (int i = 0; i < vars.size(); i++) { + if (USER_ID_VAR.test(vars.get(i)) && + !GET_USER_ID_CALL.matches(tree.getArguments().get(i), state)) { + return buildDescription(tree) + .setMessage("Must pass Context.getUserId() as user ID" + + "to enable createContextAsUser()") + .build(); + } + } + } + return Description.NO_MATCH; + } + + private static final UserIdMatcher USER_ID_VAR = new UserIdMatcher(); + + private static class UserIdMatcher implements Predicate<VarSymbol> { + @Override + public boolean test(VarSymbol t) { + if ("int".equals(t.type.toString())) { + switch (t.name.toString().toLowerCase(Locale.ROOT)) { + case "user": + case "userid": + case "userhandle": + case "user_id": + return true; + } + } + return false; + } + } +} |