Repository navigation
Expand file tree
/
Copy pathReturnEmptyCollectionRatherThanNullRule.java
More file actions
136 lines (123 loc) · 6.15 KB
/
Copy pathReturnEmptyCollectionRatherThanNullRule.java
File metadata and controls
136 lines (123 loc) · 6.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.errorprone;
import java.util.Collection;
import java.util.Map;
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression;
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
import net.sourceforge.pmd.lang.java.ast.ReturnScopeNode;
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass;
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass.AssignmentEntry;
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass.DataflowResult;
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass.ReachingDefinitionSet;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
import net.sourceforge.pmd.reporting.RuleContext;
/**
* For methods that return an array, a {@link Collection} or a {@link Map}, this rule reports
* a {@code return} statement when the value it produces has an explicit {@code null} source.
*
* <p>The analysis is conservative about values it cannot trace: method calls, field reads and
* parameters are not treated as {@code null} without an explicit {@code null} source, and the
* {@code null} literal is ignored when it appears only in a condition, a switch selector or an array
* element rather than in the produced value.
*
* @since 6.37.0 (as XPath) / 7.27.0 (as Java)
*/
public class ReturnEmptyCollectionRatherThanNullRule extends AbstractJavaRulechainRule {
public ReturnEmptyCollectionRatherThanNullRule() {
super(ASTReturnStatement.class);
}
@Override
public Object visit(ASTReturnStatement returnStmt, Object data) {
RuleContext ctx = (RuleContext) data;
ReturnScopeNode target = JavaAstUtils.getReturnTarget(returnStmt);
if (!(target instanceof ASTMethodDeclaration)) {
return null;
}
ASTMethodDeclaration method = (ASTMethodDeclaration) target;
if (!returnsArrayOrCollection(method)) {
return null;
}
ASTExpression expression = returnStmt.getExpr();
if (expression == null) {
return null;
}
if (mayReturnExplicitNull(expression) || reachesExplicitNullThroughLocal(expression)) {
ctx.addViolation(returnStmt);
}
return null;
}
private static boolean returnsArrayOrCollection(ASTMethodDeclaration method) {
JTypeMirror returnType = method.getResultTypeNode().getTypeMirror();
return returnType.isArray()
|| TypeTestUtil.isA(Collection.class, returnType)
|| TypeTestUtil.isA(Map.class, returnType);
}
/**
* Returns true when {@code expression} can produce the value {@code null} through an explicit
* null source. The condition of a conditional and the selector of a switch are value-selecting
* expressions, not produced values, so they are not examined. Local variables are not traced
* here; {@link #reachesExplicitNullThroughLocal(ASTExpression)} handles a bare returned local.
*/
private static boolean mayReturnExplicitNull(ASTExpression expression) {
if (expression instanceof ASTNullLiteral) {
return true;
} else if (expression instanceof ASTConditionalExpression) {
ASTConditionalExpression conditional = (ASTConditionalExpression) expression;
return mayReturnExplicitNull(conditional.getThenBranch())
|| mayReturnExplicitNull(conditional.getElseBranch());
} else if (expression instanceof ASTCastExpression) {
return mayReturnExplicitNull(((ASTCastExpression) expression).getOperand());
} else if (expression instanceof ASTSwitchExpression) {
for (ASTExpression yielded : ((ASTSwitchExpression) expression).getYieldExpressions()) {
if (mayReturnExplicitNull(yielded)) {
return true;
}
}
return false;
} else if (expression instanceof ASTAssignmentExpression) {
ASTAssignmentExpression assignment = (ASTAssignmentExpression) expression;
// Only a plain assignment can carry an explicit null reference as its value;
// compound assignments are not a direct null source.
return !assignment.isCompound()
&& mayReturnExplicitNull(assignment.getRightOperand());
}
return false;
}
/**
* For a bare {@code return localVar;}, follows the reaching definitions of the local variable
* and returns true when any of them has an explicit {@code null} source. This is only applied
* to a variable returned directly, never to one nested in a conditional or switch branch, so a
* null guard around the value is not reinterpreted. Reaching definitions that the dataflow
* analysis cannot fully determine are treated as non-null.
*/
private static boolean reachesExplicitNullThroughLocal(ASTExpression expression) {
if (!JavaAstUtils.isReferenceToLocal(expression)) {
return false;
}
ASTVariableAccess access = (ASTVariableAccess) expression;
DataflowResult dataflow = DataflowPass.getDataflowResult(access.getRoot());
ReachingDefinitionSet reaching = dataflow.getReachingDefinitions(access);
if (reaching.isNotFullyKnown()) {
return false;
}
for (AssignmentEntry def : reaching.getReaching()) {
ASTExpression rhs = def.getRhsAsExpression();
if (rhs != null && mayReturnExplicitNull(rhs)) {
return true;
}
}
return false;
}
}