• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 4516

27 Mar 2025 03:42PM UTC coverage: 79.841% (+2.0%) from 77.853%
4516

push

github

adangel
[doc] Fix search index (#5618)

Merge pull request #5618 from adangel:doc/fix-search

16710 of 21943 branches covered (76.15%)

Branch coverage included in aggregate %.

34885 of 42679 relevant lines covered (81.74%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.58
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/DataflowPass.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.rule.internal;
6

7
import static java.util.Collections.emptySet;
8
import static net.sourceforge.pmd.util.CollectionUtil.asSingle;
9

10
import java.util.ArrayDeque;
11
import java.util.ArrayList;
12
import java.util.Collections;
13
import java.util.Deque;
14
import java.util.LinkedHashMap;
15
import java.util.LinkedHashSet;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Objects;
19
import java.util.Set;
20
import java.util.function.Supplier;
21

22
import org.checkerframework.checker.nullness.qual.NonNull;
23
import org.checkerframework.checker.nullness.qual.Nullable;
24
import org.pcollections.HashTreePSet;
25
import org.pcollections.PSet;
26

27
import net.sourceforge.pmd.lang.ast.Node;
28
import net.sourceforge.pmd.lang.ast.NodeStream;
29
import net.sourceforge.pmd.lang.java.ast.ASTArrayAllocation;
30
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
31
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType;
32
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression;
33
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
34
import net.sourceforge.pmd.lang.java.ast.ASTBodyDeclaration;
35
import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
36
import net.sourceforge.pmd.lang.java.ast.ASTCatchClause;
37
import net.sourceforge.pmd.lang.java.ast.ASTCatchParameter;
38
import net.sourceforge.pmd.lang.java.ast.ASTCompactConstructorDeclaration;
39
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
40
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
41
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
42
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
43
import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement;
44
import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
45
import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant;
46
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
47
import net.sourceforge.pmd.lang.java.ast.ASTFieldAccess;
48
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
49
import net.sourceforge.pmd.lang.java.ast.ASTFinallyClause;
50
import net.sourceforge.pmd.lang.java.ast.ASTForInit;
51
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
52
import net.sourceforge.pmd.lang.java.ast.ASTForUpdate;
53
import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement;
54
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
55
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
56
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
57
import net.sourceforge.pmd.lang.java.ast.ASTInitializer;
58
import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement;
59
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
60
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
61
import net.sourceforge.pmd.lang.java.ast.ASTLoopStatement;
62
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
63
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
64
import net.sourceforge.pmd.lang.java.ast.ASTResourceList;
65
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
66
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
67
import net.sourceforge.pmd.lang.java.ast.ASTSwitchArrowBranch;
68
import net.sourceforge.pmd.lang.java.ast.ASTSwitchBranch;
69
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
70
import net.sourceforge.pmd.lang.java.ast.ASTSwitchFallthroughBranch;
71
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLike;
72
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
73
import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
74
import net.sourceforge.pmd.lang.java.ast.ASTThisExpression;
75
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
76
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
77
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
78
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression;
79
import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
80
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
81
import net.sourceforge.pmd.lang.java.ast.ASTVariableId;
82
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
83
import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement;
84
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
85
import net.sourceforge.pmd.lang.java.ast.InvocationNode;
86
import net.sourceforge.pmd.lang.java.ast.JavaNode;
87
import net.sourceforge.pmd.lang.java.ast.JavaVisitorBase;
88
import net.sourceforge.pmd.lang.java.ast.QualifiableExpression;
89
import net.sourceforge.pmd.lang.java.ast.TypeNode;
90
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
91
import net.sourceforge.pmd.lang.java.rule.bestpractices.UnusedAssignmentRule;
92
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
93
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
94
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
95
import net.sourceforge.pmd.lang.java.symbols.JFormalParamSymbol;
96
import net.sourceforge.pmd.lang.java.symbols.JLocalVariableSymbol;
97
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
98
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
99
import net.sourceforge.pmd.util.CollectionUtil;
100
import net.sourceforge.pmd.util.DataMap;
101
import net.sourceforge.pmd.util.DataMap.SimpleDataKey;
102
import net.sourceforge.pmd.util.OptionalBool;
103

104
/**
105
 * A reaching definition analysis. This may be used to check whether
106
 * eg a value escapes, or is overwritten on all code paths.
107
 */
108
public final class DataflowPass {
109

110
    // todo probably, make that non-optional. It would be useful to implement
111
    //  the flow-sensitive scopes of pattern variables
112

113
    // todo things missing for full coverage of the JLS:
114
    //  - follow `this(...)` constructor calls
115
    //  - treat `while(true)` and `do while(true)` specially
116

117
    //  see also the todo comments in UnusedAssignmentRule
118

119
    private static final SimpleDataKey<DataflowResult> DATAFLOW_RESULT_K = DataMap.simpleDataKey("java.dataflow.global");
1✔
120
    private static final SimpleDataKey<ReachingDefinitionSet> REACHING_DEFS = DataMap.simpleDataKey("java.dataflow.reaching.backwards");
1✔
121
    private static final SimpleDataKey<AssignmentEntry> VAR_DEFINITION = DataMap.simpleDataKey("java.dataflow.field.def");
1✔
122
    private static final SimpleDataKey<OptionalBool> SWITCH_BRANCH_FALLS_THROUGH = DataMap.simpleDataKey("java.dataflow.switch.fallthrough");
1✔
123
    
124
    private DataflowPass() {
125
        // utility class
126
    }
127

128
    /**
129
     * Returns the info computed by the dataflow pass for the given file.
130
     * The computation is done at most once.
131
     */
132
    public static DataflowResult getDataflowResult(ASTCompilationUnit acu) {
133
        return acu.getUserMap().computeIfAbsent(DATAFLOW_RESULT_K, () -> process(acu));
1✔
134
    }
135

136
    /**
137
     * If the var id is that of a field, returns the assignment entry that
138
     * corresponds to its definition (either blank or its initializer). From
139
     * there, using the kill record, we can draw the graph of all assignments.
140
     * Returns null if not a field, or the compilation unit has not been processed.
141
     */
142
    public static @Nullable AssignmentEntry getFieldDefinition(ASTVariableId varId) {
143
        if (!varId.isField()) {
1!
144
            return null;
×
145
        }
146
        return varId.getUserMap().get(VAR_DEFINITION);
1✔
147
    }
148

149
    private static DataflowResult process(ASTCompilationUnit node) {
150
        DataflowResult dataflowResult = new DataflowResult();
1✔
151
        for (ASTTypeDeclaration typeDecl : node.getTypeDeclarations()) {
1✔
152
            GlobalAlgoState subResult = new GlobalAlgoState();
1✔
153
            ReachingDefsVisitor.processTypeDecl(typeDecl, new SpanInfo(subResult));
1✔
154
            if (subResult.usedAssignments.size() < subResult.allAssignments.size()) {
1✔
155
                Set<AssignmentEntry> unused = subResult.allAssignments;
1✔
156
                unused.removeAll(subResult.usedAssignments);
1✔
157
                unused.removeIf(AssignmentEntry::isUnbound);
1✔
158
                unused.removeIf(AssignmentEntry::isFieldDefaultValue);
1✔
159
                dataflowResult.unusedAssignments.addAll(unused);
1✔
160
            }
161

162
            CollectionUtil.mergeMaps(
1✔
163
                dataflowResult.killRecord,
164
                subResult.killRecord,
165
                (s1, s2) -> {
166
                    s1.addAll(s2);
×
167
                    return s1;
×
168
                });
169
        }
1✔
170

171
        return dataflowResult;
1✔
172
    }
173

174
    /**
175
     * A set of reaching definitions, ie the assignments that are visible
176
     * at some point. One can use {@link DataflowResult#getReachingDefinitions(ASTNamedReferenceExpr)}
177
     * to get the data flow that reaches a variable usage (and go backwards with
178
     * the {@linkplain DataflowResult#getKillers(AssignmentEntry) kill record}).
179
     */
180
    public static final class ReachingDefinitionSet {
181

182
        static final ReachingDefinitionSet UNKNOWN = new ReachingDefinitionSet();
1✔
183
        static final ReachingDefinitionSet EMPTY_KNOWN = new ReachingDefinitionSet(emptySet());
1✔
184

185
        private Set<AssignmentEntry> reaching;
186
        private boolean isNotFullyKnown;
187
        private boolean containsInitialFieldValue;
188

189

190
        static {
191
            assert !EMPTY_KNOWN.isNotFullyKnown();
1!
192
            assert UNKNOWN.isNotFullyKnown();
1!
193
        }
1✔
194

195
        private ReachingDefinitionSet() {
1✔
196
            this.reaching = emptySet();
1✔
197
            this.containsInitialFieldValue = false;
1✔
198
            this.isNotFullyKnown = true;
1✔
199
        }
1✔
200

201
        ReachingDefinitionSet(/*Mutable*/Set<AssignmentEntry> reaching) {
1✔
202
            this.reaching = reaching;
1✔
203
            this.containsInitialFieldValue = reaching.removeIf(AssignmentEntry::isFieldAssignmentAtStartOfMethod);
1✔
204
            // not || as we want the side effect
205
            this.isNotFullyKnown = containsInitialFieldValue | reaching.removeIf(AssignmentEntry::isUnbound);
1✔
206
        }
1✔
207

208
        /** Returns the set of assignments that may reach the place. */
209
        public Set<AssignmentEntry> getReaching() {
210
            return Collections.unmodifiableSet(reaching);
1✔
211
        }
212

213
        /**
214
         * Returns true if there were some {@linkplain AssignmentEntry#isUnbound() unbound}
215
         * assignments in this set. They are not part of {@link #getReaching()}.
216
         */
217
        public boolean isNotFullyKnown() {
218
            return isNotFullyKnown;
1✔
219
        }
220

221
        /**
222
         * Contains a {@link AssignmentEntry#isFieldAssignmentAtStartOfMethod()}.
223
         * They are not part of {@link #getReaching()}.
224
         */
225
        public boolean containsInitialFieldValue() {
226
            return containsInitialFieldValue;
1✔
227
        }
228

229
        void absorb(ReachingDefinitionSet reaching) {
230
            this.containsInitialFieldValue |= reaching.containsInitialFieldValue;
1✔
231
            this.isNotFullyKnown |= reaching.isNotFullyKnown;
1✔
232
            if (this.reaching.isEmpty()) { // unmodifiable
1✔
233
                this.reaching = new LinkedHashSet<>(reaching.reaching);
1✔
234
            } else {
235
                this.reaching.addAll(reaching.reaching);
1✔
236
            }
237
        }
1✔
238

239
        public static ReachingDefinitionSet unknown() {
240
            return new ReachingDefinitionSet();
1✔
241
        }
242

243
        public static ReachingDefinitionSet blank() {
244
            return new ReachingDefinitionSet(emptySet());
1✔
245
        }
246
    }
247

248
    /**
249
     * Global result of the dataflow analysis.
250
     */
251
    // this is a façade class
252
    public static final class DataflowResult {
1✔
253

254
        final Set<AssignmentEntry> unusedAssignments;
255
        final Map<AssignmentEntry, Set<AssignmentEntry>> killRecord;
256

257

258
        DataflowResult() {
1✔
259
            this.unusedAssignments = new LinkedHashSet<>();
1✔
260
            this.killRecord = new LinkedHashMap<>();
1✔
261
        }
1✔
262

263
        /**
264
         * To be interpreted by {@link  UnusedAssignmentRule}.
265
         */
266
        public Set<AssignmentEntry> getUnusedAssignments() {
267
            return Collections.unmodifiableSet(unusedAssignments);
1✔
268
        }
269

270
        /**
271
         * May be useful to check for reassignment.
272
         */
273
        public @NonNull Set<AssignmentEntry> getKillers(AssignmentEntry assignment) {
274
            return killRecord.getOrDefault(assignment, emptySet());
1✔
275
        }
276

277
        // These methods are only valid to be called if the dataflow pass has run.
278
        // This is why they are instance methods here: by asking for the DataflowResult
279
        // instance to get access to them, you ensure that the pass has been executed properly.
280

281
        /**
282
         * Returns whether the switch branch falls-through to the next one (or the end of the switch).
283
         */
284
        public @NonNull OptionalBool switchBranchFallsThrough(ASTSwitchBranch b) {
285
            if (b instanceof ASTSwitchFallthroughBranch) {
1!
286
                return Objects.requireNonNull(b.getUserMap().get(SWITCH_BRANCH_FALLS_THROUGH));
1✔
287
            }
288
            return OptionalBool.NO;
×
289
        }
290

291

292
        public @NonNull ReachingDefinitionSet getReachingDefinitions(ASTNamedReferenceExpr expr) {
293
            return expr.getUserMap().computeIfAbsent(REACHING_DEFS, () -> reachingFallback(expr));
1✔
294
        }
295

296
        // Fallback, to compute reaching definitions for some nodes
297
        // that are not tracked by the tree exploration. Final fields
298
        // indeed have a fully known set of reaching definitions.
299
        private @NonNull ReachingDefinitionSet reachingFallback(ASTNamedReferenceExpr expr) {
300
            JVariableSymbol sym = expr.getReferencedSym();
1✔
301
            if (sym == null || sym.isField() && !sym.isFinal()) {
1!
302
                return ReachingDefinitionSet.unknown();
1✔
303
            } else if (!sym.isField()) {
1✔
304
                ASTVariableId node = sym.tryGetNode();
1✔
305
                assert node != null
1!
306
                    : "Not a field, and symbol is known, so should be a local which has a node";
307
                if (node.isLocalVariable()) {
1✔
308
                    assert node.getInitializer() == null : "Should be a blank local variable";
1!
309
                    return ReachingDefinitionSet.blank();
1✔
310
                } else {
311
                    // Formal parameter or other kind of def which has
312
                    // an implicit initializer.
313
                    return ReachingDefinitionSet.unknown();
1✔
314
                }
315
            }
316

317
            ASTVariableId node = sym.tryGetNode();
1✔
318
            if (node == null) {
1✔
319
                return ReachingDefinitionSet.unknown(); // we don't care about non-local declarations
1✔
320
            }
321
            Set<AssignmentEntry> assignments = node.getLocalUsages()
1✔
322
                                                   .stream()
1✔
323
                                                   .filter(it -> it.getAccessType() == AccessType.WRITE)
1✔
324
                                                   .map(usage -> {
1✔
325
                                                       JavaNode parent = usage.getParent();
1✔
326
                                                       if (parent instanceof ASTUnaryExpression
1!
327
                                                           && !((ASTUnaryExpression) parent).getOperator().isPure()) {
×
328
                                                           return parent;
×
329
                                                       } else if (usage.getIndexInParent() == 0
1!
330
                                                           && parent instanceof ASTAssignmentExpression) {
331
                                                           return ((ASTAssignmentExpression) parent).getRightOperand();
1✔
332
                                                       } else {
333
                                                           return null;
×
334
                                                       }
335
                                                   }).filter(Objects::nonNull)
1✔
336
                                                   .map(it -> new AssignmentEntry(sym, node, it))
1✔
337
                                                   .collect(CollectionUtil.toMutableSet());
1✔
338

339
            ASTExpression init = node.getInitializer(); // this one is not in the usages
1✔
340
            if (init != null) {
1✔
341
                assignments.add(new AssignmentEntry(sym, node, init));
1✔
342
            }
343

344
            return new ReachingDefinitionSet(assignments);
1✔
345
        }
346
    }
347

348
    private static final class ReachingDefsVisitor extends JavaVisitorBase<SpanInfo, SpanInfo> {
1✔
349

350
        // The class scope for the "this" reference, used to find fields
351
        // of this class
352
        private final @NonNull JClassSymbol enclosingClassScope;
353
        private final boolean inStaticCtx;
354

355
        private ReachingDefsVisitor(@NonNull JClassSymbol scope, boolean inStaticCtx) {
1✔
356
            this.enclosingClassScope = scope;
1✔
357
            this.inStaticCtx = inStaticCtx;
1✔
358
        }
1✔
359

360
        /**
361
         * If true, we're also tracking fields of the {@code this} instance,
362
         * because we're in a ctor or initializer, or instance method.
363
         */
364
        private boolean trackThisInstance() {
365
            return !inStaticCtx;
1✔
366
        }
367

368
        // following deals with control flow structures
369

370
        @Override
371
        protected SpanInfo visitChildren(Node node, SpanInfo data) {
372
            for (Node child : node.children()) {
1✔
373
                // each output is passed as input to the next (most relevant for blocks)
374
                data = child.acceptVisitor(this, data);
1✔
375
            }
1✔
376
            return data;
1✔
377
        }
378

379
        @Override
380
        public SpanInfo visit(ASTBlock node, final SpanInfo data) {
381
            return processBreakableStmt(node, data, () -> {
1✔
382
                // variables local to a loop iteration must be killed before the
383
                // next iteration
384

385
                SpanInfo state = data;
1✔
386
                List<ASTVariableId> localsToKill = new ArrayList<>(0);
1✔
387

388
                for (JavaNode child : node.children()) {
1✔
389
                    // each output is passed as input to the next (most relevant for blocks)
390
                    state = acceptOpt(child, state);
1✔
391
                    if (child instanceof ASTLocalVariableDeclaration) {
1✔
392
                        for (ASTVariableId id : (ASTLocalVariableDeclaration) child) {
1✔
393
                            localsToKill.add(id);
1✔
394
                        }
1✔
395
                    }
396
                }
1✔
397

398
                for (ASTVariableId var : localsToKill) {
1✔
399
                    state.deleteVar(var.getSymbol());
1✔
400
                }
1✔
401

402
                return state;
1✔
403
            });
404
        }
405

406
        @Override
407
        public SpanInfo visit(ASTSwitchStatement node, SpanInfo data) {
408
            return processSwitch(node, data);
1✔
409
        }
410

411
        @Override
412
        public SpanInfo visit(ASTSwitchExpression node, SpanInfo data) {
413
            return processSwitch(node, data);
1✔
414
        }
415

416
        private SpanInfo processSwitch(ASTSwitchLike switchLike, SpanInfo data) {
417
            GlobalAlgoState global = data.global;
1✔
418
            SpanInfo before = acceptOpt(switchLike.getTestedExpression(), data);
1✔
419

420
            SpanInfo breakTarget = before.fork();
1✔
421
            global.breakTargets.push(breakTarget);
1✔
422
            accLabels(switchLike, global, breakTarget, null);
1✔
423

424
            // If switch non-total then there is a path where the switch completes normally
425
            // (value not matched).
426
            // Todo make that an attribute of ASTSwitchLike, check for totality when pattern matching is involved
427
            boolean isTotal = switchLike.hasDefaultCase()
1✔
428
                || switchLike instanceof ASTSwitchExpression
429
                || switchLike.isExhaustiveEnumSwitch();
1✔
430

431
            PSet<SpanInfo> successors = HashTreePSet.empty();
1✔
432
            boolean allBranchesCompleteAbruptly = true;
1✔
433
            SpanInfo current = before;
1✔
434
            for (ASTSwitchBranch branch : switchLike.getBranches()) {
1✔
435
                if (branch instanceof ASTSwitchArrowBranch) {
1✔
436
                    current = acceptOpt(((ASTSwitchArrowBranch) branch).getRightHandSide(), before.fork());
1✔
437
                    current = global.breakTargets.doBreak(current, null); // process this as if it was followed by a break
1✔
438
                } else {
439
                    // fallthrough branch
440
                    current = acceptOpt(branch, before.fork().absorb(current));
1✔
441
                    OptionalBool isFallingThrough = current.hasCompletedAbruptly.complement();
1✔
442
                    branch.getUserMap().set(SWITCH_BRANCH_FALLS_THROUGH, isFallingThrough);
1✔
443
                    successors = CollectionUtil.union(successors, current.abruptCompletionTargets);
1✔
444
                    allBranchesCompleteAbruptly &= current.hasCompletedAbruptly.isTrue();
1✔
445

446
                    if (isFallingThrough == OptionalBool.NO) {
1✔
447
                        current = before.fork();
1✔
448
                    }
449
                }
450
            }
1✔
451

452
            before = global.breakTargets.pop();
1✔
453

454
            PSet<@Nullable SpanInfo> externalTargets = successors.minus(before);
1✔
455
            OptionalBool switchCompletesAbruptly;
456
            if (isTotal && allBranchesCompleteAbruptly && externalTargets.equals(successors)) {
1✔
457
                // then all branches complete abruptly, and none of them because of a break to this switch
458
                switchCompletesAbruptly = OptionalBool.YES;
1✔
459
            } else if (successors.isEmpty() || asSingle(successors) == breakTarget) { // NOPMD CompareObjectsWithEqual this is what we want
1✔
460
                // then the branches complete normally, or they just break the switch
461
                switchCompletesAbruptly = OptionalBool.NO;
1✔
462
            } else {
463
                switchCompletesAbruptly = OptionalBool.UNKNOWN;
1✔
464
            }
465

466
            // join with the last state, which is the exit point of the
467
            // switch, if it's not closed by a break;
468
            SpanInfo result = before.absorb(current);
1✔
469
            result.hasCompletedAbruptly = switchCompletesAbruptly;
1✔
470
            result.abruptCompletionTargets = externalTargets;
1✔
471
            return result;
1✔
472
        }
473

474
        @Override
475
        public SpanInfo visit(ASTIfStatement node, SpanInfo data) {
476
            return processBreakableStmt(node, data, () -> makeConditional(data, node.getCondition(), node.getThenBranch(), node.getElseBranch()));
1✔
477
        }
478

479
        @Override
480
        public SpanInfo visit(ASTConditionalExpression node, SpanInfo data) {
481
            return makeConditional(data, node.getCondition(), node.getThenBranch(), node.getElseBranch());
1✔
482
        }
483

484
        SpanInfo makeConditional(SpanInfo before, ASTExpression condition, JavaNode thenBranch, JavaNode elseBranch) {
485
            SpanInfo thenState = before.fork();
1✔
486
            SpanInfo elseState = elseBranch != null ? before.fork() : before;
1✔
487

488
            linkConditional(before, condition, thenState, elseState, true);
1✔
489

490
            thenState = acceptOpt(thenBranch, thenState);
1✔
491
            elseState = acceptOpt(elseBranch, elseState);
1✔
492

493
            return elseState.absorb(thenState);
1✔
494
        }
495

496
        /*
497
         * This recursive procedure translates shortcut conditionals
498
         * that occur in condition position in the following way:
499
         *
500
         * if (a || b) <then>                  if (a)      <then>
501
         * else <else>               ~>        else
502
         *                                       if (b)    <then>
503
         *                                       else      <else>
504
         *
505
         *
506
         * if (a && b) <then>                  if (a)
507
         * else <else>               ~>          if (b)    <then>
508
         *                                       else      <else>
509
         *                                     else        <else>
510
         *
511
         * The new conditions are recursively processed to translate
512
         * bigger conditions, like `a || b && c`
513
         *
514
         * This is how it works, but the <then> and <else> branch are
515
         * visited only once, because it's not done in this method, but
516
         * in makeConditional.
517
         *
518
         * @return the state in which all expressions have been evaluated
519
         *      Eg for `a || b`, this is the `else` state (all evaluated to false)
520
         *      Eg for `a && b`, this is the `then` state (all evaluated to true)
521
         *
522
         */
523
        private SpanInfo linkConditional(SpanInfo before, ASTExpression condition, SpanInfo thenState, SpanInfo elseState, boolean isTopLevel) {
524
            if (condition == null) {
1✔
525
                return before;
1✔
526
            }
527

528
            if (condition instanceof ASTInfixExpression) {
1✔
529
                BinaryOp op = ((ASTInfixExpression) condition).getOperator();
1✔
530
                if (op == BinaryOp.CONDITIONAL_OR) {
1✔
531
                    return visitShortcutOrExpr((ASTInfixExpression) condition, before, thenState, elseState);
1✔
532
                } else if (op == BinaryOp.CONDITIONAL_AND) {
1✔
533
                    // To mimic a shortcut AND expr, swap the thenState and the elseState
534
                    // See explanations in method
535
                    return visitShortcutOrExpr((ASTInfixExpression) condition, before, elseState, thenState);
1✔
536
                }
537
            }
538

539
            SpanInfo state = acceptOpt(condition, before);
1✔
540
            if (isTopLevel) {
1✔
541
                thenState.absorb(state);
1✔
542
                elseState.absorb(state);
1✔
543
            }
544
            return state;
1✔
545
        }
546

547
        SpanInfo visitShortcutOrExpr(ASTInfixExpression orExpr,
548
                                     SpanInfo before,
549
                                     SpanInfo thenState,
550
                                     SpanInfo elseState) {
551

552
            //  if (<a> || <b> || ... || <n>) <then>
553
            //  else <else>
554
            //
555
            // in <then>, we are sure that at least <a> was evaluated,
556
            // but really any prefix of <a> ... <n> is possible so they're all merged
557

558
            // in <else>, we are sure that all of <a> ... <n> were evaluated (to false)
559

560
            // If you replace || with &&, then the above holds if you swap <then> and <else>
561
            // So this method handles the OR expr, the caller can swap the arguments to make an AND
562

563
            // ---
564
            // This method side effects on thenState and elseState to
565
            // set the variables.
566

567
            SpanInfo cur = before;
1✔
568
            cur = linkConditional(cur, orExpr.getLeftOperand(), thenState, elseState, false);
1✔
569
            thenState.absorb(cur);
1✔
570
            cur = linkConditional(cur, orExpr.getRightOperand(), thenState, elseState, false);
1✔
571
            thenState.absorb(cur);
1✔
572

573
            elseState.absorb(cur);
1✔
574

575
            return cur;
1✔
576
        }
577

578
        @Override
579
        public SpanInfo visit(ASTSynchronizedStatement node, SpanInfo data) {
580
            return processBreakableStmt(node, data, () -> {
1✔
581
                // visit lock expr and child block
582
                SpanInfo body = super.visit(node, data);
1✔
583
                // We should assume that all assignments may be observed by other threads
584
                // at the end of the critical section.
585
                useAllSelfFields(body, JavaAstUtils.isInStaticCtx(node), enclosingClassScope);
1✔
586
                return body;
1✔
587
            });
588
        }
589

590
        @Override
591
        public SpanInfo visit(ASTTryStatement node, final SpanInfo before) {
592

593
            /*
594
                <before>
595
                try (<resources>) {
596
                    <body>
597
                } catch (IOException e) {
598
                    <catch>
599
                } finally {
600
                    <finally>
601
                }
602
                <end>
603

604
                There is a path      <before> -> <resources> -> <body> -> <finally> -> <end>
605
                and for each catch,  <before> -> <catch> -> <finally> -> <end>
606

607
                Except that abrupt completion before the <finally> jumps
608
                to the <finally> and completes abruptly for the same
609
                reason (if the <finally> completes normally), which
610
                means it doesn't go to <end>
611
             */
612
            ASTFinallyClause finallyClause = node.getFinallyClause();
1✔
613

614
            SpanInfo finalState = processBreakableStmt(node, before, () -> {
1✔
615

616
                if (finallyClause != null) {
1✔
617
                    before.myFinally = before.forkEmpty();
1✔
618
                }
619

620
                final List<ASTCatchClause> catchClauses = node.getCatchClauses().toList();
1✔
621
                final List<SpanInfo> catchSpans = catchClauses.isEmpty() ? Collections.emptyList()
1✔
622
                                                                         : new ArrayList<>();
1✔
623

624
                // pre-fill catch spans
625
                for (int i = 0; i < catchClauses.size(); i++) {
1✔
626
                    catchSpans.add(before.forkEmpty());
1✔
627
                }
628

629
                @Nullable ASTResourceList resources = node.getResources();
1✔
630

631
                SpanInfo bodyState = before.fork();
1✔
632
                bodyState = bodyState.withCatchBlocks(catchSpans);
1✔
633
                bodyState = acceptOpt(resources, bodyState);
1✔
634
                bodyState = acceptOpt(node.getBody(), bodyState);
1✔
635
                bodyState = bodyState.withCatchBlocks(Collections.emptyList());
1✔
636

637
                SpanInfo exceptionalState = null;
1✔
638
                int i = 0;
1✔
639
                for (ASTCatchClause catchClause : node.getCatchClauses()) {
1✔
640
                    /*
641
                        Note: here we absorb the end state of the body, which is not necessary.
642
                        We do that to conform to the language's definition of "effective-finality",
643
                        which is more conservative than needed. Doing this fixes FPs in LocalVariableCouldBeFinal
644
                        at the cost of some FNs in UnusedAssignment.
645
                     */
646
                    SpanInfo catchSpan = catchSpans.get(i);
1✔
647
                    catchSpan.absorb(bodyState);
1✔
648

649
                    SpanInfo current = acceptOpt(catchClause, catchSpan);
1✔
650
                    exceptionalState = current.absorb(exceptionalState);
1✔
651
                    i++;
1✔
652
                }
1✔
653
                return bodyState.absorb(exceptionalState);
1✔
654
            });
655

656
            if (finallyClause != null) {
1✔
657
                if (finalState.abruptCompletionTargets.contains(finalState.returnOrThrowTarget)) {
1✔
658
                    // this represents the finally clause when it was entered
659
                    // because of abrupt completion
660
                    // since we don't know when it terminated we must join it with before
661
                    SpanInfo abruptFinally = before.myFinally.absorb(before);
1✔
662
                    acceptOpt(finallyClause, abruptFinally);
1✔
663
                    before.myFinally = null;
1✔
664
                    abruptFinally.abruptCompletionByThrow(false); // propagate to enclosing catch/finallies
1✔
665
                }
666

667
                // this is the normal finally
668
                finalState = acceptOpt(finallyClause, finalState);
1✔
669
                // then all break targets are successors of the finally
670
                for (SpanInfo target : finalState.abruptCompletionTargets) {
1✔
671
                    // Then there is a return or throw within the try or catch blocks.
672
                    // Control first passes to the finally, then tries to get out of the function
673
                    // (stopping on finally).
674
                    // before.myFinally = null;
675
                    //finalState.abruptCompletionByThrow(false); // propagate to enclosing catch/finallies
676
                    target.absorb(finalState);
1✔
677
                }
1✔
678
            }
679

680
            // In the 7.0 grammar, the resources should be explicitly
681
            // used here. For now they don't trigger anything as their
682
            // node is not a VariableId. There's a test to
683
            // check that.
684

685
            return finalState;
1✔
686
        }
687

688
        @Override
689
        public SpanInfo visit(ASTCatchClause node, SpanInfo data) {
690
            SpanInfo result = visitJavaNode(node, data);
1✔
691
            result.deleteVar(node.getParameter().getVarId().getSymbol());
1✔
692
            return result;
1✔
693
        }
694

695
        @Override
696
        public SpanInfo visit(ASTLambdaExpression node, SpanInfo data) {
697
            // Lambda expression have control flow that is separate from the method
698
            // So we fork the context, but don't join it
699

700
            // Reaching definitions of the enclosing context still reach in the lambda
701
            // Since those definitions are [effectively] final, they actually can't be
702
            // killed, but they can be used in the lambda
703

704
            SpanInfo before = data;
1✔
705

706
            JavaNode lambdaBody = node.getChild(node.getNumChildren() - 1);
1✔
707
            // if it's an expression, then no assignments may occur in it,
708
            // but it can still use some variables of the context
709
            acceptOpt(lambdaBody, before.forkCapturingNonLocal());
1✔
710
            return before;
1✔
711
        }
712

713
        @Override
714
        public SpanInfo visit(ASTWhileStatement node, SpanInfo data) {
715
            return handleLoop(node, data, null, node.getCondition(), null, node.getBody(), true, null);
1✔
716
        }
717

718
        @Override
719
        public SpanInfo visit(ASTDoStatement node, SpanInfo data) {
720
            return handleLoop(node, data, null, node.getCondition(), null, node.getBody(), false, null);
1✔
721
        }
722

723
        @Override
724
        public SpanInfo visit(ASTForeachStatement node, SpanInfo data) {
725
            ASTStatement body = node.getBody();
1✔
726
            ASTExpression init = node.getIterableExpr();
1✔
727
            return handleLoop(node, data, init, null, null, body, true, node.getVarId());
1✔
728
        }
729

730
        @Override
731
        public SpanInfo visit(ASTForStatement node, SpanInfo data) {
732
            ASTStatement body = node.getBody();
1✔
733
            ASTForInit init = node.firstChild(ASTForInit.class);
1✔
734
            ASTExpression cond = node.getCondition();
1✔
735
            ASTForUpdate update = node.firstChild(ASTForUpdate.class);
1✔
736
            return handleLoop(node, data, init, cond, update, body, true, null);
1✔
737
        }
738

739

740
        private SpanInfo handleLoop(ASTLoopStatement loop,
741
                                    SpanInfo before,
742
                                    JavaNode init,
743
                                    ASTExpression cond,
744
                                    JavaNode update,
745
                                    ASTStatement body,
746
                                    boolean checkFirstIter,
747
                                    ASTVariableId foreachVar) {
748

749
            //todo while(true) and do {}while(true); are special-cased
750
            // by the compiler and there is no fork
751

752
            SpanInfo breakTarget = before.forkEmpty();
1✔
753
            SpanInfo continueTarget = before.forkEmpty();
1✔
754
            pushTargets(loop, breakTarget, continueTarget);
1✔
755

756
            // perform a few "iterations", to make sure that assignments in
757
            // the body can affect themselves in the next iteration, and
758
            // that they affect the condition, etc
759

760
            before = acceptOpt(init, before);
1✔
761
            if (checkFirstIter && cond != null) { // false for do-while
1✔
762
                SpanInfo ifcondTrue = before.forkEmpty();
1✔
763
                linkConditional(before, cond, ifcondTrue, breakTarget, true);
1✔
764
                before = ifcondTrue;
1✔
765
            }
766

767
            if (foreachVar != null) {
1✔
768
                // in foreach loops, the loop variable is assigned before the first iteration
769
                before.assign(foreachVar.getSymbol(), foreachVar);
1✔
770
            }
771

772

773
            // make the defs of the body reach the other parts of the loop,
774
            // including itself
775
            SpanInfo iter = acceptOpt(body, before.fork());
1✔
776

777
            // Make assignments that reached a continue reach the condition block before next iteration
778
            iter.absorb(continueTarget);
1✔
779

780
            if (foreachVar != null && iter.hasVar(foreachVar)) {
1!
781
                // in foreach loops, the loop variable is reassigned on each update
782
                iter.assign(foreachVar.getSymbol(), foreachVar);
1✔
783
            } else {
784
                iter = acceptOpt(update, iter);
1✔
785
            }
786

787
            linkConditional(iter, cond, iter, breakTarget, true);
1✔
788
            // do a second round to make sure assignments can reach themselves.
789
            iter = acceptOpt(body, iter);
1✔
790

791
            SpanInfo result = popTargets(loop, breakTarget, continueTarget);
1✔
792
            result.absorb(iter);
1✔
793
            if (checkFirstIter) {
1✔
794
                // if the first iteration is checked,
795
                // then it could be false on the first try, meaning
796
                // the definitions before the loop reach after too
797
                result.absorb(before);
1✔
798
            }
799

800
            if (foreachVar != null) {
1✔
801
                result.deleteVar(foreachVar.getSymbol());
1✔
802
            }
803

804
            // These targets are now obsolete
805
            result.abruptCompletionTargets =
1✔
806
                result.abruptCompletionTargets.minus(breakTarget).minus(continueTarget);
1✔
807
            return result;
1✔
808
        }
809

810
        /**
811
         * Process a statement that may be broken out of if it is annotated with a label.
812
         * This is theoretically all statements, as all of them may be annotated. However,
813
         * some statements may not contain a break. Eg if a return statement has a label,
814
         * the label can never be used. The weirdest example is probably an annotated break
815
         * statement, which may break out of itself.
816
         *
817
         * <p>Try statements are handled specially because of the finally.
818
         */
819
        private SpanInfo processBreakableStmt(ASTStatement statement, SpanInfo input, Supplier<SpanInfo> processFun) {
820
            if (!(statement.getParent() instanceof ASTLabeledStatement)) {
1✔
821
                // happy path, no labels
822
                return processFun.get();
1✔
823
            }
824
            GlobalAlgoState globalState = input.global;
1✔
825
            // this will be filled with the reaching defs of the break statements, then merged with the actual exit state
826
            SpanInfo placeholderForExitState = input.forkEmpty();
1✔
827

828
            PSet<String> labels = accLabels(statement, globalState, placeholderForExitState, null);
1✔
829
            SpanInfo endState = processFun.get();
1✔
830

831
            // remove the labels
832
            globalState.breakTargets.namedTargets.keySet().removeAll(labels);
1✔
833
            SpanInfo result = endState.absorb(placeholderForExitState);
1✔
834
            result.abruptCompletionTargets = result.abruptCompletionTargets.minus(placeholderForExitState);
1✔
835
            return result;
1✔
836
        }
837

838
        private static PSet<String> accLabels(JavaNode statement, GlobalAlgoState globalState, SpanInfo breakTarget, @Nullable SpanInfo continueTarget) {
839
            Node parent = statement.getParent();
1✔
840
            PSet<String> labels = HashTreePSet.empty();
1✔
841
            // collect labels and give a name to the exit state.
842
            while (parent instanceof ASTLabeledStatement) {
1✔
843
                String label = ((ASTLabeledStatement) parent).getLabel();
1✔
844
                labels = labels.plus(label);
1✔
845
                globalState.breakTargets.namedTargets.put(label, breakTarget);
1✔
846
                if (continueTarget != null) {
1✔
847
                    globalState.continueTargets.namedTargets.put(label, continueTarget);
1✔
848
                }
849
                parent = parent.getParent();
1✔
850
            }
1✔
851
            return labels;
1✔
852
        }
853

854
        private void pushTargets(ASTLoopStatement loop, SpanInfo breakTarget, SpanInfo continueTarget) {
855
            GlobalAlgoState globalState = breakTarget.global;
1✔
856
            accLabels(loop, globalState, breakTarget, continueTarget);
1✔
857
            globalState.breakTargets.unnamedTargets.push(breakTarget);
1✔
858
            globalState.continueTargets.unnamedTargets.push(continueTarget);
1✔
859
        }
1✔
860

861
        private SpanInfo popTargets(ASTLoopStatement loop, SpanInfo breakTarget, SpanInfo continueTarget) {
862
            GlobalAlgoState globalState = breakTarget.global;
1✔
863
            globalState.breakTargets.unnamedTargets.pop();
1✔
864
            globalState.continueTargets.unnamedTargets.pop();
1✔
865

866
            SpanInfo total = breakTarget.absorb(continueTarget);
1✔
867

868
            Node parent = loop.getParent();
1✔
869
            while (parent instanceof ASTLabeledStatement) {
1✔
870
                String label = ((ASTLabeledStatement) parent).getLabel();
1✔
871
                total = total.absorb(globalState.breakTargets.namedTargets.remove(label));
1✔
872
                total = total.absorb(globalState.continueTargets.namedTargets.remove(label));
1✔
873
                parent = parent.getParent();
1✔
874
            }
1✔
875
            return total;
1✔
876
        }
877

878
        private SpanInfo acceptOpt(JavaNode node, SpanInfo before) {
879
            return node == null ? before : node.acceptVisitor(this, before);
1✔
880
        }
881

882
        @Override
883
        public SpanInfo visit(ASTContinueStatement node, SpanInfo data) {
884
            return data.global.continueTargets.doBreak(data, node.getImage());
1✔
885
        }
886

887
        @Override
888
        public SpanInfo visit(ASTBreakStatement node, SpanInfo data) {
889
            return processBreakableStmt(node, data, () -> data.global.breakTargets.doBreak(data, node.getImage()));
1✔
890
        }
891

892
        @Override
893
        public SpanInfo visit(ASTYieldStatement node, SpanInfo data) {
894
            super.visit(node, data); // visit expression
1✔
895

896
            // treat as break, ie abrupt completion + link reaching defs to outer context
897
            return data.global.breakTargets.doBreak(data, null);
1✔
898
        }
899

900

901
        // both of those exit the scope of the method/ctor, so their assignments go dead
902

903
        @Override
904
        public SpanInfo visit(ASTThrowStatement node, SpanInfo data) {
905
            super.visit(node, data);
1✔
906
            return data.abruptCompletionByThrow(false);
1✔
907
        }
908

909
        @Override
910
        public SpanInfo visit(ASTReturnStatement node, SpanInfo data) {
911
            super.visit(node, data);
1✔
912
            return data.abruptCompletion(data.returnOrThrowTarget);
1✔
913
        }
914

915
        // following deals with assignment
916

917
        @Override
918
        public SpanInfo visit(ASTFormalParameter node, SpanInfo data) {
919
            data.declareBlank(node.getVarId());
1✔
920
            return data;
1✔
921
        }
922

923
        @Override
924
        public SpanInfo visit(ASTCatchParameter node, SpanInfo data) {
925
            data.declareBlank(node.getVarId());
1✔
926
            return data;
1✔
927
        }
928

929
        @Override
930
        public SpanInfo visit(ASTVariableDeclarator node, SpanInfo data) {
931
            JVariableSymbol var = node.getVarId().getSymbol();
1✔
932
            ASTExpression rhs = node.getInitializer();
1✔
933
            if (rhs != null) {
1✔
934
                rhs.acceptVisitor(this, data);
1✔
935
                data.assign(var, rhs);
1✔
936
            } else if (isAssignedImplicitly(node.getVarId())) {
1✔
937
                data.declareBlank(node.getVarId());
1✔
938
            }
939
            return data;
1✔
940
        }
941

942
        @Override
943
        public SpanInfo visit(ASTCompactConstructorDeclaration node, SpanInfo data) {
944
            // Visiting a method/ctor declaration declares formal parameters.
945
            // In the compact ctor decl there is no formal parameter nodes, so
946
            // we need to declare these explicitly
947
            JConstructorSymbol ctorSym = node.getSymbol();
1✔
948
            for (JFormalParamSymbol formal : ctorSym.getFormalParameters()) {
1✔
949
                ASTVariableId varId = formal.tryGetNode();
1✔
950
                assert varId != null && varId.isRecordComponent();
1!
951
                data.assign(formal, varId);
1✔
952
            }
1✔
953
            // visit the body
954
            data = super.visit(node, data);
1✔
955

956
            // At the end of the ctor, the formals are each used to assign to their respective
957
            // fields.
958
            for (JFormalParamSymbol formal : ctorSym.getFormalParameters()) {
1✔
959
                data.use(formal, null);
1✔
960
            }
1✔
961

962
            return data;
1✔
963
        }
964

965
        /**
966
         * Whether the variable has an implicit initializer, that is not
967
         * an expression. For instance, formal parameters have a value
968
         * within the method, same for exception parameters, foreach variables,
969
         * fields (default value), etc. Only blank local variables have
970
         * no initial value.
971
         */
972
        private boolean isAssignedImplicitly(ASTVariableId var) {
973
            return !var.isLocalVariable() || var.isForeachVariable();
1!
974
        }
975

976
        @Override
977
        public SpanInfo visit(ASTUnaryExpression node, SpanInfo data) {
978
            data = acceptOpt(node.getOperand(), data);
1✔
979

980
            if (node.getOperator().isPure()) {
1✔
981
                return data;
1✔
982
            } else {
983
                return processAssignment(node.getOperand(), node, true, data);
1✔
984
            }
985
        }
986

987
        @Override
988
        public SpanInfo visit(ASTAssignmentExpression node, SpanInfo data) {
989
            // visit operands in order
990
            data = acceptOpt(node.getRightOperand(), data);
1✔
991
            data = acceptOpt(node.getLeftOperand(), data);
1✔
992

993
            return processAssignment(node.getLeftOperand(),
1✔
994
                                     node.getRightOperand(),
1✔
995
                                     node.getOperator().isCompound(),
1✔
996
                                     data);
997

998
        }
999

1000
        private SpanInfo processAssignment(ASTExpression lhs0, // LHS or unary operand
1001
                                           ASTExpression rhs,  // RHS or unary
1002
                                           boolean useBeforeAssigning,
1003
                                           SpanInfo result) {
1004

1005
            if (lhs0 instanceof ASTNamedReferenceExpr) {
1✔
1006
                ASTNamedReferenceExpr lhs = (ASTNamedReferenceExpr) lhs0;
1✔
1007
                JVariableSymbol lhsVar = lhs.getReferencedSym();
1✔
1008
                if (lhsVar != null
1✔
1009
                    && (lhsVar instanceof JLocalVariableSymbol
1010
                    || isRelevantField(lhs))) {
1✔
1011

1012
                    if (useBeforeAssigning) {
1✔
1013
                        // compound assignment, to use BEFORE assigning
1014
                        result.use(lhsVar, lhs);
1✔
1015
                    }
1016

1017
                    VarLocalInfo oldVar = result.assign(lhsVar, rhs);
1✔
1018
                    SpanInfo.updateReachingDefs(lhs, lhsVar, oldVar);
1✔
1019
                }
1020
            }
1021
            return result;
1✔
1022
        }
1023

1024
        private boolean isRelevantField(ASTExpression lhs) {
1025
            return lhs instanceof ASTNamedReferenceExpr && (trackThisInstance() && JavaAstUtils.isThisFieldAccess(lhs)
1!
1026
                || isStaticFieldOfThisClass(((ASTNamedReferenceExpr) lhs).getReferencedSym()));
1✔
1027
        }
1028

1029
        private boolean isStaticFieldOfThisClass(JVariableSymbol var) {
1030
            return var instanceof JFieldSymbol
1!
1031
                && ((JFieldSymbol) var).isStatic()
1✔
1032
                && enclosingClassScope.equals(((JFieldSymbol) var).getEnclosingClass());
1!
1033
        }
1034

1035
        private static JVariableSymbol getVarIfUnaryAssignment(ASTUnaryExpression node) { // NOPMD UnusedPrivateMethod
1036
            ASTExpression operand = node.getOperand();
1✔
1037
            if (!node.getOperator().isPure() && operand instanceof ASTNamedReferenceExpr) {
1!
1038
                return ((ASTNamedReferenceExpr) operand).getReferencedSym();
1✔
1039
            }
1040
            return null;
1✔
1041
        }
1042

1043
        // variable usage
1044

1045
        @Override
1046
        public SpanInfo visit(ASTVariableAccess node, SpanInfo data) {
1047
            if (node.getAccessType() == AccessType.READ) {
1✔
1048
                data.use(node.getReferencedSym(), node);
1✔
1049
            }
1050
            return data;
1✔
1051
        }
1052

1053
        @Override
1054
        public SpanInfo visit(ASTFieldAccess node, SpanInfo data) {
1055
            data = node.getQualifier().acceptVisitor(this, data);
1✔
1056
            if (node.getAccessType() == AccessType.READ) {
1✔
1057
                data.use(node.getReferencedSym(), node);
1✔
1058
            }
1059
            return data;
1✔
1060
        }
1061

1062
        @Override
1063
        public SpanInfo visit(ASTThisExpression node, SpanInfo data) {
1064
            if (trackThisInstance() && isThisExprLeaking(node)) {
1✔
1065
                data.recordThisLeak(enclosingClassScope, node);
1✔
1066
            }
1067
            return data;
1✔
1068
        }
1069

1070
        private static boolean isThisExprLeaking(ASTThisExpression node) {
1071
            boolean isAllowed = node.getParent() instanceof ASTFieldAccess
1✔
1072
                || node.getParent() instanceof ASTSynchronizedStatement;
1✔
1073
            return !isAllowed;
1✔
1074
        }
1075

1076
        @Override
1077
        public SpanInfo visit(ASTMethodCall node, SpanInfo state) {
1078
            if (trackThisInstance() && JavaAstUtils.isCallOnThisInstance(node) != OptionalBool.NO) {
1✔
1079
                state.recordThisLeak(enclosingClassScope, node);
1✔
1080
            }
1081
            return visitInvocationExpr(node, state);
1✔
1082
        }
1083

1084
        @Override
1085
        public SpanInfo visit(ASTConstructorCall node, SpanInfo state) {
1086
            state = visitInvocationExpr(node, state);
1✔
1087
            acceptOpt(node.getAnonymousClassDeclaration(), state);
1✔
1088
            return state;
1✔
1089
        }
1090

1091
        @Override
1092
        public SpanInfo visit(ASTArrayAllocation node, SpanInfo state) {
1093
            state = acceptOpt(node.getArrayInitializer(), state);
1✔
1094
            state = acceptOpt(node.getTypeNode().getDimensions(), state);
1✔
1095
            // May throw OOM error for instance. This abrupt completion routine is
1096
            // noop if we are outside a try block.
1097
            state.abruptCompletionByThrow(true);
1✔
1098
            return state;
1✔
1099
        }
1100

1101
        private <T extends InvocationNode & QualifiableExpression> SpanInfo visitInvocationExpr(T node, SpanInfo state) {
1102
            state = acceptOpt(node.getQualifier(), state);
1✔
1103
            state = acceptOpt(node.getArguments(), state);
1✔
1104

1105
            // todo In 7.0, with the precise type/overload resolution, we
1106
            //  could only target methods that throw checked exceptions
1107
            //  (unless some catch block catches an unchecked exceptions)
1108

1109
            state.abruptCompletionByThrow(true); // this is a noop if we're outside a try block that has catch/finally
1✔
1110
            return state;
1✔
1111
        }
1112

1113

1114
        // ctor/initializer handling
1115

1116
        @Override
1117
        public SpanInfo visitTypeDecl(ASTTypeDeclaration node, SpanInfo data) {
1118
            return processTypeDecl(node, data);
1✔
1119
        }
1120

1121
        private static SpanInfo processTypeDecl(ASTTypeDeclaration node, SpanInfo data) {
1122
            ReachingDefsVisitor instanceVisitor = new ReachingDefsVisitor(node.getSymbol(), false);
1✔
1123
            ReachingDefsVisitor staticVisitor = new ReachingDefsVisitor(node.getSymbol(), true);
1✔
1124
            // process initializers and ctors first
1125
            processInitializers(node.getDeclarations(), data, node.getSymbol(),
1✔
1126
                                instanceVisitor, staticVisitor);
1127

1128
            for (ASTBodyDeclaration decl : node.getDeclarations()) {
1✔
1129
                if (decl instanceof ASTMethodDeclaration) {
1✔
1130
                    ASTMethodDeclaration method = (ASTMethodDeclaration) decl;
1✔
1131
                    if (method.getBody() != null) {
1✔
1132
                        SpanInfo span = data.forkCapturingNonLocal();
1✔
1133
                        boolean staticCtx = method.isStatic();
1✔
1134
                        span.declareSpecialFieldValues(node.getSymbol(), staticCtx);
1✔
1135
                        SpanInfo endState;
1136
                        if (staticCtx) {
1✔
1137
                            endState = staticVisitor.acceptOpt(decl, span);
1✔
1138
                        } else {
1139
                            endState = instanceVisitor.acceptOpt(decl, span);
1✔
1140
                        }
1141
                        useAllSelfFields(endState, staticCtx, node.getSymbol());
1✔
1142
                    }
1143
                } else if (decl instanceof ASTTypeDeclaration) {
1✔
1144
                    processTypeDecl((ASTTypeDeclaration) decl, data.forkEmptyNonLocal());
1✔
1145
                }
1146
            }
1✔
1147
            return data;
1✔
1148
        }
1149

1150
        private static void processInitializers(NodeStream<ASTBodyDeclaration> declarations,
1151
                                                SpanInfo beforeLocal,
1152
                                                @NonNull JClassSymbol classSymbol,
1153
                                                ReachingDefsVisitor instanceVisitor,
1154
                                                ReachingDefsVisitor staticVisitor) {
1155

1156
            // All static field initializers + static initializers
1157
            SpanInfo staticInit = beforeLocal.forkEmptyNonLocal();
1✔
1158

1159
            List<ASTBodyDeclaration> ctors = new ArrayList<>();
1✔
1160
            // Those are initializer blocks and instance field initializers
1161
            List<ASTBodyDeclaration> ctorHeaders = new ArrayList<>();
1✔
1162

1163
            for (ASTBodyDeclaration declaration : declarations) {
1✔
1164
                final boolean isStatic;
1165
                if (declaration instanceof ASTEnumConstant) {
1✔
1166
                    isStatic = true;
1✔
1167
                } else if (declaration instanceof ASTFieldDeclaration) {
1✔
1168
                    isStatic = ((ASTFieldDeclaration) declaration).isStatic();
1✔
1169
                } else if (declaration instanceof ASTInitializer) {
1✔
1170
                    isStatic = ((ASTInitializer) declaration).isStatic();
1✔
1171
                } else if (declaration instanceof ASTConstructorDeclaration
1✔
1172
                    || declaration instanceof ASTCompactConstructorDeclaration) {
1173
                    ctors.add(declaration);
1✔
1174
                    continue;
1✔
1175
                } else {
1176
                    continue;
1177
                }
1178

1179
                if (isStatic) {
1✔
1180
                    staticInit = staticVisitor.acceptOpt(declaration, staticInit);
1✔
1181
                } else {
1182
                    ctorHeaders.add(declaration);
1✔
1183
                }
1184
            }
1✔
1185

1186
            // Static init is done, mark all static fields as escaping
1187
            useAllSelfFields(staticInit, true, classSymbol);
1✔
1188

1189

1190
            // All field initializers + instance initializers
1191
            // This also contains the static definitions, as the class must be
1192
            // initialized before an instance is created.
1193
            SpanInfo ctorHeader = beforeLocal.forkCapturingNonLocal().absorb(staticInit);
1✔
1194

1195
            // Static fields get an "initial value" placeholder before starting instance ctors
1196
            ctorHeader.declareSpecialFieldValues(classSymbol, true);
1✔
1197

1198
            for (ASTBodyDeclaration fieldInit : ctorHeaders) {
1✔
1199
                ctorHeader = instanceVisitor.acceptOpt(fieldInit, ctorHeader);
1✔
1200
            }
1✔
1201

1202
            SpanInfo ctorEndState = ctors.isEmpty() ? ctorHeader : null;
1✔
1203
            for (ASTBodyDeclaration ctor : ctors) {
1✔
1204
                SpanInfo ctorBody = ctorHeader.forkCapturingNonLocal();
1✔
1205
                ctorBody.declareSpecialFieldValues(classSymbol, true);
1✔
1206
                SpanInfo state = instanceVisitor.acceptOpt(ctor, ctorBody);
1✔
1207
                ctorEndState = ctorEndState == null ? state : ctorEndState.absorb(state);
1✔
1208
            }
1✔
1209

1210
            // assignments that reach the end of any constructor must be considered used
1211
            useAllSelfFields(ctorEndState, false, classSymbol);
1✔
1212
        }
1✔
1213

1214
        static void useAllSelfFields(SpanInfo state, boolean inStaticCtx, JClassSymbol enclosingSym) {
1215
            for (JFieldSymbol field : enclosingSym.getDeclaredFields()) {
1✔
1216
                if (!inStaticCtx || field.isStatic()) {
1✔
1217
                    JavaNode escapingNode = enclosingSym.tryGetNode();
1✔
1218
                    state.assignOutOfScope(field, escapingNode, SpecialAssignmentKind.INITIAL_FIELD_VALUE);
1✔
1219
                }
1220
            }
1✔
1221
        }
1✔
1222
    }
1223

1224
    /**
1225
     * The shared state for all {@link SpanInfo} instances in the same
1226
     * toplevel class.
1227
     */
1228
    private static final class GlobalAlgoState {
1229

1230
        final Set<AssignmentEntry> allAssignments;
1231
        final Set<AssignmentEntry> usedAssignments;
1232

1233
        // track which assignments kill which
1234
        // assignment -> killers(assignment)
1235
        final Map<AssignmentEntry, Set<AssignmentEntry>> killRecord;
1236

1237
        final TargetStack breakTargets = new TargetStack();
1✔
1238
        // continue jumps to the condition check, while break jumps to after the loop
1239
        final TargetStack continueTargets = new TargetStack();
1✔
1240

1241
        private GlobalAlgoState(Set<AssignmentEntry> allAssignments,
1242
                                Set<AssignmentEntry> usedAssignments,
1243
                                Map<AssignmentEntry, Set<AssignmentEntry>> killRecord) {
1✔
1244
            this.allAssignments = allAssignments;
1✔
1245
            this.usedAssignments = usedAssignments;
1✔
1246
            this.killRecord = killRecord;
1✔
1247

1248
        }
1✔
1249

1250
        private GlobalAlgoState() {
1251
            this(new LinkedHashSet<>(),
1✔
1252
                 new LinkedHashSet<>(),
1253
                 new LinkedHashMap<>());
1254
        }
1✔
1255
    }
1256

1257
    // Information about a variable in a code span.
1258
    static class VarLocalInfo {
1259

1260
        // this is not modified so can be shared between different SpanInfos.
1261
        final Set<AssignmentEntry> reachingDefs;
1262

1263
        VarLocalInfo(Set<AssignmentEntry> reachingDefs) {
1✔
1264
            this.reachingDefs = reachingDefs;
1✔
1265
        }
1✔
1266

1267
        // and produce an independent instance
1268
        VarLocalInfo merge(VarLocalInfo other) {
1269
            if (other == this) { // NOPMD #3205
1✔
1270
                return this;
1✔
1271
            }
1272
            Set<AssignmentEntry> merged = new LinkedHashSet<>(reachingDefs.size() + other.reachingDefs.size());
1✔
1273
            merged.addAll(reachingDefs);
1✔
1274
            merged.addAll(other.reachingDefs);
1✔
1275
            return new VarLocalInfo(merged);
1✔
1276
        }
1277

1278
        @Override
1279
        public String toString() {
1280
            return "VarLocalInfo{reachingDefs=" + reachingDefs + '}';
×
1281
        }
1282

1283
    }
1284

1285
    /**
1286
     * Information about a span of code.
1287
     */
1288
    private static final class SpanInfo {
1✔
1289

1290
        // spans are arranged in a tree, to look for enclosing finallies
1291
        // when abrupt completion occurs. Blocks that have non-local
1292
        // control-flow (lambda bodies, anonymous classes, etc) aren't
1293
        // linked to the outer parents.
1294
        final SpanInfo parent;
1295

1296
        // If != null, then abrupt completion in this span of code (and any descendant)
1297
        // needs to go through the finally span (the finally must absorb it)
1298
        SpanInfo myFinally = null;
1✔
1299

1300

1301
        /**
1302
         * Inside a try block, we assume that any method/ctor call may
1303
         * throw, which means, any assignment reaching such a method call
1304
         * may reach the catch blocks if there are any.
1305
         */
1306
        List<SpanInfo> myCatches;
1307

1308
        final GlobalAlgoState global;
1309

1310
        final Map<JVariableSymbol, VarLocalInfo> symtable;
1311

1312
        /**
1313
         * Whether the current span completed abruptly. Abrupt
1314
         * completion occurs with break, continue, return or throw
1315
         * statements. A loop whose body completes abruptly may or
1316
         * may not complete abruptly itself. For instance in
1317
         * <pre>{@code
1318
         * for (int i = 0; i < 5; i++) {
1319
         *     break;
1320
         * }
1321
         * }</pre>
1322
         * the loop body completes abruptly on all paths, but the loop
1323
         * itself completes normally. This is also the case in a switch
1324
         * statement where all cases are followed by a break.
1325
         */
1326
        private OptionalBool hasCompletedAbruptly = OptionalBool.NO;
1✔
1327

1328
        /**
1329
         * Collects the abrupt completion targets of the current span.
1330
         * The value {@link #returnOrThrowTarget}
1331
         * represents a return statement or a throw that
1332
         * is not followed by an enclosing finally block.
1333
         */
1334
        private PSet<SpanInfo> abruptCompletionTargets = HashTreePSet.empty();
1✔
1335

1336
        /**
1337
         * Sentinel to represent the target of a throw or return statement.
1338
         */
1339
        private final SpanInfo returnOrThrowTarget;
1340

1341

1342
        private SpanInfo(GlobalAlgoState global) {
1343
            this(null, global, new LinkedHashMap<>());
1✔
1344
        }
1✔
1345

1346
        private SpanInfo(@Nullable SpanInfo parent,
1347
                         GlobalAlgoState global,
1348
                         Map<JVariableSymbol, VarLocalInfo> symtable) {
1✔
1349
            this.parent = parent;
1✔
1350
            this.returnOrThrowTarget = parent == null ? this : parent.returnOrThrowTarget;
1✔
1351
            this.global = global;
1✔
1352
            this.symtable = symtable;
1✔
1353
            this.myCatches = Collections.emptyList();
1✔
1354
        }
1✔
1355

1356
        boolean hasVar(ASTVariableId var) {
1357
            return symtable.containsKey(var.getSymbol());
1✔
1358
        }
1359

1360
        void declareBlank(ASTVariableId id) {
1361
            assign(id.getSymbol(), id);
1✔
1362
        }
1✔
1363

1364
        VarLocalInfo assign(JVariableSymbol var, JavaNode rhs) {
1365
            return assign(var, rhs, SpecialAssignmentKind.NOT_SPECIAL);
1✔
1366
        }
1367

1368
        @Nullable
1369
        VarLocalInfo assign(JVariableSymbol var, JavaNode rhs, SpecialAssignmentKind kind) {
1370
            ASTVariableId node = var.tryGetNode();
1✔
1371
            if (node == null) {
1!
1372
                return null; // we don't care about non-local declarations
×
1373
            }
1374
            AssignmentEntry entry = kind != SpecialAssignmentKind.NOT_SPECIAL
1✔
1375
                                    ? new UnboundAssignment(var, node, rhs, kind)
1✔
1376
                                    : new AssignmentEntry(var, node, rhs);
1✔
1377
            VarLocalInfo newInfo = new VarLocalInfo(Collections.singleton(entry));
1✔
1378
            if (kind.shouldJoinWithPreviousAssignment()) {
1✔
1379
                // For unknown method calls, we don't know if the existing reaching defs were killed or not.
1380
                // In that case we just add an unbound entry to the existing reaching def set.
1381
                VarLocalInfo prev = symtable.remove(var);
1✔
1382
                if (prev != null) {
1!
1383
                    newInfo = prev.merge(newInfo);
1✔
1384
                }
1385
            }
1386
            VarLocalInfo previous = symtable.put(var, newInfo);
1✔
1387
            if (previous != null) {
1✔
1388
                // those assignments were overwritten ("killed")
1389
                for (AssignmentEntry killed : previous.reachingDefs) {
1✔
1390
                    if (killed.isBlankLocal()) {
1!
1391
                        continue;
×
1392
                    }
1393

1394
                    global.killRecord.computeIfAbsent(killed, k -> new LinkedHashSet<>(1))
1✔
1395
                                     .add(entry);
1✔
1396
                }
1✔
1397
            }
1398
            global.allAssignments.add(entry);
1✔
1399
            return previous;
1✔
1400
        }
1401

1402
        void declareSpecialFieldValues(JClassSymbol sym, boolean onlyStatic) {
1403
            List<JFieldSymbol> declaredFields = sym.getDeclaredFields();
1✔
1404
            for (JFieldSymbol field : declaredFields) {
1✔
1405
                if (onlyStatic && !field.isStatic()) {
1✔
1406
                    continue;
1✔
1407
                }
1408
                ASTVariableId id = field.tryGetNode();
1✔
1409
                if (id == null) {
1!
1410
                    continue;
×
1411
                }
1412

1413
                assign(field, id, SpecialAssignmentKind.INITIAL_FIELD_VALUE);
1✔
1414
            }
1✔
1415
        }
1✔
1416

1417

1418
        void assignOutOfScope(@Nullable JVariableSymbol var, JavaNode escapingNode, SpecialAssignmentKind kind) {
1419
            if (var == null) {
1!
1420
                return;
×
1421
            }
1422
            if (!symtable.containsKey(var)) {
1✔
1423
                // just an optimization, no need to assign this var since it's not being tracked
1424
                return;
1✔
1425
            }
1426
            use(var, null);
1✔
1427
            assign(var, escapingNode, kind);
1✔
1428
        }
1✔
1429

1430
        void use(@Nullable JVariableSymbol var, @Nullable ASTNamedReferenceExpr reachingDefSink) {
1431
            if (var == null) {
1✔
1432
                return;
1✔
1433
            }
1434
            VarLocalInfo info = symtable.get(var);
1✔
1435
            // may be null for implicit assignments, like method parameter
1436
            if (info != null) {
1✔
1437
                global.usedAssignments.addAll(info.reachingDefs);
1✔
1438
                if (reachingDefSink != null) {
1✔
1439
                    updateReachingDefs(reachingDefSink, var, info);
1✔
1440
                }
1441
            }
1442
        }
1✔
1443

1444
        private static void updateReachingDefs(@NonNull ASTNamedReferenceExpr reachingDefSink, JVariableSymbol var, VarLocalInfo info) {
1445
            ReachingDefinitionSet reaching;
1446
            if (info == null || var.isField() && var.isFinal()) {
1✔
1447
                return;
1✔
1448
            } else {
1449
                reaching = new ReachingDefinitionSet(new LinkedHashSet<>(info.reachingDefs));
1✔
1450
            }
1451
            // need to merge into previous to account for cyclic control flow
1452
            reachingDefSink.getUserMap().merge(REACHING_DEFS, reaching, (current, newer) -> {
1✔
1453
                current.absorb(newer);
1✔
1454
                return current;
1✔
1455
            });
1456
        }
1✔
1457

1458
        void deleteVar(JVariableSymbol var) {
1459
            symtable.remove(var);
1✔
1460
        }
1✔
1461

1462
        /**
1463
         * Record a leak of the `this` reference in a ctor (including field initializers).
1464
         *
1465
         * <p>This means, all defs reaching this point, for all fields
1466
         * of `this`, may be used in the expression. We assume that the
1467
         * ctor finishes its execution atomically, that is, following
1468
         * definitions are not observable at an arbitrary point (that
1469
         * would be too conservative).
1470
         *
1471
         * <p>Constructs that are considered to leak the `this` reference
1472
         * (only processed if they occur in a ctor):
1473
         * - using `this` as a method/ctor argument
1474
         * - using `this` as the receiver of a method/ctor invocation (also implicitly)
1475
         *
1476
         * <p>Because `this` may be aliased (eg in a field, a local var,
1477
         * inside an anon class or capturing lambda, etc), any method
1478
         * call, on any receiver, may actually observe field definitions
1479
         * of `this`. So the analysis may show some false positives, which
1480
         * hopefully should be rare enough.
1481
         */
1482
        public void recordThisLeak(JClassSymbol enclosingClassSym, JavaNode escapingNode) {
1483
            // all reaching defs to fields until now may be observed
1484
            for (JFieldSymbol field : enclosingClassSym.getDeclaredFields()) {
1✔
1485
                if (!field.isStatic()) {
1✔
1486
                    assignOutOfScope(field, escapingNode, SpecialAssignmentKind.UNKNOWN_METHOD_CALL);
1✔
1487
                }
1488
            }
1✔
1489
        }
1✔
1490

1491
        // Forks duplicate this context, to preserve the reaching defs
1492
        // of the current context while analysing a sub-block
1493
        // Forks must be merged later if control flow merges again, see ::absorb
1494

1495
        SpanInfo fork() {
1496
            return doFork(this, copyTable());
1✔
1497
        }
1498

1499
        SpanInfo forkEmpty() {
1500
            return doFork(this, new LinkedHashMap<>());
1✔
1501
        }
1502

1503

1504
        SpanInfo forkEmptyNonLocal() {
1505
            return doFork(null, new LinkedHashMap<>());
1✔
1506
        }
1507

1508
        SpanInfo forkCapturingNonLocal() {
1509
            return doFork(null, copyTable());
1✔
1510
        }
1511

1512
        private Map<JVariableSymbol, VarLocalInfo> copyTable() {
1513
            return new LinkedHashMap<>(this.symtable);
1✔
1514
        }
1515

1516
        private SpanInfo doFork(/*nullable*/ SpanInfo parent, Map<JVariableSymbol, VarLocalInfo> reaching) {
1517
            return new SpanInfo(parent, this.global, reaching);
1✔
1518
        }
1519

1520
        /** Abrupt completion for return, continue, break. */
1521
        SpanInfo abruptCompletion(@NonNull SpanInfo target) {
1522
            hasCompletedAbruptly = OptionalBool.YES;
1✔
1523
            abruptCompletionTargets = abruptCompletionTargets.plus(target);
1✔
1524

1525
            SpanInfo parent = this;
1✔
1526
            while (parent != null) {
1✔
1527
                if (parent.myFinally != null) {
1✔
1528
                    parent.myFinally.absorb(this);
1✔
1529
                    // stop on the first finally, its own end state will
1530
                    // be merged into the nearest enclosing finally
1531
                    break;
1✔
1532
                }
1533
                if (parent == target) { // NOPMD CompareObjectsWithEqual this is what we want
1✔
1534
                    break;
1✔
1535
                }
1536
                parent = parent.parent;
1✔
1537

1538
            }
1539

1540
            // rest of this block is dead code so we don't track declarations
1541
            this.symtable.clear();
1✔
1542
            return this;
1✔
1543
        }
1544

1545

1546
        /**
1547
         * Record an abrupt completion occurring because of a thrown
1548
         * exception.
1549
         *
1550
         * @param byMethodCall If true, a method/ctor call threw the exception
1551
         *                     (we conservatively consider they do inside try blocks).
1552
         *                     Otherwise, a throw statement threw.
1553
         */
1554
        SpanInfo abruptCompletionByThrow(boolean byMethodCall) {
1555
            // Find the first block that has a finally
1556
            // Be absorbed into every catch block on the way.
1557

1558
            // todo In 7.0, with the precise type/overload resolution, we
1559
            // can target the specific catch block that would catch the
1560
            // exception.
1561
            if (!byMethodCall) {
1✔
1562
                hasCompletedAbruptly = OptionalBool.YES;
1✔
1563
            }
1564
            abruptCompletionTargets = abruptCompletionTargets.plus(returnOrThrowTarget);
1✔
1565

1566
            SpanInfo parent = this;
1✔
1567
            while (parent != null) {
1✔
1568

1569
                if (!parent.myCatches.isEmpty()) {
1✔
1570
                    for (SpanInfo c : parent.myCatches) {
1✔
1571
                        c.absorb(this);
1✔
1572
                    }
1✔
1573
                }
1574

1575
                if (parent.myFinally != null) {
1✔
1576
                    // stop on the first finally, its own end state will
1577
                    // be merged into the nearest enclosing finally
1578
                    parent.myFinally.absorb(this);
1✔
1579
                    return this;
1✔
1580
                }
1581
                parent = parent.parent;
1✔
1582
            }
1583

1584

1585
            if (!byMethodCall) {
1✔
1586
                this.symtable.clear(); // following is dead code
1✔
1587
            }
1588
            return this;
1✔
1589
        }
1590

1591
        SpanInfo withCatchBlocks(List<SpanInfo> catchStmts) {
1592
            assert myCatches.isEmpty() || catchStmts.isEmpty() : "Cannot set catch blocks twice";
1!
1593
            myCatches = Collections.unmodifiableList(catchStmts); // we own the list now, to avoid copying
1✔
1594
            return this;
1✔
1595
        }
1596

1597
        SpanInfo absorb(SpanInfo other) {
1598
            // Merge reaching defs of the other scope into this
1599
            // This is used to join paths after the control flow has forked
1600

1601
            // a spanInfo may be absorbed several times so this method should not
1602
            // destroy the parameter
1603
            if (other == this || other == null || other.symtable.isEmpty()) { // NOPMD #3205
1✔
1604
                return this;
1✔
1605
            }
1606

1607
            CollectionUtil.mergeMaps(this.symtable, other.symtable, VarLocalInfo::merge);
1✔
1608
            this.hasCompletedAbruptly = mergeCertitude(this.hasCompletedAbruptly, other.hasCompletedAbruptly);
1✔
1609
            this.abruptCompletionTargets = CollectionUtil.union(this.abruptCompletionTargets, other.abruptCompletionTargets);
1✔
1610
            return this;
1✔
1611
        }
1612

1613
        static OptionalBool mergeCertitude(OptionalBool first, OptionalBool other) {
1614
            if (first.isKnown() && other.isKnown()) {
1✔
1615
                return first == other ? first : OptionalBool.UNKNOWN;
1✔
1616
            }
1617
            return OptionalBool.UNKNOWN;
1✔
1618
        }
1619

1620

1621
        @Override
1622
        public String toString() {
1623
            return symtable.toString();
×
1624
        }
1625
    }
1626

1627
    static class TargetStack {
1✔
1628

1629
        final Deque<SpanInfo> unnamedTargets = new ArrayDeque<>();
1✔
1630
        final Map<String, SpanInfo> namedTargets = new LinkedHashMap<>();
1✔
1631

1632

1633
        void push(SpanInfo state) {
1634
            unnamedTargets.push(state);
1✔
1635
        }
1✔
1636

1637
        SpanInfo pop() {
1638
            return unnamedTargets.pop();
1✔
1639
        }
1640

1641
        SpanInfo peek() {
1642
            return unnamedTargets.getFirst();
×
1643
        }
1644

1645
        SpanInfo doBreak(SpanInfo data, /* nullable */ String label) {
1646
            // basically, reaching defs at the point of the break
1647
            // also reach after the break (wherever it lands)
1648
            SpanInfo target;
1649
            if (label == null) {
1✔
1650
                target = unnamedTargets.getFirst();
1✔
1651
            } else {
1652
                target = namedTargets.get(label);
1✔
1653
            }
1654

1655
            if (target != null) { // otherwise CT error
1!
1656
                target.absorb(data);
1✔
1657
                return data.abruptCompletion(target);
1✔
1658
            }
1659
            return data;
×
1660
        }
1661
    }
1662

1663
    public static class AssignmentEntry implements Comparable<AssignmentEntry> {
1664

1665
        final JVariableSymbol var;
1666
        final ASTVariableId node;
1667

1668
        // this is not necessarily an expression, it may be also the
1669
        // variable declarator of a foreach loop
1670
        final JavaNode rhs;
1671

1672
        AssignmentEntry(JVariableSymbol var, ASTVariableId node, JavaNode rhs) {
1✔
1673
            this.var = var;
1✔
1674
            this.node = node;
1✔
1675
            this.rhs = rhs;
1✔
1676
            // This may be overwritten repeatedly in loops, we probably don't care,
1677
            // as normally they're created equal
1678
            // Also for now we don't support getting a field.
1679
            if ((isInitializer() || isBlankDeclaration()) && !isUnbound()) {
1✔
1680
                node.getUserMap().set(VAR_DEFINITION, this);
1✔
1681
            }
1682
        }
1✔
1683

1684
        public boolean isInitializer() {
1685
            return rhs.getParent() instanceof ASTVariableDeclarator
1✔
1686
                && rhs.getIndexInParent() > 0;
1✔
1687
        }
1688

1689
        public boolean isBlankDeclaration() {
1690
            return rhs instanceof ASTVariableId;
1✔
1691
        }
1692

1693
        public boolean isFieldDefaultValue() {
1694
            return isBlankDeclaration() && isField();
1✔
1695
        }
1696

1697
        /**
1698
         * A blank local that has no value (ie not a catch param or formal).
1699
         */
1700
        public boolean isBlankLocal() {
1701
            return isBlankDeclaration() && node.isLocalVariable();
1!
1702
        }
1703

1704
        public boolean isUnaryReassign() {
1705
            return rhs instanceof ASTUnaryExpression
1✔
1706
                && ReachingDefsVisitor.getVarIfUnaryAssignment((ASTUnaryExpression) rhs) == var; // NOPMD #3205
1✔
1707
        }
1708

1709
        @Override
1710
        public int compareTo(AssignmentEntry o) {
1711
            return this.rhs.compareLocation(o.rhs);
1✔
1712
        }
1713

1714
        public int getLine() {
1715
            return getLocation().getBeginLine();
1✔
1716
        }
1717

1718
        public boolean isField() {
1719
            return var instanceof JFieldSymbol;
1✔
1720
        }
1721

1722
        public boolean isForeachVar() {
1723
            return node.isForeachVariable();
1✔
1724
        }
1725

1726
        public ASTVariableId getVarId() {
1727
            return node;
1✔
1728
        }
1729

1730

1731
        public JavaNode getLocation() {
1732
            return rhs;
1✔
1733
        }
1734

1735
        // todo i'm probably missing some
1736

1737
        /**
1738
         * <p>Returns non-null for an assignment expression, eg for (a = b), returns b.
1739
         * For (i++), returns (i++) and not (i), same for (i--).
1740
         * Returns null if the assignment is, eg, the default value
1741
         * of a field; the "blank" definition of a local variable,
1742
         * exception parameter, formal parameter, foreach variable, etc.
1743
         */
1744
        public @Nullable ASTExpression getRhsAsExpression() {
1745
            if (isUnbound() || isBlankDeclaration()) {
1!
1746
                return null;
1✔
1747
            }
1748
            if (rhs instanceof ASTExpression) {
1!
1749
                return (ASTExpression) rhs;
1✔
1750
            }
1751
            return null;
×
1752
        }
1753

1754
        /**
1755
         * Returns the type of the right-hand side if it is an explicit
1756
         * expression, null if it cannot be determined or there is no
1757
         * right-hand side to this expression. TODO test
1758
         */
1759
        public @Nullable JTypeMirror getRhsType() {
1760
            /* test case
1761
               List<A> as;
1762
               for (Object o : as) {
1763
                 // the rhs type of o should be A
1764
               }
1765
             */
1766
            if (isUnbound() || isBlankDeclaration()) {
1!
1767
                return null;
1✔
1768
            } else if (rhs instanceof ASTExpression) {
1!
1769
                return ((TypeNode) rhs).getTypeMirror();
1✔
1770
            }
1771
            return null;
×
1772
        }
1773

1774
        /**
1775
         * If true, then this "assignment" is not real. We conservatively
1776
         * assume that the variable may have been set to another value by
1777
         * a call to some external code.
1778
         *
1779
         * @see #isFieldAssignmentAtEndOfCtor()
1780
         * @see #isFieldAssignmentAtStartOfMethod()
1781
         */
1782
        public boolean isUnbound() {
1783
            return false;
1✔
1784
        }
1785

1786
        /**
1787
         * If true, then this "assignment" is the placeholder value given
1788
         * to an instance field before a method starts. This is a subset of
1789
         * {@link #isUnbound()}.
1790
         */
1791
        public boolean isFieldAssignmentAtStartOfMethod() {
1792
            return false;
1✔
1793
        }
1794

1795
        /**
1796
         * If true, then this "assignment" is the placeholder value given
1797
         * to a non-final instance field after a ctor ends. This is a subset of
1798
         * {@link #isUnbound()}.
1799
         */
1800
        public boolean isFieldAssignmentAtEndOfCtor() {
1801
            return false;
1✔
1802
        }
1803

1804
        @Override
1805
        public String toString() {
1806
            return var.getSimpleName() + " := " + rhs;
×
1807
        }
1808

1809
        @Override
1810
        public boolean equals(Object o) {
1811
            if (this == o) {
1!
1812
                return true;
×
1813
            }
1814
            if (o == null || getClass() != o.getClass()) {
1!
1815
                return false;
1✔
1816
            }
1817
            AssignmentEntry that = (AssignmentEntry) o;
1✔
1818
            return Objects.equals(var, that.var)
1!
1819
                && Objects.equals(rhs, that.rhs);
1!
1820
        }
1821

1822
        @Override
1823
        public int hashCode() {
1824
            return 31 * var.hashCode() + rhs.hashCode();
1✔
1825
        }
1826
    }
1827

1828
    static class UnboundAssignment extends AssignmentEntry {
1829

1830
        private final SpecialAssignmentKind kind;
1831

1832
        UnboundAssignment(JVariableSymbol var, ASTVariableId node, JavaNode rhs, SpecialAssignmentKind kind) {
1833
            super(var, node, rhs);
1✔
1834
            this.kind = kind;
1✔
1835
        }
1✔
1836

1837
        @Override
1838
        public boolean isUnbound() {
1839
            return true;
1✔
1840
        }
1841

1842
        @Override
1843
        public boolean isFieldAssignmentAtStartOfMethod() {
1844
            return kind == SpecialAssignmentKind.INITIAL_FIELD_VALUE;
1✔
1845
        }
1846

1847
        @Override
1848
        public boolean isFieldAssignmentAtEndOfCtor() {
1849
            return rhs instanceof ASTTypeDeclaration;
1✔
1850
        }
1851
    }
1852

1853
    enum SpecialAssignmentKind {
1✔
1854
        NOT_SPECIAL,
1✔
1855
        UNKNOWN_METHOD_CALL,
1✔
1856
        INITIAL_FIELD_VALUE;
1✔
1857

1858
        boolean shouldJoinWithPreviousAssignment() {
1859
            return this == UNKNOWN_METHOD_CALL;
1✔
1860
        }
1861
    }
1862
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc