1919import static com .google .errorprone .BugPattern .SeverityLevel .WARNING ;
2020import static com .google .errorprone .fixes .SuggestedFixes .removeModifiers ;
2121import static com .google .errorprone .matchers .Description .NO_MATCH ;
22+ import static com .google .errorprone .util .ASTHelpers .enclosingClass ;
2223import static com .google .errorprone .util .ASTHelpers .getSymbol ;
2324import static com .google .errorprone .util .ASTHelpers .hasAnnotation ;
2425import static com .google .errorprone .util .ASTHelpers .isEffectivelyPrivate ;
2728import com .google .common .collect .ImmutableSet ;
2829import com .google .errorprone .BugPattern ;
2930import com .google .errorprone .VisitorState ;
30- import com .google .errorprone .bugpatterns .BugChecker .ClassTreeMatcher ;
31- import com .google .errorprone .bugpatterns .BugChecker .MethodTreeMatcher ;
32- import com .google .errorprone .bugpatterns .BugChecker .VariableTreeMatcher ;
33- import com .google .errorprone .fixes .SuggestedFix ;
31+ import com .google .errorprone .bugpatterns .BugChecker .CompilationUnitTreeMatcher ;
3432import com .google .errorprone .matchers .Description ;
3533import com .sun .source .tree .ClassTree ;
34+ import com .sun .source .tree .CompilationUnitTree ;
3635import com .sun .source .tree .MethodTree ;
3736import com .sun .source .tree .ModifiersTree ;
3837import com .sun .source .tree .Tree ;
3938import com .sun .source .tree .VariableTree ;
39+ import com .sun .source .util .TreePathScanner ;
4040import com .sun .tools .javac .code .Symbol ;
41+ import com .sun .tools .javac .code .Symbol .ClassSymbol ;
4142import com .sun .tools .javac .code .Symbol .MethodSymbol ;
4243import com .sun .tools .javac .code .Symbol .VarSymbol ;
44+ import com .sun .tools .javac .code .Type ;
4345import java .util .Collections ;
44- import java .util .Optional ;
4546import javax .inject .Inject ;
4647import javax .lang .model .element .Modifier ;
4748
4849/** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */
4950@ BugPattern (
5051 summary = "This declaration has public or protected modifiers, but is effectively private." ,
5152 severity = WARNING )
52- public final class EffectivelyPrivate extends BugChecker
53- implements MethodTreeMatcher , VariableTreeMatcher , ClassTreeMatcher {
54-
53+ public final class EffectivelyPrivate extends BugChecker implements CompilationUnitTreeMatcher {
5554 private final WellKnownKeep wellKnownKeep ;
5655
5756 @ Inject
@@ -60,55 +59,89 @@ public final class EffectivelyPrivate extends BugChecker
6059 }
6160
6261 @ Override
63- public Description matchVariable (VariableTree tree , VisitorState state ) {
64- VarSymbol sym = getSymbol (tree );
65- if (!sym .getKind ().isField ()) {
66- return NO_MATCH ;
67- }
68- return match (tree , tree .getModifiers (), state );
69- }
62+ public Description matchCompilationUnit (CompilationUnitTree tree , VisitorState state ) {
63+ ImmutableSet <ClassSymbol > hasVisibleSubclass = findClassesWithVisibleSubclasses (tree , state );
64+ new SuppressibleTreePathScanner <Void , Void >(state ) {
65+ @ Override
66+ public Void visitVariable (VariableTree tree , Void unused ) {
67+ VarSymbol sym = getSymbol (tree );
68+ if (sym .getKind ().isField ()) {
69+ match (tree , tree .getModifiers (), hasVisibleSubclass , state );
70+ }
71+ return super .visitVariable (tree , null );
72+ }
7073
71- @ Override
72- public Description matchClass (ClassTree tree , VisitorState state ) {
73- return match (tree , tree .getModifiers (), state );
74- }
74+ @ Override
75+ public Void visitClass (ClassTree tree , Void unused ) {
76+ match (tree , tree .getModifiers (), hasVisibleSubclass , state );
77+ return super .visitClass (tree , null );
78+ }
7579
76- @ Override
77- public Description matchMethod (MethodTree tree , VisitorState state ) {
78- return match (tree , tree .getModifiers (), state );
80+ @ Override
81+ public Void visitMethod (MethodTree tree , Void unused ) {
82+ match (tree , tree .getModifiers (), hasVisibleSubclass , state );
83+ return super .visitMethod (tree , null );
84+ }
85+ }.scan (tree , null );
86+ return NO_MATCH ;
7987 }
8088
81- private Description match (Tree tree , ModifiersTree modifiers , VisitorState state ) {
89+ private void match (
90+ Tree tree ,
91+ ModifiersTree modifiers ,
92+ ImmutableSet <ClassSymbol > hasVisibleSubclass ,
93+ VisitorState state ) {
8294 Symbol sym = getSymbol (tree );
8395 if (!isEffectivelyPrivate (sym )) {
84- return NO_MATCH ;
96+ return ;
8597 }
8698 if (wellKnownKeep .shouldKeep (tree )) {
87- return NO_MATCH ;
99+ return ;
88100 }
89101 if (sym instanceof MethodSymbol methodSymbol ) {
90102 if (hasAnnotation (methodSymbol , "java.lang.Override" , state )) {
91- return NO_MATCH ;
103+ return ;
92104 }
93105 // TODO: cushon - technically this should only match final classes, otherwise it could break
94106 // a subclass that relies on inheriting a method of a particular visibility to fulfil and
95107 // interface contract. Skip that for now, since many classes don't rely on that and also
96108 // aren't explicitly final.
97109 if (streamSuperMethods (methodSymbol , state .getTypes ()).findAny ().isPresent ()) {
98- return NO_MATCH ;
110+ return ;
99111 }
100112 }
101- if (Collections .disjoint (modifiers .getFlags (), MODIFIER_TO_REMOVE )) {
102- return NO_MATCH ;
113+ var enclosingClass = enclosingClass (sym );
114+ if (hasVisibleSubclass .contains (enclosingClass )) {
115+ return ;
103116 }
104- Optional <SuggestedFix > fix = removeModifiers (modifiers , state , MODIFIER_TO_REMOVE );
105- if (fix .isEmpty ()) {
106- // The fix may be empty for implicit modifiers, e.g. on enum constant fields
107- return NO_MATCH ;
117+ if (Collections .disjoint (modifiers .getFlags (), MODIFIER_TO_REMOVE )) {
118+ return ;
108119 }
109- return describeMatch (tree , fix .get ());
120+ removeModifiers (modifiers , state , MODIFIER_TO_REMOVE )
121+ // The fix may be empty for implicit modifiers, e.g. on enum constant fields
122+ .ifPresent (fix -> state .reportMatch (describeMatch (tree , fix )));
110123 }
111124
112125 private static final ImmutableSet <Modifier > MODIFIER_TO_REMOVE =
113126 ImmutableSet .of (Modifier .PUBLIC , Modifier .PROTECTED );
127+
128+ private static ImmutableSet <ClassSymbol > findClassesWithVisibleSubclasses (
129+ CompilationUnitTree compilationUnit , VisitorState state ) {
130+ ImmutableSet .Builder <ClassSymbol > hasVisibleSubclass = ImmutableSet .builder ();
131+ new TreePathScanner <Void , Void >() {
132+ @ Override
133+ public Void visitClass (ClassTree tree , Void unused ) {
134+ ClassSymbol sym = getSymbol (tree );
135+ if (!isEffectivelyPrivate (sym )) {
136+ for (Type superType : state .getTypes ().closure (sym .type )) {
137+ if (superType .tsym instanceof ClassSymbol classSymbol ) {
138+ hasVisibleSubclass .add (classSymbol );
139+ }
140+ }
141+ }
142+ return super .visitClass (tree , null );
143+ }
144+ }.scan (compilationUnit , null );
145+ return hasVisibleSubclass .build ();
146+ }
114147}
0 commit comments