Skip to content

Commit 6f96cef

Browse files
cpovirkError Prone Team
authored andcommitted
Refactor ChainingConstructorIgnoresParameter to not store mutable state in BugChecker fields.
Checkers shouldn't do that; I just didn't know better at the time. Future work in this vein: `FloggerRequiredModifiers`. This CL also fixes a [bug](http://sponge2/682f43a9-c73c-4a4f-9924-d8f09888ef06) that caused a `@SuppressWarnings` annotation on a callee constructor to suppress warnings in its caller. PiperOrigin-RevId: 912658003
1 parent e140d4c commit 6f96cef

2 files changed

Lines changed: 64 additions & 44 deletions

File tree

core/src/main/java/com/google/errorprone/bugpatterns/ChainingConstructorIgnoresParameter.java

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,9 @@
2626
import static java.util.Collections.unmodifiableList;
2727

2828
import com.google.common.collect.ArrayListMultimap;
29-
import com.google.common.collect.ListMultimap;
3029
import com.google.errorprone.BugPattern;
3130
import com.google.errorprone.VisitorState;
3231
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
33-
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
34-
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
3532
import com.google.errorprone.matchers.Description;
3633
import com.google.errorprone.matchers.Matcher;
3734
import com.sun.source.tree.CompilationUnitTree;
@@ -41,8 +38,10 @@
4138
import com.sun.source.tree.MethodTree;
4239
import com.sun.source.tree.Tree;
4340
import com.sun.source.tree.VariableTree;
41+
import com.sun.source.util.TreePathScanner;
4442
import com.sun.tools.javac.code.Symbol.MethodSymbol;
4543
import com.sun.tools.javac.code.Type;
44+
import java.util.HashMap;
4645
import java.util.List;
4746
import java.util.Map;
4847

@@ -70,54 +69,50 @@
7069
+ "its caller's parameters, but its caller doesn't pass that parameter to it. It's "
7170
+ "likely that it was intended to.")
7271
public final class ChainingConstructorIgnoresParameter extends BugChecker
73-
implements CompilationUnitTreeMatcher, MethodInvocationTreeMatcher, MethodTreeMatcher {
74-
private final Map<MethodSymbol, List<VariableTree>> paramTypesForMethod = newHashMap();
75-
private final ListMultimap<MethodSymbol, Caller> callersToEvaluate = ArrayListMultimap.create();
76-
72+
implements CompilationUnitTreeMatcher {
7773
@Override
7874
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
79-
/*
80-
* Clear the collections to save memory. (I wonder if it also helps to handle weird cases when a
81-
* class has multiple definitions. But I would expect for multiple definitions within the same
82-
* compiler invocation to cause deeper problems.)
83-
*/
84-
paramTypesForMethod.clear();
85-
callersToEvaluate.clear(); // should have already been cleared
86-
return NO_MATCH;
87-
}
75+
var paramTypesForMethod = new HashMap<MethodSymbol, List<VariableTree>>();
76+
var callersToEvaluate = ArrayListMultimap.<MethodSymbol, Caller>create();
77+
78+
new TreePathScanner<Void, Void>() {
79+
@Override
80+
public Void visitMethodInvocation(MethodInvocationTree tree, Void unused) {
81+
// TODO(cpovirk): determine whether anyone might be calling Foo.this()
82+
if (isIdentifierWithName(tree.getMethodSelect(), "this")) {
83+
var symbol = getSymbol(tree);
84+
callersToEvaluate.put(symbol, new Caller(tree, state.withPath(getCurrentPath())));
85+
}
86+
return super.visitMethodInvocation(tree, null);
87+
}
8888

89-
@Override
90-
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
91-
MethodSymbol symbol = getSymbol(tree);
92-
// TODO(cpovirk): determine whether anyone might be calling Foo.this()
93-
if (!isIdentifierWithName(tree.getMethodSelect(), "this")) {
94-
return NO_MATCH;
95-
}
96-
callersToEvaluate.put(symbol, new Caller(tree, state));
97-
return evaluateCallers(symbol);
98-
}
89+
@Override
90+
public Void visitMethod(MethodTree tree, Void unused) {
91+
var symbol = getSymbol(tree);
92+
if (symbol.isConstructor()) {
93+
paramTypesForMethod.put(symbol, unmodifiableList(tree.getParameters()));
94+
}
95+
return super.visitMethod(tree, null);
96+
}
97+
}.scan(tree, null);
9998

