Skip to content

Commit 3a4244e

Browse files
SONARJAVA-6186 Fix FPs in S6207 for accessor methods (#5516)
1 parent 1d35938 commit 3a4244e

File tree

2 files changed

+151
-27
lines changed

2 files changed

+151
-27
lines changed

java-checks-test-sources/default/src/main/java/checks/RedundantRecordMethodsCheckSample.java

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ public String something() { // Compliant
165165
}
166166

167167
record GetterWithBranches(String name, int age) {
168-
public String name() {
168+
public String name() { // Noncompliant {{Remove this redundant method which is the same as a default one.}}
169+
// ^^^^
169170
if ((new Random()).nextBoolean()) {
170171
return this.name;
171172
} else {
@@ -228,4 +229,89 @@ public BranchInConstructor2(int arg1, int arg2) { // Compliant
228229
}
229230
}
230231
}
232+
233+
record AccessorWithLoop(String name) {
234+
public String name() { // Noncompliant {{Remove this redundant method which is the same as a default one.}}
235+
// ^^^^
236+
while (true) {
237+
return name;
238+
}
239+
}
240+
}
241+
242+
interface NamedThing {
243+
244+
String name();
245+
246+
}
247+
248+
record AnnotatedAccessor(String name) implements NamedThing {
249+
@Override
250+
public String name() { // Compliant, no issues are raised for annotated accessors
251+
return name;
252+
}
253+
}
254+
255+
record DifferentlyNamedAccessor(String name) {
256+
public String getName() { // Compliant, no issues are raised for accessors with different names
257+
return name;
258+
}
259+
}
260+
261+
record AccessorSettingField(String name) {
262+
private static int AGE;
263+
264+
public String name() {
265+
AGE = 0;
266+
return name;
267+
}
268+
}
269+
270+
record AccessorWithParameter(String name) {
271+
public String name(int x) { // Compliant, no issues are raised for accessors with parameters
272+
return name;
273+
}
274+
}
275+
276+
record AccessorWithLogic(String name) {
277+
public String name() { // Compliant, no issues are raised for accessors with logic
278+
System.out.println("Side effects !");
279+
return name;
280+
}
281+
}
282+
283+
record AccessorWithLogic2(String name) {
284+
public String name() {
285+
int x = 42;
286+
System.out.println(x);
287+
return name;
288+
}
289+
}
290+
291+
record AccessorWithAssertion(String name) {
292+
public String name() { // Compliant, no issues are raised for accessors with assertions
293+
assert name != null;
294+
return name;
295+
}
296+
}
297+
298+
record AccessorWithThrow(int age) {
299+
public int age() { // Compliant, no issues are raised for accessors that throw exceptions
300+
if (age < 0) {
301+
throw new IllegalStateException("Negative age");
302+
}
303+
return age;
304+
}
305+
}
306+
307+
record AccessorWithValidation(int age) {
308+
public int age() { // Compliant, no issues are raised for accessors that call methods
309+
validate(age);
310+
return age;
311+
}
312+
313+
void validate(int age) {
314+
// Do something with potential side effects.
315+
}
316+
}
231317
}

java-checks/src/main/java/org/sonar/java/checks/RedundantRecordMethodsCheck.java

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616
*/
1717
package org.sonar.java.checks;
1818

19-
import java.util.Collection;
2019
import java.util.Collections;
2120
import java.util.HashSet;
2221
import java.util.List;
2322
import java.util.Optional;
2423
import java.util.Set;
2524
import java.util.stream.Collectors;
2625
import org.sonar.check.Rule;
26+
import org.sonar.java.model.ExpressionUtils;
2727
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
2828
import org.sonar.plugins.java.api.semantic.Symbol;
29+
import org.sonar.plugins.java.api.tree.AssertStatementTree;
2930
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
31+
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
3032
import org.sonar.plugins.java.api.tree.BlockTree;
3133
import org.sonar.plugins.java.api.tree.ClassTree;
3234
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
@@ -37,6 +39,7 @@
3739
import org.sonar.plugins.java.api.tree.MethodTree;
3840
import org.sonar.plugins.java.api.tree.ReturnStatementTree;
3941
import org.sonar.plugins.java.api.tree.StatementTree;
42+
import org.sonar.plugins.java.api.tree.ThrowStatementTree;
4043
import org.sonar.plugins.java.api.tree.Tree;
4144
import org.sonar.plugins.java.api.tree.VariableTree;
4245

@@ -64,7 +67,7 @@ public void visitNode(Tree tree) {
6467
if (member.is(Tree.Kind.CONSTRUCTOR)) {
6568
checkConstructor((MethodTree) member, componentNames);
6669
} else if (member.is(Tree.Kind.METHOD)) {
67-
checkMethod((MethodTree) member, components, componentNames);
70+
checkMethod((MethodTree) member, components);
6871
}
6972
}
7073
}
@@ -95,38 +98,30 @@ private void checkConstructor(MethodTree constructor, Set<String> componentNames
9598
}
9699
}
97100

