22
22
import static com .google .errorprone .util .ASTHelpers .isSwitchDefault ;
23
23
import static java .util .Objects .requireNonNull ;
24
24
25
+ import com .google .common .collect .ImmutableMap ;
25
26
import com .google .errorprone .annotations .CanIgnoreReturnValue ;
26
27
import com .sun .source .tree .AssertTree ;
27
28
import com .sun .source .tree .BlockTree ;
@@ -66,7 +67,20 @@ public class Reachability {
66
67
* <p>An exception is made for {@code System.exit}, which cannot complete normally in practice.
67
68
*/
68
69
public static boolean canCompleteNormally (StatementTree statement ) {
69
- return statement .accept (new CanCompleteNormallyVisitor (), null );
70
+ return statement .accept (new CanCompleteNormallyVisitor (/* patches= */ ImmutableMap .of ()), null );
71
+ }
72
+
73
+ /**
74
+ * Returns {@code true} if the given statement can complete normally, as defined by JLS 14.21,
75
+ * when taking into account the given {@code patches}. The patches are a (possibly empty) map from
76
+ * {@code Tree} to a boolean indicating whether that specific {@code Tree} can complete normally.
77
+ * All relevant tree(s) not present in the patches will be analyzed as per the JLS.
78
+ *
79
+ * <p>An exception is made for {@code System.exit}, which cannot complete normally in practice.
80
+ */
81
+ public static boolean canCompleteNormally (
82
+ StatementTree statement , ImmutableMap <Tree , Boolean > patches ) {
83
+ return statement .accept (new CanCompleteNormallyVisitor (patches ), null );
70
84
}
71
85
72
86
/**
@@ -100,10 +114,21 @@ private static class CanCompleteNormallyVisitor extends SimpleTreeVisitor<Boolea
100
114
/** Trees that are the target of a reachable continue statement. */
101
115
private final Set <Tree > continues = new HashSet <>();
102
116
117
+ /** Trees that are patched to have a specific completion result. */
118
+ private final ImmutableMap <Tree , Boolean > patches ;
119
+
120
+ public CanCompleteNormallyVisitor (ImmutableMap <Tree , Boolean > patches ) {
121
+ this .patches = patches ;
122
+ }
123
+
103
124
boolean scan (List <? extends StatementTree > trees ) {
104
125
boolean completes = true ;
105
126
for (StatementTree tree : trees ) {
106
- completes = scan (tree );
127
+ if (patches .containsKey (tree )) {
128
+ completes = patches .get (tree );
129
+ } else {
130
+ completes = scan (tree );
131
+ }
107
132
}
108
133
return completes ;
109
134
}
@@ -112,12 +137,18 @@ boolean scan(List<? extends StatementTree> trees) {
112
137
// don't otherwise affect the result of the reachability analysis.
113
138
@ CanIgnoreReturnValue
114
139
private boolean scan (Tree tree ) {
140
+ if (patches .containsKey (tree )) {
141
+ return patches .get (tree );
142
+ }
115
143
return tree .accept (this , null );
116
144
}
117
145
118
146
/* A break statement cannot complete normally. */
119
147
@ Override
120
148
public Boolean visitBreak (BreakTree tree , Void unused ) {
149
+ if (patches .containsKey (tree )) {
150
+ return patches .get (tree );
151
+ }
121
152
breaks .add (skipLabel (requireNonNull (((JCTree .JCBreak ) tree ).target )));
122
153
return false ;
123
154
}
@@ -126,6 +157,9 @@ public Boolean visitBreak(BreakTree tree, Void unused) {
126
157
@ Override
127
158
public Boolean visitContinue (ContinueTree tree , Void unused ) {
128
159
continues .add (skipLabel (requireNonNull (((JCTree .JCContinue ) tree ).target )));
160
+ if (patches .containsKey (tree )) {
161
+ return patches .get (tree );
162
+ }
129
163
return false ;
130
164
}
131
165
@@ -135,29 +169,44 @@ private static Tree skipLabel(JCTree tree) {
135
169
136
170
@ Override
137
171
public Boolean visitBlock (BlockTree tree , Void unused ) {
172
+ if (patches .containsKey (tree )) {
173
+ return patches .get (tree );
174
+ }
138
175
return scan (tree .getStatements ());
139
176
}
140
177
141
178
/* A local class declaration statement can complete normally iff it is reachable. */
142
179
@ Override
143
180
public Boolean visitClass (ClassTree tree , Void unused ) {
181
+ if (patches .containsKey (tree )) {
182
+ return patches .get (tree );
183
+ }
144
184
return true ;
145
185
}
146
186
147
187
/* A local variable declaration statement can complete normally iff it is reachable. */
148
188
@ Override
149
189
public Boolean visitVariable (VariableTree tree , Void unused ) {
190
+ if (patches .containsKey (tree )) {
191
+ return patches .get (tree );
192
+ }
150
193
return true ;
151
194
}
152
195
153
196
/* An empty statement can complete normally iff it is reachable. */
154
197
@ Override
155
198
public Boolean visitEmptyStatement (EmptyStatementTree tree , Void unused ) {
199
+ if (patches .containsKey (tree )) {
200
+ return patches .get (tree );
201
+ }
156
202
return true ;
157
203
}
158
204
159
205
@ Override
160
206
public Boolean visitLabeledStatement (LabeledStatementTree tree , Void unused ) {
207
+ if (patches .containsKey (tree )) {
208
+ return patches .get (tree );
209
+ }
161
210
// break/continue targets have already been resolved by javac, so
162
211
// there's nothing to do here
163
212
return scan (tree .getStatement ());
@@ -166,6 +215,9 @@ public Boolean visitLabeledStatement(LabeledStatementTree tree, Void unused) {
166
215
/* An expression statement can complete normally iff it is reachable. */
167
216
@ Override
168
217
public Boolean visitExpressionStatement (ExpressionStatementTree tree , Void unused ) {
218
+ if (patches .containsKey (tree )) {
219
+ return patches .get (tree );
220
+ }
169
221
if (isSystemExit (tree .getExpression ())) {
170
222
// The spec doesn't have any special handling for {@code System.exit}, but in practice it
171
223
// cannot complete normally
@@ -198,6 +250,9 @@ private static boolean isSystemExit(ExpressionTree expression) {
198
250
*/
199
251
@ Override
200
252
public Boolean visitIf (IfTree tree , Void unused ) {
253
+ if (patches .containsKey (tree )) {
254
+ return patches .get (tree );
255
+ }
201
256
boolean thenCompletes = scan (tree .getThenStatement ());
202
257
boolean elseCompletes = tree .getElseStatement () == null || scan (tree .getElseStatement ());
203
258
return thenCompletes || elseCompletes ;
@@ -206,6 +261,9 @@ public Boolean visitIf(IfTree tree, Void unused) {
206
261
/* An assert statement can complete normally iff it is reachable. */
207
262
@ Override
208
263
public Boolean visitAssert (AssertTree tree , Void unused ) {
264
+ if (patches .containsKey (tree )) {
265
+ return patches .get (tree );
266
+ }
209
267
return true ;
210
268
}
211
269
@@ -232,6 +290,10 @@ public Boolean visitAssert(AssertTree tree, Void unused) {
232
290
*/
233
291
@ Override
234
292
public Boolean visitSwitch (SwitchTree tree , Void unused ) {
293
+ if (patches .containsKey (tree )) {
294
+ return patches .get (tree );
295
+ }
296
+
235
297
// (1)
236
298
if (tree .getCases ().stream ().noneMatch (c -> isSwitchDefault (c ))) {
237
299
return true ;
@@ -294,6 +356,9 @@ public Boolean visitSwitch(SwitchTree tree, Void unused) {
294
356
*/
295
357
@ Override
296
358
public Boolean visitWhileLoop (WhileLoopTree tree , Void unused ) {
359
+ if (patches .containsKey (tree )) {
360
+ return patches .get (tree );
361
+ }
297
362
Boolean condValue = ASTHelpers .constValue (tree .getCondition (), Boolean .class );
298
363
if (!Objects .equals (condValue , false )) {
299
364
scan (tree .getStatement ());
@@ -333,6 +398,9 @@ public Boolean visitWhileLoop(WhileLoopTree tree, Void unused) {
333
398
*/
334
399
@ Override
335
400
public Boolean visitDoWhileLoop (DoWhileLoopTree that , Void unused ) {
401
+ if (patches .containsKey (that )) {
402
+ return patches .get (that );
403
+ }
336
404
boolean completes = scan (that .getStatement ());
337
405
boolean conditionIsAlwaysTrue =
338
406
firstNonNull (ASTHelpers .constValue (that .getCondition (), Boolean .class ), false );
@@ -367,6 +435,9 @@ public Boolean visitDoWhileLoop(DoWhileLoopTree that, Void unused) {
367
435
*/
368
436
@ Override
369
437
public Boolean visitForLoop (ForLoopTree that , Void unused ) {
438
+ if (patches .containsKey (that )) {
439
+ return patches .get (that );
440
+ }
370
441
Boolean condValue = ASTHelpers .constValue (that .getCondition (), Boolean .class );
371
442
if (!Objects .equals (condValue , false )) {
372
443
scan (that .getStatement ());
@@ -385,19 +456,28 @@ public Boolean visitForLoop(ForLoopTree that, Void unused) {
385
456
/* An enhanced for statement can complete normally iff it is reachable. */
386
457
@ Override
387
458
public Boolean visitEnhancedForLoop (EnhancedForLoopTree that , Void unused ) {
459
+ if (patches .containsKey (that )) {
460
+ return patches .get (that );
461
+ }
388
462
scan (that .getStatement ());
389
463
return true ;
390
464
}
391
465
392
466
/* A return statement cannot complete normally. */
393
467
@ Override
394
468
public Boolean visitReturn (ReturnTree tree , Void unused ) {
469
+ if (patches .containsKey (tree )) {
470
+ return patches .get (tree );
471
+ }
395
472
return false ;
396
473
}
397
474
398
475
/* A throw statement cannot complete normally. */
399
476
@ Override
400
477
public Boolean visitThrow (ThrowTree tree , Void unused ) {
478
+ if (patches .containsKey (tree )) {
479
+ return patches .get (tree );
480
+ }
401
481
return false ;
402
482
}
403
483
@@ -410,6 +490,9 @@ public Boolean visitThrow(ThrowTree tree, Void unused) {
410
490
*/
411
491
@ Override
412
492
public Boolean visitSynchronized (SynchronizedTree tree , Void unused ) {
493
+ if (patches .containsKey (tree )) {
494
+ return patches .get (tree );
495
+ }
413
496
return scan (tree .getBlock ());
414
497
}
415
498
@@ -424,6 +507,9 @@ public Boolean visitSynchronized(SynchronizedTree tree, Void unused) {
424
507
*/
425
508
@ Override
426
509
public Boolean visitTry (TryTree that , Void unused ) {
510
+ if (patches .containsKey (that )) {
511
+ return patches .get (that );
512
+ }
427
513
boolean completes = scan (that .getBlock ());
428
514
// assume all catch blocks are reachable; javac has already rejected unreachable
429
515
// checked exception handlers
0 commit comments