100-
@Override
101-
public Description matchMethod(MethodTree tree, VisitorState state) {
102-
MethodSymbol symbol = getSymbol(tree);
103-
if (!symbol.isConstructor()) {
104-
return NO_MATCH;
99+
for (var symbol : callersToEvaluate.keySet()) {
100+
evaluateCallers(paramTypesForMethod.get(symbol), callersToEvaluate.get(symbol));
105101
}
106-
paramTypesForMethod.put(symbol, unmodifiableList(tree.getParameters()));
107-
return evaluateCallers(symbol);
108-
}
109102

110-
private Description evaluateCallers(MethodSymbol symbol) {
111-
List<VariableTree> paramTypes = paramTypesForMethod.get(symbol);
112-
if (paramTypes == null) {
113-
// We haven't seen the declaration yet. We'll evaluate the call when we do.
114-
return NO_MATCH;
115-
}
103+
// All matches are reported through reportMatch calls instead of return values.
104+
return NO_MATCH;
105+
}
116106

117-
for (Caller caller : callersToEvaluate.removeAll(symbol)) {
107+
private void evaluateCallers(List<VariableTree> paramTypes, List<Caller> callers) {
108+
for (var caller : callers) {
118109
VisitorState state = caller.state;
119110
MethodInvocationTree invocation = caller.tree;
120111

112+
if (isSuppressed(state)) {
113+
continue;
114+
}
115+
121116
MethodTree callerConstructor = state.findEnclosing(MethodTree.class);
122117
if (callerConstructor == null) {
123118
continue; // impossible, at least in compilable code?
@@ -175,9 +170,6 @@ private Description evaluateCallers(MethodSymbol symbol) {
175170
*/
176171
}
177172
}
178-
179-
// All matches are reported through reportMatch calls instead of return values.
180-
return NO_MATCH;
181173
}
182174

183175
private static Map<String, Type> indexTypeByName(List<? extends VariableTree> parameters) {
@@ -188,6 +180,15 @@ private static Map<String, Type> indexTypeByName(List<? extends VariableTree> pa
188180
return result;
189181
}
190182

183+
private boolean isSuppressed(VisitorState state) {
184+
for (Tree tree : state.getPath()) {
185+
if (isSuppressed(tree, state)) {
186+
return true;
187+
}
188+
}
189+
return false;
190+
}
191+
191192
private void reportMatch(
192193
Tree diagnosticPosition, VisitorState state, Tree toReplace, String replaceWith) {
193194
state.reportMatch(describeMatch(diagnosticPosition, replace(toReplace, replaceWith)));

core/src/test/java/com/google/errorprone/bugpatterns/ChainingConstructorIgnoresParameterTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ static class MissileLauncher {
5656
}
5757
}
5858
59+
static class SuppressedCallee {
60+
@SuppressWarnings("ChainingConstructorIgnoresParameter")
61+
SuppressedCallee(String foo, boolean bar) {}
62+
63+
SuppressedCallee(String foo) {
64+
// BUG: Diagnostic contains: this(foo, false)
65+
this("default", false);
66+
}
67+
}
68+
5969
static class ClassRatherThanPrimitive {
6070
ClassRatherThanPrimitive(String foo, boolean bar) {}
6171
@@ -233,6 +243,15 @@ static class Varargs2 {
233243
this("something");
234244
}
235245
}
246+
247+
static class SuppressedCaller {
248+
SuppressedCaller(String foo, boolean bar) {}
249+
250+
@SuppressWarnings("ChainingConstructorIgnoresParameter")
251+
SuppressedCaller(String foo) {
252+
this("default", false);
253+
}
254+
}
236255
}
237256
""")
238257
.doTest();

0 commit comments

Comments
 (0)