98-
private void checkMethod(MethodTree method, List<Symbol.VariableSymbol> components, Set<String> componentsByName) {
99-
String methodName = method.symbol().name();
100-
if (!componentsByName.contains(methodName)) {
101+
private void checkMethod(MethodTree method, List<Symbol.VariableSymbol> components) {
102+
if (isAnnotated(method) || !method.parameters().isEmpty()) {
101103
return;
102104
}
103-
if (onlyReturnsRawValue(method, components)) {
104-
reportIssue(method.simpleName(), "Remove this redundant method which is the same as a default one.");
105+
106+
String methodName = method.symbol().name();
107+
Symbol accessedComponent = components.stream().filter(c -> c.name().equals(methodName)).findFirst().orElse(null);
108+
if (accessedComponent == null) {
109+
return;
105110
}
106-
}
107111

108-
public static boolean onlyReturnsRawValue(MethodTree method, Collection<Symbol.VariableSymbol> components) {
109-
Optional<ReturnStatementTree> returnStatement = getFirstReturnStatement(method);
110-
if (!returnStatement.isPresent()) {
111-
return false;
112+
var accessorVisitor = new AccessorVisitor();
113+
method.block().accept(accessorVisitor);
114+
if (accessorVisitor.containsLogic) {
115+
return;
112116
}
113-
ExpressionTree expression = returnStatement.get().expression();
114-
Symbol identifierSymbol;
115-
if (expression.is(Tree.Kind.IDENTIFIER)) {
116-
identifierSymbol = ((IdentifierTree) expression).symbol();
117-
} else if (expression.is(Tree.Kind.MEMBER_SELECT)) {
118-
identifierSymbol = (((MemberSelectExpressionTree) expression).identifier()).symbol();
119-
} else {
120-
return false;
117+
if (accessorVisitor.returnedExpressions.stream().allMatch(e -> isComponent(e, accessedComponent))) {
118+
reportIssue(method.simpleName(), "Remove this redundant method which is the same as a default one.");
121119
}
122-
return components.stream().anyMatch(identifierSymbol::equals);
123120
}
124121

125-
private static Optional<ReturnStatementTree> getFirstReturnStatement(MethodTree method) {
126-
return method.block().body().stream()
127-
.filter(statement -> statement.is(Tree.Kind.RETURN_STATEMENT))
128-
.map(ReturnStatementTree.class::cast)
129-
.findFirst();
122+
private static boolean isComponent(ExpressionTree expression, Symbol component) {
123+
return (ExpressionUtils.skipParentheses(expression) instanceof IdentifierTree identifier && component.equals(identifier.symbol()))
124+
|| (expression instanceof MemberSelectExpressionTree memberSelect && component.equals(memberSelect.identifier().symbol()));
130125
}
131126

132127
private static boolean isAnnotated(MethodTree method) {
@@ -259,4 +254,47 @@ private void mergeWith(ConstructorExecutionState other) {
259254
logicAfterAssignments = logicAfterAssignments || other.logicAfterAssignments;
260255
}
261256
}
257+
258+
private static class AccessorVisitor extends BaseTreeVisitor {
259+
260+
Set<ExpressionTree> returnedExpressions = new HashSet<>();
261+
262+
boolean containsLogic = false;
263+
264+
@Override
265+
public void visitReturnStatement(ReturnStatementTree tree) {
266+
returnedExpressions.add(tree.expression());
267+
super.visitReturnStatement(tree);
268+
}
269+
270+
@Override
271+
public void visitAssertStatement(AssertStatementTree tree) {
272+
containsLogic = true;
273+
super.visitAssertStatement(tree);
274+
}
275+
276+
@Override
277+
public void visitThrowStatement(ThrowStatementTree tree) {
278+
containsLogic = true;
279+
super.visitThrowStatement(tree);
280+
}
281+
282+
@Override
283+
public void visitExpressionStatement(ExpressionStatementTree tree) {
284+
containsLogic = true;
285+
super.visitExpressionStatement(tree);
286+
}
287+
288+
@Override
289+
public void visitVariable(VariableTree tree) {
290+
containsLogic = true;
291+
super.visitVariable(tree);
292+
}
293+
294+
@Override
295+
public void visitAssignmentExpression(AssignmentExpressionTree tree) {
296+
containsLogic = true;
297+
super.visitAssignmentExpression(tree);
298+
}
299+
}
262300
}

0 commit comments

Comments
 (0)