1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.sonar.java.se.checks;
21
22 import com.google.common.base.Preconditions;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Objects;
26 import javax.annotation.Nullable;
27 import org.sonar.check.Rule;
28 import org.sonar.java.matcher.MethodMatcher;
29 import org.sonar.java.matcher.MethodMatcherCollection;
30 import org.sonar.java.se.CheckerContext;
31 import org.sonar.java.se.ProgramState;
32 import org.sonar.java.se.constraint.BooleanConstraint;
33 import org.sonar.java.se.constraint.Constraint;
34 import org.sonar.java.se.constraint.ConstraintManager;
35 import org.sonar.java.se.constraint.ObjectConstraint;
36 import org.sonar.java.se.symbolicvalues.SymbolicValue;
37 import org.sonar.plugins.java.api.semantic.Symbol;
38 import org.sonar.plugins.java.api.tree.ExpressionTree;
39 import org.sonar.plugins.java.api.tree.IdentifierTree;
40 import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
41 import org.sonar.plugins.java.api.tree.MethodInvocationTree;
42 import org.sonar.plugins.java.api.tree.Tree;
43
44 @Rule(key = "S3655")
45 public class OptionalGetBeforeIsPresentCheck extends SECheck {
46
47 private static final ExceptionalYieldChecker EXCEPTIONAL_YIELD_CHECKER = new ExceptionalYieldChecker(
48 "\"NoSuchElementException\" will be thrown when invoking method \"%s()\" without verifying Optional parameter.");
49 private static final MethodMatcher OPTIONAL_GET = optionalMethod("get").withoutParameter();
50 private static final MethodMatcher OPTIONAL_ORELSE = optionalMethod("orElse").withAnyParameters();
51 private static final MethodMatcherCollection OPTIONAL_TEST_METHODS = MethodMatcherCollection.create(
52 optionalMethod("isPresent").withoutParameter(),
53 optionalMethod("isEmpty").withoutParameter());
54 private static final MethodMatcher OPTIONAL_EMPTY = optionalMethod("empty").withoutParameter();
55 private static final MethodMatcher OPTIONAL_OF = optionalMethod("of").withAnyParameters();
56 private static final MethodMatcher OPTIONAL_OF_NULLABLE = optionalMethod("ofNullable").withAnyParameters();
57 private static final MethodMatcher OPTIONAL_FILTER = optionalMethod("filter").withAnyParameters();
58
59 private enum OptionalConstraint implements Constraint {
60 PRESENT, NOT_PRESENT;
61
62 @Override
63 public boolean isValidWith(@Nullable Constraint constraint) {
64 return constraint == null || this == constraint;
65 }
66
67 @Override
68 public boolean hasPreciseValue() {
69 return this == NOT_PRESENT;
70 }
71 }
72
73 private static MethodMatcher optionalMethod(String methodName) {
74 return MethodMatcher.create().typeDefinition("java.util.Optional").name(methodName);
75 }
76
77 @Override
78 public ProgramState checkPreStatement(CheckerContext context, Tree syntaxNode) {
79 PreStatementVisitor visitor = new PreStatementVisitor(this, context);
80 syntaxNode.accept(visitor);
81 return visitor.programState;
82 }
83
84 @Override
85 public ProgramState checkPostStatement(CheckerContext context, Tree syntaxNode) {
86 List<ProgramState> programStates = setOptionalConstraint(context, syntaxNode);
87 Preconditions.checkState(programStates.size() == 1);
88 return programStates.get(0);
89 }
90
91 private static List<ProgramState> setOptionalConstraint(CheckerContext context, Tree syntaxNode) {
92 ProgramState programState = context.getState();
93 if (!syntaxNode.is(Tree.Kind.METHOD_INVOCATION)) {
94 return Collections.singletonList(programState);
95 }
96 MethodInvocationTree mit = (MethodInvocationTree) syntaxNode;
97 SymbolicValue peekValue = programState.peekValue();
98 Objects.requireNonNull(peekValue);
99 if (OPTIONAL_EMPTY.matches(mit)) {
100 return peekValue.setConstraint(programState, OptionalConstraint.NOT_PRESENT);
101 }
102 if (OPTIONAL_OF.matches(mit)) {
103 return peekValue.setConstraint(programState, OptionalConstraint.PRESENT);
104 }
105 if (OPTIONAL_OF_NULLABLE.matches(mit)) {
106 ProgramState psPriorMethodInvocation = context.getNode().programState;
107 SymbolicValue paramSV = psPriorMethodInvocation.peekValue(0);
108 ObjectConstraint paramConstraint = psPriorMethodInvocation.getConstraint(paramSV, ObjectConstraint.class);
109 if (paramConstraint != null) {
110
111 return peekValue.setConstraint(programState, paramConstraint == ObjectConstraint.NULL ? OptionalConstraint.NOT_PRESENT : OptionalConstraint.PRESENT);
112 }
113 }
114 return Collections.singletonList(programState);
115 }
116
117 private static class OptionalSymbolicValue extends SymbolicValue {
118 protected final SymbolicValue wrappedValue;
119
120 private OptionalSymbolicValue(SymbolicValue wrappedValue) {
121 this.wrappedValue = wrappedValue;
122 }
123
124 @Override
125 public boolean references(SymbolicValue other) {
126 return wrappedValue.equals(other) || wrappedValue.references(other);
127 }
128 }
129
130 private static class FilteredOptionalSymbolicValue extends OptionalSymbolicValue {
131 private FilteredOptionalSymbolicValue(SymbolicValue wrappedValue) {
132 super(wrappedValue);
133 }
134
135 @Override
136 public List<ProgramState> setConstraint(ProgramState programState, Constraint constraint) {
137 ProgramState ps = programState;
138 if (constraint == OptionalConstraint.PRESENT) {
139 List<ProgramState> programStates = wrappedValue.setConstraint(ps, constraint);
140
141
142
143 Preconditions.checkState(programStates.size() == 1);
144 ps = programStates.get(0);
145 }
146 return super.setConstraint(ps, constraint);
147 }
148
149 }
150
151
152
153
154
155
156 private static class OptionalTestMethodSymbolicValue extends SymbolicValue {
157
158 private final SymbolicValue optionalSV;
159 private final boolean isIsEmpty;
160
161 public OptionalTestMethodSymbolicValue(SymbolicValue sv, Symbol testMethod) {
162 this.optionalSV = sv;
163 this.isIsEmpty = "isEmpty".equals(testMethod.name());
164 }
165
166
167
168
169 @Override
170 public List<ProgramState> setConstraint(ProgramState programState, BooleanConstraint booleanConstraint) {
171 OptionalConstraint optionalConstraint = programState.getConstraint(optionalSV, OptionalConstraint.class);
172 if (isImpossibleState(booleanConstraint, optionalConstraint)) {
173 return Collections.emptyList();
174 }
175 if (optionalConstraint == OptionalConstraint.NOT_PRESENT || optionalConstraint == OptionalConstraint.PRESENT) {
176 return Collections.singletonList(programState);
177 }
178
179 return optionalSV.setConstraint(programState, expectedOptionalConstraint(booleanConstraint));
180 }
181
182 private boolean isImpossibleState(BooleanConstraint booleanConstraint, @Nullable OptionalConstraint optionalConstraint) {
183 return optionalConstraint == expectedOptionalConstraint(booleanConstraint.isTrue() ? BooleanConstraint.FALSE : BooleanConstraint.TRUE);
184 }
185
186 private OptionalConstraint expectedOptionalConstraint(BooleanConstraint booleanConstraint) {
187 if (booleanConstraint.isTrue()) {
188 return isIsEmpty ? OptionalConstraint.NOT_PRESENT : OptionalConstraint.PRESENT;
189 }
190 return isIsEmpty ? OptionalConstraint.PRESENT : OptionalConstraint.NOT_PRESENT;
191 }
192
193 @Override
194 public boolean references(SymbolicValue other) {
195 return optionalSV.equals(other) || optionalSV.references(other);
196 }
197 }
198
199 private static class PreStatementVisitor extends CheckerTreeNodeVisitor {
200
201 private final CheckerContext context;
202 private final ConstraintManager constraintManager;
203 private final SECheck check;
204
205 private PreStatementVisitor(SECheck check, CheckerContext context) {
206 super(context.getState());
207 this.context = context;
208 this.constraintManager = context.getConstraintManager();
209 this.check = check;
210 }
211
212 @Override
213 public void visitMethodInvocation(MethodInvocationTree tree) {
214 SymbolicValue peek = programState.peekValue();
215 if (OPTIONAL_TEST_METHODS.anyMatch(tree)) {
216 constraintManager.setValueFactory(() -> new OptionalTestMethodSymbolicValue(peek, tree.symbol()));
217 } else if (OPTIONAL_GET.matches(tree) && presenceHasNotBeenChecked(peek)) {
218 context.addExceptionalYield(peek, programState, "java.util.NoSuchElementException", check);
219 reportIssue(tree);
220
221 programState = programState.addConstraint(peek, OptionalConstraint.PRESENT);
222 } else if (OPTIONAL_FILTER.matches(tree)) {
223
224 SymbolicValue optionalSV = programState.peekValue(1);
225
226 if (programState.getConstraint(optionalSV, OptionalConstraint.class) == OptionalConstraint.NOT_PRESENT) {
227
228 constraintManager.setValueFactory(() -> optionalSV);
229 } else {
230 constraintManager.setValueFactory(() -> new FilteredOptionalSymbolicValue(optionalSV));
231 }
232 } else if (OPTIONAL_ORELSE.matches(tree)) {
233 ProgramState.Pop pop = programState.unstackValue(2);
234 SymbolicValue orElseValue = pop.values.get(0);
235 SymbolicValue optional = pop.values.get(1);
236 List<ProgramState> psEmpty = optional.setConstraint(pop.state.stackValue(orElseValue), OptionalConstraint.NOT_PRESENT);
237 SymbolicValue symbolicValue;
238 if(optional instanceof OptionalSymbolicValue) {
239 symbolicValue = ((OptionalSymbolicValue) optional).wrappedValue;
240 } else {
241 symbolicValue = constraintManager.createSymbolicValue(tree);
242 }
243 List<ProgramState> psPresent = optional.setConstraint(pop.state.stackValue(symbolicValue), OptionalConstraint.PRESENT);
244 psEmpty.forEach(context::addTransition);
245 psPresent.forEach(context::addTransition);
246 programState = null;
247 } else if (OPTIONAL_OF.matches(tree) || OPTIONAL_OF_NULLABLE.matches(tree)) {
248 constraintManager.setValueFactory(() -> new OptionalSymbolicValue(peek));
249 }
250 }
251
252 private void reportIssue(MethodInvocationTree mit) {
253 String identifier = getIdentifierPart(mit.methodSelect());
254 String issueMsg = identifier.isEmpty() ? "Optional#" : (identifier + ".");
255 Tree reportTree = mit.methodSelect().is(Tree.Kind.MEMBER_SELECT) ? ((MemberSelectExpressionTree) mit.methodSelect()).expression() : mit;
256 context.reportIssue(reportTree, check, "Call \""+ issueMsg + "isPresent()\" before accessing the value.");
257 }
258
259 private boolean presenceHasNotBeenChecked(SymbolicValue sv) {
260 return programState.getConstraint(sv, OptionalConstraint.class) != OptionalConstraint.PRESENT;
261 }
262
263 private static String getIdentifierPart(ExpressionTree methodSelect) {
264 if (methodSelect.is(Tree.Kind.MEMBER_SELECT)) {
265 ExpressionTree expression = ((MemberSelectExpressionTree) methodSelect).expression();
266 if (expression.is(Tree.Kind.IDENTIFIER)) {
267 return ((IdentifierTree) expression).name();
268 }
269 }
270 return "";
271 }
272
273 }
274
275 @Override
276 public void checkEndOfExecutionPath(CheckerContext context, ConstraintManager constraintManager) {
277 EXCEPTIONAL_YIELD_CHECKER.reportOnExceptionalYield(context.getNode(), this);
278 }
279 }