• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.errorprone.bugpatterns;
2 
3 import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
4 import static com.google.errorprone.matchers.Matchers.hasAnnotation;
5 
6 import com.google.errorprone.BugPattern;
7 import com.google.errorprone.BugPattern.StandardTags;
8 import com.google.errorprone.VisitorState;
9 import com.google.errorprone.bugpatterns.BugChecker;
10 import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
11 import com.google.errorprone.fixes.SuggestedFix;
12 import com.google.errorprone.fixes.SuggestedFixes;
13 import com.google.errorprone.matchers.Description;
14 import com.google.errorprone.matchers.Matcher;
15 import com.google.errorprone.util.ASTHelpers;
16 import com.sun.source.doctree.DocCommentTree;
17 import com.sun.source.doctree.EndElementTree;
18 import com.sun.source.doctree.ReferenceTree;
19 import com.sun.source.doctree.StartElementTree;
20 import com.sun.source.doctree.TextTree;
21 import com.sun.source.tree.AnnotationTree;
22 import com.sun.source.tree.ClassTree;
23 import com.sun.source.tree.CompilationUnitTree;
24 import com.sun.source.tree.ExpressionTree;
25 import com.sun.source.tree.IdentifierTree;
26 import com.sun.source.tree.MethodTree;
27 import com.sun.source.tree.ModifiersTree;
28 import com.sun.source.util.DocSourcePositions;
29 import com.sun.source.util.DocTreePath;
30 import com.sun.source.util.DocTreePathScanner;
31 import com.sun.source.util.TreePathScanner;
32 import com.sun.tools.javac.api.JavacTrees;
33 import com.sun.tools.javac.code.Symbol;
34 import com.sun.tools.javac.tree.DCTree.DCDocComment;
35 import com.sun.tools.javac.tree.DCTree.DCReference;
36 import com.sun.tools.javac.tree.JCTree.JCAssign;
37 import com.sun.tools.javac.tree.JCTree.JCIdent;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Optional;
41 import java.util.Set;
42 import javax.lang.model.element.Modifier;
43 import org.robolectric.annotation.Implementation;
44 import org.robolectric.annotation.Implements;
45 
46 /**
47  * Ensure Robolectric shadow's method marked with {@code @Implementation} is protected
48  *
49  * @author christianw@google.com (Christian Williams)
50  */
51 @BugPattern(
52     summary = "Robolectric @Implementation methods should be protected.",
53     severity = SUGGESTION,
54     documentSuppression = false,
55     tags = StandardTags.REFACTORING)
56 public final class RobolectricShadow extends BugChecker implements ClassTreeMatcher {
57   private static final Matcher<ClassTree> implementsClassMatcher = hasAnnotation(Implements.class);
58 
59   private static final Matcher<MethodTree> implementationMethodMatcher =
60       hasAnnotation(Implementation.class);
61 
62   private final boolean doScanJavadoc = false;
63 
64   @Override
matchClass(ClassTree classTree, VisitorState state)65   public Description matchClass(ClassTree classTree, VisitorState state) {
66     List<Optional<SuggestedFix>> fixes = new ArrayList<>();
67 
68     if (implementsClassMatcher.matches(classTree, state)) {
69       boolean inSdk = true;
70 
71       JavacTrees trees = JavacTrees.instance(state.context);
72       for (AnnotationTree annotationTree : classTree.getModifiers().getAnnotations()) {
73         JCIdent ident = (JCIdent) annotationTree.getAnnotationType();
74         String annotationClassName = ident.sym.getQualifiedName().toString();
75         if ("org.robolectric.annotation.Implements".equals(annotationClassName)) {
76           for (ExpressionTree expressionTree : annotationTree.getArguments()) {
77             JCAssign jcAnnotation = (JCAssign) expressionTree;
78             if ("isInAndroidSdk".equals(state.getSourceForNode(jcAnnotation.lhs))
79                 && "false".equals(state.getSourceForNode(jcAnnotation.rhs))) {
80               // shadows of classes not in the public Android SDK can keep their public methods.
81               inSdk = false;
82             }
83           }
84         }
85       }
86 
87       if (inSdk) {
88         new ImplementationMethodScanner(state, fixes, trees).scan(state.getPath(), null);
89       }
90     }
91 
92     SuggestedFix.Builder builder = SuggestedFix.builder();
93     for (Optional<SuggestedFix> fix : fixes) {
94       fix.ifPresent(builder::merge);
95     }
96 
97     if (builder.isEmpty()) {
98       return Description.NO_MATCH;
99     } else {
100       return describeMatch(classTree, builder.build());
101     }
102   }
103 
104   static final class DocTreeSymbolScanner extends DocTreePathScanner<Void, Void> {
105     private final JavacTrees trees;
106     private final List<Optional<SuggestedFix>> fixes;
107 
DocTreeSymbolScanner(JavacTrees trees, List<Optional<SuggestedFix>> fixes)108     DocTreeSymbolScanner(JavacTrees trees, List<Optional<SuggestedFix>> fixes) {
109       this.trees = trees;
110       this.fixes = fixes;
111     }
112 
113     @Override
visitStartElement(StartElementTree startElementTree, Void aVoid)114     public Void visitStartElement(StartElementTree startElementTree, Void aVoid) {
115       if (startElementTree.getName().toString().equalsIgnoreCase("p")) {
116         DocTreePath path = getCurrentPath();
117         DCDocComment doc = (DCDocComment) path.getDocComment();
118         DocSourcePositions positions = trees.getSourcePositions();
119         CompilationUnitTree compilationUnitTree = path.getTreePath().getCompilationUnit();
120         int start = (int) positions.getStartPosition(compilationUnitTree, doc, startElementTree);
121         int end = (int) positions.getEndPosition(compilationUnitTree, doc, startElementTree);
122 
123         fixes.add(Optional.of(SuggestedFix.replace(start, end, "")));
124       }
125       return super.visitStartElement(startElementTree, aVoid);
126     }
127 
128     @Override
visitEndElement(EndElementTree endElementTree, Void aVoid)129     public Void visitEndElement(EndElementTree endElementTree, Void aVoid) {
130       return super.visitEndElement(endElementTree, aVoid);
131     }
132 
133     @Override
visitText(TextTree textTree, Void aVoid)134     public Void visitText(TextTree textTree, Void aVoid) {
135       System.out.println("textTree = " + textTree);
136       return super.visitText(textTree, aVoid);
137     }
138 
139     @Override
visitReference(ReferenceTree referenceTree, Void sink)140     public Void visitReference(ReferenceTree referenceTree, Void sink) {
141       // do this first, it attributes the referenceTree as a side-effect
142       trees.getElement(getCurrentPath());
143       com.sun.source.util.TreeScanner<Void, Void> nonRecursiveScanner =
144           new com.sun.source.util.TreeScanner<Void, Void>() {
145             @Override
146             public Void visitIdentifier(IdentifierTree tree, Void sink) {
147               Symbol sym = ASTHelpers.getSymbol(tree);
148               if (sym != null) {
149                 System.out.println("sym = " + sym);
150               }
151               return null;
152             }
153           };
154       DCReference reference = (DCReference) referenceTree;
155       nonRecursiveScanner.scan(reference.qualifierExpression, sink);
156       nonRecursiveScanner.scan(reference.paramTypes, sink);
157       return null;
158     }
159   }
160 
161   private class ImplementationMethodScanner extends TreePathScanner<Void, Void> {
162 
163     private final com.google.errorprone.VisitorState state;
164     private final List<Optional<SuggestedFix>> fixes;
165     private final JavacTrees trees;
166 
ImplementationMethodScanner( com.google.errorprone.VisitorState state, List<Optional<SuggestedFix>> fixes, JavacTrees trees)167     ImplementationMethodScanner(
168         com.google.errorprone.VisitorState state,
169         List<Optional<SuggestedFix>> fixes,
170         JavacTrees trees) {
171       this.state = state;
172       this.fixes = fixes;
173       this.trees = trees;
174     }
175 
176     @Override
visitMethod(MethodTree methodTree, Void aVoid)177     public Void visitMethod(MethodTree methodTree, Void aVoid) {
178       if (implementationMethodMatcher.matches(methodTree, state)) {
179         processImplementationMethod(methodTree);
180       }
181       return super.visitMethod(methodTree, aVoid);
182     }
183 
processImplementationMethod(MethodTree methodTree)184     private void processImplementationMethod(MethodTree methodTree) {
185       String methodName = methodTree.getName().toString();
186       if ("toString".equals(methodName)
187           || "equals".equals(methodName)
188           || "hashCode".equals(methodName)) {
189         return; // they need to remain public
190       }
191       ModifiersTree modifiersTree = methodTree.getModifiers();
192       for (AnnotationTree annotationTree : modifiersTree.getAnnotations()) {
193         JCIdent ident = (JCIdent) annotationTree.getAnnotationType();
194         String annotationClassName = ident.sym.getQualifiedName().toString();
195         if ("java.lang.Override".equals(annotationClassName)) {
196           // can't have more restrictive permissions than the overridden method.
197           return;
198         }
199         if ("org.robolectric.annotation.HiddenApi".equals(annotationClassName)) {
200           // @HiddenApi implementation methods can stay public for the convenience of tests.
201           return;
202         }
203       }
204 
205       Set<Modifier> modifiers = modifiersTree.getFlags();
206       if (!modifiers.contains(Modifier.PROTECTED)) {
207         fixes.add(
208             SuggestedFixes.removeModifiers(methodTree, state, Modifier.PUBLIC, Modifier.PRIVATE));
209         fixes.add(SuggestedFixes.addModifiers(methodTree, state, Modifier.PROTECTED));
210       }
211 
212       if (doScanJavadoc) {
213         scanJavadoc();
214       }
215     }
216 
scanJavadoc()217     private void scanJavadoc() {
218       DocCommentTree commentTree = trees.getDocCommentTree(getCurrentPath());
219       if (commentTree != null) {
220         DocTreePath docTrees = new DocTreePath(getCurrentPath(), commentTree);
221         new DocTreeSymbolScanner(trees, fixes).scan(docTrees, null);
222       }
223     }
224   }
225 }
226