1 /*
2  * Copyright (C) 2018 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.android.apifinder;
18 
19 import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
20 
21 import com.google.auto.service.AutoService;
22 import com.google.errorprone.BugPattern;
23 import com.google.errorprone.VisitorState;
24 import com.google.errorprone.bugpatterns.BugChecker;
25 import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
26 import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher;
27 import com.google.errorprone.matchers.Description;
28 import com.google.errorprone.util.ASTHelpers;
29 import com.sun.source.tree.MethodInvocationTree;
30 import com.sun.source.tree.NewClassTree;
31 import com.sun.tools.javac.code.Symbol;
32 import com.sun.tools.javac.code.Symbol.MethodSymbol;
33 import com.sun.tools.javac.code.Symbol.VarSymbol;
34 import java.util.ArrayList;
35 import java.util.List;
36 import javax.lang.model.element.ElementKind;
37 import javax.lang.model.element.Modifier;
38 
39 /** Bug checker to detect method or field used by a mainline module */
40 @AutoService(BugChecker.class)
41 @BugPattern(
42     name = "JavaApiUsedByMainlineModule",
43     summary = "Any public method used by a mainline module.",
44     severity = SUGGESTION)
45 public final class JavaApiUsedByMainlineModule extends BugChecker
46     implements MethodInvocationTreeMatcher, NewClassTreeMatcher {
47 
48     /*
49      * Checks if a method or class is private.
50      * A method is considered as private method when any of the following condition met.
51      *    1. Method is defined as private.
52      *    2. Method's class is defined as private.
53      *    3. Method's ancestor classes is defined as private.
54      */
isPrivate(Symbol sym)55     private boolean isPrivate(Symbol sym) {
56         Symbol tmpSym = sym;
57         while (tmpSym != null) {
58             if (!tmpSym.getModifiers().contains(Modifier.PUBLIC)) {
59                 return true;
60             }
61             tmpSym = ASTHelpers.enclosingClass(tmpSym);
62         }
63         return false;
64     }
65 
66     /*
67      * Constructs parameters. Only return parameter type.
68      * For example
69      *     (int, boolean, java.lang.String)
70      */
constructParameters(Symbol sym)71     private String constructParameters(Symbol sym) {
72         List<VarSymbol> paramsList = ((MethodSymbol) sym).getParameters();
73         List<StringBuilder> stringParamsList = new ArrayList();
74         for (VarSymbol paramSymbol : paramsList) {
75             StringBuilder tmpParam = new StringBuilder(paramSymbol.asType().toString());
76 
77             // Removes "<*>" in parameter type.
78             if (tmpParam.indexOf("<") != -1) {
79                 tmpParam = tmpParam.replace(
80                     tmpParam.indexOf("<"), tmpParam.lastIndexOf(">") + 1, "");
81             }
82 
83             stringParamsList.add(tmpParam);
84         }
85         return "(" + String.join(", ", stringParamsList) + ")";
86     }
87 
88     @Override
matchMethodInvocation(MethodInvocationTree tree, VisitorState state)89     public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
90         Symbol sym = ASTHelpers.getSymbol(tree);
91 
92         // Exclude private function.
93         if (isPrivate(sym)) {
94             return Description.NO_MATCH;
95         }
96 
97         // Constructs method name formatted as superClassName.className.methodName,
98         // using supperClassName.className.className for constructor
99         String methodName = sym.name.toString();
100         List<String> nameList = new ArrayList();
101         if (sym.getKind() == ElementKind.CONSTRUCTOR) {
102             Symbol classSymbol = ASTHelpers.enclosingClass(sym);
103             while (classSymbol != null) {
104                 nameList.add(0, classSymbol.name.toString());
105                 classSymbol = ASTHelpers.enclosingClass(classSymbol);
106             }
107             methodName = String.join(".", nameList);
108         }
109 
110         String params = constructParameters(sym);
111 
112         return buildDescription(tree)
113             .setMessage(
114                 String.format("%s.%s%s", ASTHelpers.enclosingClass(sym), methodName, params))
115             .build();
116     }
117 
118     @Override
matchNewClass(NewClassTree tree, VisitorState state)119     public Description matchNewClass(NewClassTree tree, VisitorState state) {
120         Symbol sym = ASTHelpers.getSymbol(tree);
121 
122         // Excludes private class.
123         if (isPrivate(sym)) {
124             return Description.NO_MATCH;
125         }
126 
127         String params = constructParameters(sym);
128 
129         // Constructs constructor name.
130         Symbol tmpSymbol = ASTHelpers.enclosingClass(sym);
131         List<String> nameList = new ArrayList();
132         while (tmpSymbol != null) {
133             nameList.add(0, tmpSymbol.name.toString());
134             tmpSymbol = ASTHelpers.enclosingClass(tmpSymbol);
135         }
136         String constructorName = String.join(".", nameList);
137 
138         return buildDescription(tree)
139             .setMessage(
140                 String.format(
141                     "%s.%s%s", ASTHelpers.enclosingClass(sym), constructorName, params))
142             .build();
143     }
144 }
145 
146