1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.errorprone.bugpatterns.android;
18 
19 import static com.google.errorprone.BugPattern.LinkType.NONE;
20 import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
21 import static com.google.errorprone.matchers.Matchers.allOf;
22 import static com.google.errorprone.matchers.Matchers.contains;
23 import static com.google.errorprone.matchers.Matchers.enclosingClass;
24 import static com.google.errorprone.matchers.Matchers.hasAnnotation;
25 import static com.google.errorprone.matchers.Matchers.instanceMethod;
26 import static com.google.errorprone.matchers.Matchers.isSameType;
27 import static com.google.errorprone.matchers.Matchers.methodInvocation;
28 import static com.google.errorprone.matchers.Matchers.not;
29 import static com.google.errorprone.matchers.Matchers.throwStatement;
30 import static com.google.errorprone.matchers.Matchers.variableType;
31 
32 import com.google.auto.service.AutoService;
33 import com.google.errorprone.BugPattern;
34 import com.google.errorprone.VisitorState;
35 import com.google.errorprone.bugpatterns.BugChecker;
36 import com.google.errorprone.bugpatterns.BugChecker.TryTreeMatcher;
37 import com.google.errorprone.matchers.Description;
38 import com.google.errorprone.matchers.Matcher;
39 import com.google.errorprone.predicates.TypePredicate;
40 import com.sun.source.tree.CatchTree;
41 import com.sun.source.tree.ExpressionTree;
42 import com.sun.source.tree.StatementTree;
43 import com.sun.source.tree.Tree;
44 import com.sun.source.tree.TryTree;
45 import com.sun.source.tree.VariableTree;
46 import com.sun.tools.javac.code.Type;
47 
48 import java.util.List;
49 
50 /**
51  * Apps making calls into the system server may end up persisting internal state
52  * or making security decisions based on the perceived success or failure of a
53  * call, or any default values returned. For this reason, we want to strongly
54  * throw when there was trouble with the transaction.
55  * <p>
56  * The rethrowFromSystemServer() method is the best-practice way of doing this
57  * correctly, so that we don't clutter logs with misleading stack traces, and
58  * this checker verifies that best-practice is used.
59  */
60 @AutoService(BugChecker.class)
61 @BugPattern(
62     name = "AndroidFrameworkRethrowFromSystem",
63     summary = "Verifies that system_server calls use rethrowFromSystemServer()",
64     linkType = NONE,
65     severity = WARNING)
66 public final class RethrowFromSystemChecker extends BugChecker implements TryTreeMatcher {
67     private static final Matcher<Tree> INSIDE_MANAGER =
68             enclosingClass(hasAnnotation("android.annotation.SystemService"));
69 
70     // Purposefully exclude telephony Binder interfaces, since we know they
71     // always run under the separate AID_RADIO
72     private static final Matcher<ExpressionTree> SYSTEM_BINDER_CALL = methodInvocation(allOf(
73             instanceMethod().onDescendantOf("android.os.IInterface").withAnyName(),
74             not(instanceMethod().onClass(inPackage("com.android.internal.telephony"))),
75             not(instanceMethod().onClass(inPackage("com.android.internal.telecom")))));
76 
77     private static final Matcher<VariableTree> REMOTE_EXCEPTION = variableType(
78             isSameType("android.os.RemoteException"));
79     private static final Matcher<StatementTree> RETHROW_FROM_SYSTEM = throwStatement(
80             methodInvocation(instanceMethod().onExactClass("android.os.RemoteException")
81                     .named("rethrowFromSystemServer")));
82 
83     @Override
matchTry(TryTree tree, VisitorState state)84     public Description matchTry(TryTree tree, VisitorState state) {
85         if (INSIDE_MANAGER.matches(tree, state)
86                 && contains(ExpressionTree.class, SYSTEM_BINDER_CALL)
87                         .matches(tree.getBlock(), state)) {
88             for (CatchTree catchTree : tree.getCatches()) {
89                 if (REMOTE_EXCEPTION.matches(catchTree.getParameter(), state)) {
90                     final List<? extends StatementTree> statements = catchTree.getBlock()
91                             .getStatements();
92                     if (statements.size() != 1
93                             || !RETHROW_FROM_SYSTEM.matches(statements.get(0), state)) {
94                         return buildDescription(catchTree)
95                                 .setMessage("Must contain single "
96                                         + "'throw e.rethrowFromSystemServer()' statement")
97                                 .build();
98                     }
99                 }
100             }
101         }
102         return Description.NO_MATCH;
103     }
104 
inPackage(final String filter)105     private static TypePredicate inPackage(final String filter) {
106         return new TypePredicate() {
107             @Override
108             public boolean apply(Type type, VisitorState state) {
109                 return type.tsym.packge().fullname.toString().startsWith(filter);
110             }
111         };
112     }
113 }
114