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
}
@@ -118,6 +143,9 @@ private boolean scan(Tree tree) {
118
143
/* A break statement cannot complete normally. */
119
144
@ Override
120
145
public Boolean visitBreak (BreakTree tree , Void unused ) {
146
+ if (patches .containsKey (tree )) {
147
+ return patches .get (tree );
148
+ }
121
149
breaks .add (skipLabel (requireNonNull (((JCTree .JCBreak ) tree ).target )));
122
150
return false ;
123
151
}
@@ -126,6 +154,9 @@ public Boolean visitBreak(BreakTree tree, Void unused) {
126
154
@ Override
127
155
public Boolean visitContinue (ContinueTree tree , Void unused ) {
128
156
continues .add (skipLabel (requireNonNull (((JCTree .JCContinue ) tree ).target )));
157
+ if (patches .containsKey (tree )) {
158
+ return patches .get (tree );
159
+ }
129
160
return false ;
130
161
}
131
162
@@ -135,29 +166,44 @@ private static Tree skipLabel(JCTree tree) {
135
166
136
167
@ Override
137
168
public Boolean visitBlock (BlockTree tree , Void unused ) {
169
+ if (patches .containsKey (tree )) {
170
+ return patches .get (tree );
171
+ }
138
172
return scan (tree .getStatements ());
139
173
}
140
174
141
175
/* A local class declaration statement can complete normally iff it is reachable. */
142
176
@ Override
143
177
public Boolean visitClass (ClassTree tree , Void unused ) {
178
+ if (patches .containsKey (tree )) {
179
+ return patches .get (tree );
180
+ }
144
181
return true ;
145
182
}
146
183
147
184
/* A local variable declaration statement can complete normally iff it is reachable. */
148
185
@ Override
149
186
public Boolean visitVariable (VariableTree tree , Void unused ) {
187
+ if (patches .containsKey (tree )) {
188
+ return patches .get (tree );
189
+ }
150
190
return true ;
151
191
}
152
192
153
193
/* An empty statement can complete normally iff it is reachable. */
154
194
@ Override
155
195
public Boolean visitEmptyStatement (EmptyStatementTree tree , Void unused ) {
196
+ if (patches .containsKey (tree )) {
197
+ return patches .get (tree );
198
+ }
156
199
return true ;
157
200
}
158
201
159
202
@ Override
160
203
public Boolean visitLabeledStatement (LabeledStatementTree tree , Void unused ) {
204
+ if (patches .containsKey (tree )) {
205
+ return patches .get (tree );
206
+ }
161
207
// break/continue targets have already been resolved by javac, so
162
208
// there's nothing to do here
163
209
return scan (tree .getStatement ());
@@ -166,6 +212,9 @@ public Boolean visitLabeledStatement(LabeledStatementTree tree, Void unused) {
166
212
/* An expression statement can complete normally iff it is reachable. */
167
213
@ Override
168
214
public Boolean visitExpressionStatement (ExpressionStatementTree tree , Void unused ) {
215
+ if (patches .containsKey (tree )) {
216
+ return patches .get (tree );
217
+ }
169
218
if (isSystemExit (tree .getExpression ())) {
170
219
// The spec doesn't have any special handling for {@code System.exit}, but in practice it
171
220
// cannot complete normally
@@ -198,6 +247,9 @@ private static boolean isSystemExit(ExpressionTree expression) {
198
247
*/
199
248
@ Override
200
249
public Boolean visitIf (IfTree tree , Void unused ) {
250
+ if (patches .containsKey (tree )) {
251
+ return patches .get (tree );
252
+ }
201
253
boolean thenCompletes = scan (tree .getThenStatement ());
202
254
boolean elseCompletes = tree .getElseStatement () == null || scan (tree .getElseStatement ());
203
255
return thenCompletes || elseCompletes ;
@@ -206,6 +258,9 @@ public Boolean visitIf(IfTree tree, Void unused) {
206
258
/* An assert statement can complete normally iff it is reachable. */
207
259
@ Override
208
260
public Boolean visitAssert (AssertTree tree , Void unused ) {
261
+ if (patches .containsKey (tree )) {
262
+ return patches .get (tree );
263
+ }
209
264
return true ;
210
265
}
211
266
@@ -232,6 +287,10 @@ public Boolean visitAssert(AssertTree tree, Void unused) {
232
287
*/
233
288
@ Override
234
289
public Boolean visitSwitch (SwitchTree tree , Void unused ) {
290
+ if (patches .containsKey (tree )) {
291
+ return patches .get (tree );
292
+ }
293
+
235
294
// (1)
236
295
if (tree .getCases ().stream ().noneMatch (c -> isSwitchDefault (c ))) {
237
296
return true ;
@@ -294,6 +353,9 @@ public Boolean visitSwitch(SwitchTree tree, Void unused) {
294
353
*/
295
354
@ Override
296
355
public Boolean visitWhileLoop (WhileLoopTree tree , Void unused ) {
356
+ if (patches .containsKey (tree )) {
357
+ return patches .get (tree );
358
+ }
297
359
Boolean condValue = ASTHelpers .constValue (tree .getCondition (), Boolean .class );
298
360
if (!Objects .equals (condValue , false )) {
299
361
scan (tree .getStatement ());
@@ -333,6 +395,9 @@ public Boolean visitWhileLoop(WhileLoopTree tree, Void unused) {
333
395
*/
334
396
@ Override
335
397
public Boolean visitDoWhileLoop (DoWhileLoopTree that , Void unused ) {
398
+ if (patches .containsKey (that )) {
399
+ return patches .get (that );
400
+ }
336
401
boolean completes = scan (that .getStatement ());
337
402
boolean conditionIsAlwaysTrue =
338
403
firstNonNull (ASTHelpers .constValue (that .getCondition (), Boolean .class ), false );
@@ -367,6 +432,9 @@ public Boolean visitDoWhileLoop(DoWhileLoopTree that, Void unused) {
367
432
*/
368
433
@ Override
369
434
public Boolean visitForLoop (ForLoopTree that , Void unused ) {
435
+ if (patches .containsKey (that )) {
436
+ return patches .get (that );
437
+ }
370
438
Boolean condValue = ASTHelpers .constValue (that .getCondition (), Boolean .class );
371
439
if (!Objects .equals (condValue , false )) {
372
440
scan (that .getStatement ());
@@ -385,19 +453,28 @@ public Boolean visitForLoop(ForLoopTree that, Void unused) {
385
453
/* An enhanced for statement can complete normally iff it is reachable. */
386
454
@ Override
387
455
public Boolean visitEnhancedForLoop (EnhancedForLoopTree that , Void unused ) {
456
+ if (patches .containsKey (that )) {
457
+ return patches .get (that );
458
+ }
388
459
scan (that .getStatement ());
389
460
return true ;
390
461
}
391
462
392
463
/* A return statement cannot complete normally. */
393
464
@ Override
394
465
public Boolean visitReturn (ReturnTree tree , Void unused ) {
466
+ if (patches .containsKey (tree )) {
467
+ return patches .get (tree );
468
+ }
395
469
return false ;
396
470
}
397
471
398
472
/* A throw statement cannot complete normally. */
399
473
@ Override
400
474
public Boolean visitThrow (ThrowTree tree , Void unused ) {
475
+ if (patches .containsKey (tree )) {
476
+ return patches .get (tree );
477
+ }
401
478
return false ;
402
479
}
403
480
@@ -410,6 +487,9 @@ public Boolean visitThrow(ThrowTree tree, Void unused) {
410
487
*/
411
488
@ Override
412
489
public Boolean visitSynchronized (SynchronizedTree tree , Void unused ) {
490
+ if (patches .containsKey (tree )) {
491
+ return patches .get (tree );
492
+ }
413
493
return scan (tree .getBlock ());
414
494
}
415
495
@@ -424,6 +504,9 @@ public Boolean visitSynchronized(SynchronizedTree tree, Void unused) {
424
504
*/
425
505
@ Override
426
506
public Boolean visitTry (TryTree that , Void unused ) {
507
+ if (patches .containsKey (that )) {
508
+ return patches .get (that );
509
+ }
427
510
boolean completes = scan (that .getBlock ());
428
511
// assume all catch blocks are reachable; javac has already rejected unreachable
429
512
// checked exception handlers
0 commit comments