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

pmd / pmd / 4472

24 Feb 2025 06:50PM UTC coverage: 77.685% (-0.003%) from 77.688%
4472

push

github

adangel
[javacc] Move grammar files into src/main/javacc (#5544)

Merge pull request #5544 from adangel:javacc-move-src-main

17350 of 23276 branches covered (74.54%)

Branch coverage included in aggregate %.

38130 of 48141 relevant lines covered (79.2%)

0.8 hits per line

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

94.74
/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.ASTRecordComponent;
65
import net.sourceforge.pmd.lang.java.ast.ASTResourceList;
66
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
67
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
68
import net.sourceforge.pmd.lang.java.ast.ASTSwitchArrowBranch;
69
import net.sourceforge.pmd.lang.java.ast.ASTSwitchBranch;
70
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
71
import net.sourceforge.pmd.lang.java.ast.ASTSwitchFallthroughBranch;
72
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLike;
73
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
74
import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
75
import net.sourceforge.pmd.lang.java.ast.ASTThisExpression;
76
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
77
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
78
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
79
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression;
80
import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
81
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
82
import net.sourceforge.pmd.lang.java.ast.ASTVariableId;
83
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
84
import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement;
85
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
86
import net.sourceforge.pmd.lang.java.ast.InvocationNode;
87
import net.sourceforge.pmd.lang.java.ast.JavaNode;
88
import net.sourceforge.pmd.lang.java.ast.JavaVisitorBase;
89
import net.sourceforge.pmd.lang.java.ast.QualifiableExpression;
90
import net.sourceforge.pmd.lang.java.ast.TypeNode;
91
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
92
import net.sourceforge.pmd.lang.java.rule.bestpractices.UnusedAssignmentRule;
93
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
94
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
95
import net.sourceforge.pmd.lang.java.symbols.JLocalVariableSymbol;
96
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
97
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
98
import net.sourceforge.pmd.util.CollectionUtil;
99
import net.sourceforge.pmd.util.DataMap;
100
import net.sourceforge.pmd.util.DataMap.SimpleDataKey;
101
import net.sourceforge.pmd.util.OptionalBool;
102

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

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

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

116
    //  see also the todo comments in UnusedAssignmentRule
117

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

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

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

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

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

170
        return dataflowResult;
1✔
171
    }
172

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

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

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

188

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

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

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

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

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

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

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

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

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

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

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

256

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

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

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

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

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

290

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

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

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

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

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

347
    private static final class ReachingDefsVisitor extends JavaVisitorBase<SpanInfo, SpanInfo> {
348

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

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

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

367
        // following deals with control flow structures
368

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

572
            elseState.absorb(cur);
1✔
573

574
            return cur;
1✔
575
        }
576

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

684
            return finalState;
1✔
685
        }
686

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

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

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

703
            SpanInfo before = data;
1✔
704

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

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

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

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

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

738

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

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

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

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

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

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

771

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

899

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

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

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

914
        // following deals with assignment
915

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

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

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

941
        @Override
942
        public SpanInfo visit(ASTCompactConstructorDeclaration node, SpanInfo data) {
943
            super.visit(node, data);
1✔
944

945
            // mark any write to a variable that is named like a record component as usage
946
            // record compact constructors do an implicit assignment at the end.
947
            for (ASTRecordComponent component : node.getEnclosingType().getRecordComponents()) {
1✔
948
                node.descendants(ASTAssignmentExpression.class)
1✔
949
                        .descendants(ASTVariableAccess.class)
1✔
950
                        .filter(v -> v.getAccessType() == AccessType.WRITE)
1✔
951
                        .filter(v -> v.getName().equals(component.getVarId().getName()))
1✔
952
                        .forEach(varAccess -> data.use(varAccess.getReferencedSym(), null));
1✔
953
            }
1✔
954

955
            return data;
1✔
956
        }
957

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

969
        @Override
970
        public SpanInfo visit(ASTUnaryExpression node, SpanInfo data) {
971
            data = acceptOpt(node.getOperand(), data);
1✔
972

973
            if (node.getOperator().isPure()) {
1✔
974
                return data;
1✔
975
            } else {
976
                return processAssignment(node.getOperand(), node, true, data);
1✔
977
            }
978
        }
979

980
        @Override
981
        public SpanInfo visit(ASTAssignmentExpression node, SpanInfo data) {
982
            // visit operands in order
983
            data = acceptOpt(node.getRightOperand(), data);
1✔
984
            data = acceptOpt(node.getLeftOperand(), data);
1✔
985

986
            return processAssignment(node.getLeftOperand(),
1✔
987
                                     node.getRightOperand(),
1✔
988
                                     node.getOperator().isCompound(),
1✔
989
                                     data);
990

991
        }
992

993
        private SpanInfo processAssignment(ASTExpression lhs0, // LHS or unary operand
994
                                           ASTExpression rhs,  // RHS or unary
995
                                           boolean useBeforeAssigning,
996
                                           SpanInfo result) {
997

998
            if (lhs0 instanceof ASTNamedReferenceExpr) {
1✔
999
                ASTNamedReferenceExpr lhs = (ASTNamedReferenceExpr) lhs0;
1✔
1000
                JVariableSymbol lhsVar = lhs.getReferencedSym();
1✔
1001
                if (lhsVar != null
1✔
1002
                    && (lhsVar instanceof JLocalVariableSymbol
1003
                    || isRelevantField(lhs))) {
1✔
1004

1005
                    if (useBeforeAssigning) {
1✔
1006
                        // compound assignment, to use BEFORE assigning
1007
                        result.use(lhsVar, lhs);
1✔
1008
                    }
1009

1010
                    VarLocalInfo oldVar = result.assign(lhsVar, rhs);
1✔
1011
                    SpanInfo.updateReachingDefs(lhs, lhsVar, oldVar);
1✔
1012
                }
1013
            }
1014
            return result;
1✔
1015
        }
1016

1017
        private boolean isRelevantField(ASTExpression lhs) {
1018
            return lhs instanceof ASTNamedReferenceExpr && (trackThisInstance() && JavaAstUtils.isThisFieldAccess(lhs)
1!
1019
                || isStaticFieldOfThisClass(((ASTNamedReferenceExpr) lhs).getReferencedSym()));
1✔
1020
        }
1021

1022
        private boolean isStaticFieldOfThisClass(JVariableSymbol var) {
1023
            return var instanceof JFieldSymbol
1!
1024
                && ((JFieldSymbol) var).isStatic()
1✔
1025
                && enclosingClassScope.equals(((JFieldSymbol) var).getEnclosingClass());
1!
1026
        }
1027

1028
        private static JVariableSymbol getVarIfUnaryAssignment(ASTUnaryExpression node) { // NOPMD UnusedPrivateMethod
1029
            ASTExpression operand = node.getOperand();
1✔
1030
            if (!node.getOperator().isPure() && operand instanceof ASTNamedReferenceExpr) {
1!
1031
                return ((ASTNamedReferenceExpr) operand).getReferencedSym();
1✔
1032
            }
1033
            return null;
1✔
1034
        }
1035

1036
        // variable usage
1037

1038
        @Override
1039
        public SpanInfo visit(ASTVariableAccess node, SpanInfo data) {
1040
            if (node.getAccessType() == AccessType.READ) {
1✔
1041
                data.use(node.getReferencedSym(), node);
1✔
1042
            }
1043
            return data;
1✔
1044
        }
1045

1046
        @Override
1047
        public SpanInfo visit(ASTFieldAccess node, SpanInfo data) {
1048
            data = node.getQualifier().acceptVisitor(this, data);
1✔
1049
            if (node.getAccessType() == AccessType.READ) {
1✔
1050
                data.use(node.getReferencedSym(), node);
1✔
1051
            }
1052
            return data;
1✔
1053
        }
1054

1055
        @Override
1056
        public SpanInfo visit(ASTThisExpression node, SpanInfo data) {
1057
            if (trackThisInstance() && isThisExprLeaking(node)) {
1✔
1058
                data.recordThisLeak(enclosingClassScope, node);
1✔
1059
            }
1060
            return data;
1✔
1061
        }
1062

1063
        private static boolean isThisExprLeaking(ASTThisExpression node) {
1064
            boolean isAllowed = node.getParent() instanceof ASTFieldAccess
1✔
1065
                || node.getParent() instanceof ASTSynchronizedStatement;
1✔
1066
            return !isAllowed;
1✔
1067
        }
1068

1069
        @Override
1070
        public SpanInfo visit(ASTMethodCall node, SpanInfo state) {
1071
            if (trackThisInstance() && JavaAstUtils.isCallOnThisInstance(node) != OptionalBool.NO) {
1✔
1072
                state.recordThisLeak(enclosingClassScope, node);
1✔
1073
            }
1074
            return visitInvocationExpr(node, state);
1✔
1075
        }
1076

1077
        @Override
1078
        public SpanInfo visit(ASTConstructorCall node, SpanInfo state) {
1079
            state = visitInvocationExpr(node, state);
1✔
1080
            acceptOpt(node.getAnonymousClassDeclaration(), state);
1✔
1081
            return state;
1✔
1082
        }
1083

1084
        @Override
1085
        public SpanInfo visit(ASTArrayAllocation node, SpanInfo state) {
1086
            state = acceptOpt(node.getArrayInitializer(), state);
1✔
1087
            state = acceptOpt(node.getTypeNode().getDimensions(), state);
1✔
1088
            // May throw OOM error for instance. This abrupt completion routine is
1089
            // noop if we are outside a try block.
1090
            state.abruptCompletionByThrow(true);
1✔
1091
            return state;
1✔
1092
        }
1093

1094
        private <T extends InvocationNode & QualifiableExpression> SpanInfo visitInvocationExpr(T node, SpanInfo state) {
1095
            state = acceptOpt(node.getQualifier(), state);
1✔
1096
            state = acceptOpt(node.getArguments(), state);
1✔
1097

1098
            // todo In 7.0, with the precise type/overload resolution, we
1099
            //  could only target methods that throw checked exceptions
1100
            //  (unless some catch block catches an unchecked exceptions)
1101

1102
            state.abruptCompletionByThrow(true); // this is a noop if we're outside a try block that has catch/finally
1✔
1103
            return state;
1✔
1104
        }
1105

1106

1107
        // ctor/initializer handling
1108

1109
        @Override
1110
        public SpanInfo visitTypeDecl(ASTTypeDeclaration node, SpanInfo data) {
1111
            return processTypeDecl(node, data);
1✔
1112
        }
1113

1114
        private static SpanInfo processTypeDecl(ASTTypeDeclaration node, SpanInfo data) {
1115
            ReachingDefsVisitor instanceVisitor = new ReachingDefsVisitor(node.getSymbol(), false);
1✔
1116
            ReachingDefsVisitor staticVisitor = new ReachingDefsVisitor(node.getSymbol(), true);
1✔
1117
            // process initializers and ctors first
1118
            processInitializers(node.getDeclarations(), data, node.getSymbol(),
1✔
1119
                                instanceVisitor, staticVisitor);
1120

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

1143
        private static void processInitializers(NodeStream<ASTBodyDeclaration> declarations,
1144
                                                SpanInfo beforeLocal,
1145
                                                @NonNull JClassSymbol classSymbol,
1146
                                                ReachingDefsVisitor instanceVisitor,
1147
                                                ReachingDefsVisitor staticVisitor) {
1148

1149
            // All static field initializers + static initializers
1150
            SpanInfo staticInit = beforeLocal.forkEmptyNonLocal();
1✔
1151

1152
            List<ASTBodyDeclaration> ctors = new ArrayList<>();
1✔
1153
            // Those are initializer blocks and instance field initializers
1154
            List<ASTBodyDeclaration> ctorHeaders = new ArrayList<>();
1✔
1155

1156
            for (ASTBodyDeclaration declaration : declarations) {
1✔
1157
                final boolean isStatic;
1158
                if (declaration instanceof ASTEnumConstant) {
1✔
1159
                    isStatic = true;
1✔
1160
                } else if (declaration instanceof ASTFieldDeclaration) {
1✔
1161
                    isStatic = ((ASTFieldDeclaration) declaration).isStatic();
1✔
1162
                } else if (declaration instanceof ASTInitializer) {
1✔
1163
                    isStatic = ((ASTInitializer) declaration).isStatic();
1✔
1164
                } else if (declaration instanceof ASTConstructorDeclaration
1✔
1165
                    || declaration instanceof ASTCompactConstructorDeclaration) {
1166
                    ctors.add(declaration);
1✔
1167
                    continue;
1✔
1168
                } else {
1169
                    continue;
1170
                }
1171

1172
                if (isStatic) {
1✔
1173
                    staticInit = staticVisitor.acceptOpt(declaration, staticInit);
1✔
1174
                } else {
1175
                    ctorHeaders.add(declaration);
1✔
1176
                }
1177
            }
1✔
1178

1179
            // Static init is done, mark all static fields as escaping
1180
            useAllSelfFields(staticInit, true, classSymbol);
1✔
1181

1182

1183
            // All field initializers + instance initializers
1184
            // This also contains the static definitions, as the class must be
1185
            // initialized before an instance is created.
1186
            SpanInfo ctorHeader = beforeLocal.forkCapturingNonLocal().absorb(staticInit);
1✔
1187

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

1191
            for (ASTBodyDeclaration fieldInit : ctorHeaders) {
1✔
1192
                ctorHeader = instanceVisitor.acceptOpt(fieldInit, ctorHeader);
1✔
1193
            }
1✔
1194

1195
            SpanInfo ctorEndState = ctors.isEmpty() ? ctorHeader : null;
1✔
1196
            for (ASTBodyDeclaration ctor : ctors) {
1✔
1197
                SpanInfo ctorBody = ctorHeader.forkCapturingNonLocal();
1✔
1198
                ctorBody.declareSpecialFieldValues(classSymbol, true);
1✔
1199
                SpanInfo state = instanceVisitor.acceptOpt(ctor, ctorBody);
1✔
1200
                ctorEndState = ctorEndState == null ? state : ctorEndState.absorb(state);
1✔
1201
            }
1✔
1202

1203
            // assignments that reach the end of any constructor must be considered used
1204
            useAllSelfFields(ctorEndState, false, classSymbol);
1✔
1205
        }
1✔
1206

1207
        static void useAllSelfFields(SpanInfo state, boolean inStaticCtx, JClassSymbol enclosingSym) {
1208
            for (JFieldSymbol field : enclosingSym.getDeclaredFields()) {
1✔
1209
                if (!inStaticCtx || field.isStatic()) {
1✔
1210
                    JavaNode escapingNode = enclosingSym.tryGetNode();
1✔
1211
                    state.assignOutOfScope(field, escapingNode, SpecialAssignmentKind.INITIAL_FIELD_VALUE);
1✔
1212
                }
1213
            }
1✔
1214
        }
1✔
1215
    }
1216

1217
    /**
1218
     * The shared state for all {@link SpanInfo} instances in the same
1219
     * toplevel class.
1220
     */
1221
    private static final class GlobalAlgoState {
1222

1223
        final Set<AssignmentEntry> allAssignments;
1224
        final Set<AssignmentEntry> usedAssignments;
1225

1226
        // track which assignments kill which
1227
        // assignment -> killers(assignment)
1228
        final Map<AssignmentEntry, Set<AssignmentEntry>> killRecord;
1229

1230
        final TargetStack breakTargets = new TargetStack();
1✔
1231
        // continue jumps to the condition check, while break jumps to after the loop
1232
        final TargetStack continueTargets = new TargetStack();
1✔
1233

1234
        private GlobalAlgoState(Set<AssignmentEntry> allAssignments,
1235
                                Set<AssignmentEntry> usedAssignments,
1236
                                Map<AssignmentEntry, Set<AssignmentEntry>> killRecord) {
1✔
1237
            this.allAssignments = allAssignments;
1✔
1238
            this.usedAssignments = usedAssignments;
1✔
1239
            this.killRecord = killRecord;
1✔
1240

1241
        }
1✔
1242

1243
        private GlobalAlgoState() {
1244
            this(new LinkedHashSet<>(),
1✔
1245
                 new LinkedHashSet<>(),
1246
                 new LinkedHashMap<>());
1247
        }
1✔
1248
    }
1249

1250
    // Information about a variable in a code span.
1251
    static class VarLocalInfo {
1252

1253
        // this is not modified so can be shared between different SpanInfos.
1254
        final Set<AssignmentEntry> reachingDefs;
1255

1256
        VarLocalInfo(Set<AssignmentEntry> reachingDefs) {
1✔
1257
            this.reachingDefs = reachingDefs;
1✔
1258
        }
1✔
1259

1260
        // and produce an independent instance
1261
        VarLocalInfo merge(VarLocalInfo other) {
1262
            if (other == this) { // NOPMD #3205
1✔
1263
                return this;
1✔
1264
            }
1265
            Set<AssignmentEntry> merged = new LinkedHashSet<>(reachingDefs.size() + other.reachingDefs.size());
1✔
1266
            merged.addAll(reachingDefs);
1✔
1267
            merged.addAll(other.reachingDefs);
1✔
1268
            return new VarLocalInfo(merged);
1✔
1269
        }
1270

1271
        @Override
1272
        public String toString() {
1273
            return "VarLocalInfo{reachingDefs=" + reachingDefs + '}';
×
1274
        }
1275

1276
    }
1277

1278
    /**
1279
     * Information about a span of code.
1280
     */
1281
    private static final class SpanInfo {
1✔
1282

1283
        // spans are arranged in a tree, to look for enclosing finallies
1284
        // when abrupt completion occurs. Blocks that have non-local
1285
        // control-flow (lambda bodies, anonymous classes, etc) aren't
1286
        // linked to the outer parents.
1287
        final SpanInfo parent;
1288

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

1293

1294
        /**
1295
         * Inside a try block, we assume that any method/ctor call may
1296
         * throw, which means, any assignment reaching such a method call
1297
         * may reach the catch blocks if there are any.
1298
         */
1299
        List<SpanInfo> myCatches;
1300

1301
        final GlobalAlgoState global;
1302

1303
        final Map<JVariableSymbol, VarLocalInfo> symtable;
1304

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

1321
        /**
1322
         * Collects the abrupt completion targets of the current span.
1323
         * The value {@link #returnOrThrowTarget}
1324
         * represents a return statement or a throw that
1325
         * is not followed by an enclosing finally block.
1326
         */
1327
        private PSet<SpanInfo> abruptCompletionTargets = HashTreePSet.empty();
1✔
1328

1329
        /**
1330
         * Sentinel to represent the target of a throw or return statement.
1331
         */
1332
        private final SpanInfo returnOrThrowTarget;
1333

1334

1335
        private SpanInfo(GlobalAlgoState global) {
1336
            this(null, global, new LinkedHashMap<>());
1✔
1337
        }
1✔
1338

1339
        private SpanInfo(@Nullable SpanInfo parent,
1340
                         GlobalAlgoState global,
1341
                         Map<JVariableSymbol, VarLocalInfo> symtable) {
1✔
1342
            this.parent = parent;
1✔
1343
            this.returnOrThrowTarget = parent == null ? this : parent.returnOrThrowTarget;
1✔
1344
            this.global = global;
1✔
1345
            this.symtable = symtable;
1✔
1346
            this.myCatches = Collections.emptyList();
1✔
1347
        }
1✔
1348

1349
        boolean hasVar(ASTVariableId var) {
1350
            return symtable.containsKey(var.getSymbol());
1✔
1351
        }
1352

1353
        void declareBlank(ASTVariableId id) {
1354
            assign(id.getSymbol(), id);
1✔
1355
        }
1✔
1356

1357
        VarLocalInfo assign(JVariableSymbol var, JavaNode rhs) {
1358
            return assign(var, rhs, SpecialAssignmentKind.NOT_SPECIAL);
1✔
1359
        }
1360

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

1387
                    global.killRecord.computeIfAbsent(killed, k -> new LinkedHashSet<>(1))
1✔
1388
                                     .add(entry);
1✔
1389
                }
1✔
1390
            }
1391
            global.allAssignments.add(entry);
1✔
1392
            return previous;
1✔
1393
        }
1394

1395
        void declareSpecialFieldValues(JClassSymbol sym, boolean onlyStatic) {
1396
            List<JFieldSymbol> declaredFields = sym.getDeclaredFields();
1✔
1397
            for (JFieldSymbol field : declaredFields) {
1✔
1398
                if (onlyStatic && !field.isStatic()) {
1✔
1399
                    continue;
1✔
1400
                }
1401
                ASTVariableId id = field.tryGetNode();
1✔
1402
                if (id == null) {
1!
1403
                    continue;
×
1404
                }
1405

1406
                assign(field, id, SpecialAssignmentKind.INITIAL_FIELD_VALUE);
1✔
1407
            }
1✔
1408
        }
1✔
1409

1410

1411
        void assignOutOfScope(@Nullable JVariableSymbol var, JavaNode escapingNode, SpecialAssignmentKind kind) {
1412
            if (var == null) {
1!
1413
                return;
×
1414
            }
1415
            if (!symtable.containsKey(var)) {
1✔
1416
                // just an optimization, no need to assign this var since it's not being tracked
1417
                return;
1✔
1418
            }
1419
            use(var, null);
1✔
1420
            assign(var, escapingNode, kind);
1✔
1421
        }
1✔
1422

1423
        void use(@Nullable JVariableSymbol var, @Nullable ASTNamedReferenceExpr reachingDefSink) {
1424
            if (var == null) {
1✔
1425
                return;
1✔
1426
            }
1427
            VarLocalInfo info = symtable.get(var);
1✔
1428
            // may be null for implicit assignments, like method parameter
1429
            if (info != null) {
1✔
1430
                global.usedAssignments.addAll(info.reachingDefs);
1✔
1431
                if (reachingDefSink != null) {
1✔
1432
                    updateReachingDefs(reachingDefSink, var, info);
1✔
1433
                }
1434
            }
1435
        }
1✔
1436

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

1451
        void deleteVar(JVariableSymbol var) {
1452
            symtable.remove(var);
1✔
1453
        }
1✔
1454

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

1484
        // Forks duplicate this context, to preserve the reaching defs
1485
        // of the current context while analysing a sub-block
1486
        // Forks must be merged later if control flow merges again, see ::absorb
1487

1488
        SpanInfo fork() {
1489
            return doFork(this, copyTable());
1✔
1490
        }
1491

1492
        SpanInfo forkEmpty() {
1493
            return doFork(this, new LinkedHashMap<>());
1✔
1494
        }
1495

1496

1497
        SpanInfo forkEmptyNonLocal() {
1498
            return doFork(null, new LinkedHashMap<>());
1✔
1499
        }
1500

1501
        SpanInfo forkCapturingNonLocal() {
1502
            return doFork(null, copyTable());
1✔
1503
        }
1504

1505
        private Map<JVariableSymbol, VarLocalInfo> copyTable() {
1506
            return new LinkedHashMap<>(this.symtable);
1✔
1507
        }
1508

1509
        private SpanInfo doFork(/*nullable*/ SpanInfo parent, Map<JVariableSymbol, VarLocalInfo> reaching) {
1510
            return new SpanInfo(parent, this.global, reaching);
1✔
1511
        }
1512

1513
        /** Abrupt completion for return, continue, break. */
1514
        SpanInfo abruptCompletion(@NonNull SpanInfo target) {
1515
            hasCompletedAbruptly = OptionalBool.YES;
1✔
1516
            abruptCompletionTargets = abruptCompletionTargets.plus(target);
1✔
1517

1518
            SpanInfo parent = this;
1✔
1519
            while (parent != null) {
1✔
1520
                if (parent.myFinally != null) {
1✔
1521
                    parent.myFinally.absorb(this);
1✔
1522
                    // stop on the first finally, its own end state will
1523
                    // be merged into the nearest enclosing finally
1524
                    break;
1✔
1525
                }
1526
                if (parent == target) { // NOPMD CompareObjectsWithEqual this is what we want
1✔
1527
                    break;
1✔
1528
                }
1529
                parent = parent.parent;
1✔
1530

1531
            }
1532

1533
            // rest of this block is dead code so we don't track declarations
1534
            this.symtable.clear();
1✔
1535
            return this;
1✔
1536
        }
1537

1538

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

1551
            // todo In 7.0, with the precise type/overload resolution, we
1552
            // can target the specific catch block that would catch the
1553
            // exception.
1554
            if (!byMethodCall) {
1✔
1555
                hasCompletedAbruptly = OptionalBool.YES;
1✔
1556
            }
1557
            abruptCompletionTargets = abruptCompletionTargets.plus(returnOrThrowTarget);
1✔
1558

1559
            SpanInfo parent = this;
1✔
1560
            while (parent != null) {
1✔
1561

1562
                if (!parent.myCatches.isEmpty()) {
1✔
1563
                    for (SpanInfo c : parent.myCatches) {
1✔
1564
                        c.absorb(this);
1✔
1565
                    }
1✔
1566
                }
1567

1568
                if (parent.myFinally != null) {
1✔
1569
                    // stop on the first finally, its own end state will
1570
                    // be merged into the nearest enclosing finally
1571
                    parent.myFinally.absorb(this);
1✔
1572
                    return this;
1✔
1573
                }
1574
                parent = parent.parent;
1✔
1575
            }
1576

1577

1578
            if (!byMethodCall) {
1✔
1579
                this.symtable.clear(); // following is dead code
1✔
1580
            }
1581
            return this;
1✔
1582
        }
1583

1584
        SpanInfo withCatchBlocks(List<SpanInfo> catchStmts) {
1585
            assert myCatches.isEmpty() || catchStmts.isEmpty() : "Cannot set catch blocks twice";
1!
1586
            myCatches = Collections.unmodifiableList(catchStmts); // we own the list now, to avoid copying
1✔
1587
            return this;
1✔
1588
        }
1589

1590
        SpanInfo absorb(SpanInfo other) {
1591
            // Merge reaching defs of the other scope into this
1592
            // This is used to join paths after the control flow has forked
1593

1594
            // a spanInfo may be absorbed several times so this method should not
1595
            // destroy the parameter
1596
            if (other == this || other == null || other.symtable.isEmpty()) { // NOPMD #3205
1✔
1597
                return this;
1✔
1598
            }
1599

1600
            CollectionUtil.mergeMaps(this.symtable, other.symtable, VarLocalInfo::merge);
1✔
1601
            this.hasCompletedAbruptly = mergeCertitude(this.hasCompletedAbruptly, other.hasCompletedAbruptly);
1✔
1602
            this.abruptCompletionTargets = CollectionUtil.union(this.abruptCompletionTargets, other.abruptCompletionTargets);
1✔
1603
            return this;
1✔
1604
        }
1605

1606
        static OptionalBool mergeCertitude(OptionalBool first, OptionalBool other) {
1607
            if (first.isKnown() && other.isKnown()) {
1✔
1608
                return first == other ? first : OptionalBool.UNKNOWN;
1✔
1609
            }
1610
            return OptionalBool.UNKNOWN;
1✔
1611
        }
1612

1613

1614
        @Override
1615
        public String toString() {
1616
            return symtable.toString();
×
1617
        }
1618
    }
1619

1620
    static class TargetStack {
1✔
1621

1622
        final Deque<SpanInfo> unnamedTargets = new ArrayDeque<>();
1✔
1623
        final Map<String, SpanInfo> namedTargets = new LinkedHashMap<>();
1✔
1624

1625

1626
        void push(SpanInfo state) {
1627
            unnamedTargets.push(state);
1✔
1628
        }
1✔
1629

1630
        SpanInfo pop() {
1631
            return unnamedTargets.pop();
1✔
1632
        }
1633

1634
        SpanInfo peek() {
1635
            return unnamedTargets.getFirst();
×
1636
        }
1637

1638
        SpanInfo doBreak(SpanInfo data, /* nullable */ String label) {
1639
            // basically, reaching defs at the point of the break
1640
            // also reach after the break (wherever it lands)
1641
            SpanInfo target;
1642
            if (label == null) {
1✔
1643
                target = unnamedTargets.getFirst();
1✔
1644
            } else {
1645
                target = namedTargets.get(label);
1✔
1646
            }
1647

1648
            if (target != null) { // otherwise CT error
1!
1649
                target.absorb(data);
1✔
1650
                return data.abruptCompletion(target);
1✔
1651
            }
1652
            return data;
×
1653
        }
1654
    }
1655

1656
    public static class AssignmentEntry implements Comparable<AssignmentEntry> {
1657

1658
        final JVariableSymbol var;
1659
        final ASTVariableId node;
1660

1661
        // this is not necessarily an expression, it may be also the
1662
        // variable declarator of a foreach loop
1663
        final JavaNode rhs;
1664

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

1677
        public boolean isInitializer() {
1678
            return rhs.getParent() instanceof ASTVariableDeclarator
1✔
1679
                && rhs.getIndexInParent() > 0;
1✔
1680
        }
1681

1682
        public boolean isBlankDeclaration() {
1683
            return rhs instanceof ASTVariableId;
1✔
1684
        }
1685

1686
        public boolean isFieldDefaultValue() {
1687
            return isBlankDeclaration() && isField();
1✔
1688
        }
1689

1690
        /**
1691
         * A blank local that has no value (ie not a catch param or formal).
1692
         */
1693
        public boolean isBlankLocal() {
1694
            return isBlankDeclaration() && node.isLocalVariable();
1!
1695
        }
1696

1697
        public boolean isUnaryReassign() {
1698
            return rhs instanceof ASTUnaryExpression
1✔
1699
                && ReachingDefsVisitor.getVarIfUnaryAssignment((ASTUnaryExpression) rhs) == var; // NOPMD #3205
1✔
1700
        }
1701

1702
        @Override
1703
        public int compareTo(AssignmentEntry o) {
1704
            return this.rhs.compareLocation(o.rhs);
1✔
1705
        }
1706

1707
        public int getLine() {
1708
            return getLocation().getBeginLine();
1✔
1709
        }
1710

1711
        public boolean isField() {
1712
            return var instanceof JFieldSymbol;
1✔
1713
        }
1714

1715
        public boolean isForeachVar() {
1716
            return node.isForeachVariable();
1✔
1717
        }
1718

1719
        public ASTVariableId getVarId() {
1720
            return node;
1✔
1721
        }
1722

1723

1724
        public JavaNode getLocation() {
1725
            return rhs;
1✔
1726
        }
1727

1728
        // todo i'm probably missing some
1729

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

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

1767
        /**
1768
         * If true, then this "assignment" is not real. We conservatively
1769
         * assume that the variable may have been set to another value by
1770
         * a call to some external code.
1771
         *
1772
         * @see #isFieldAssignmentAtEndOfCtor()
1773
         * @see #isFieldAssignmentAtStartOfMethod()
1774
         */
1775
        public boolean isUnbound() {
1776
            return false;
1✔
1777
        }
1778

1779
        /**
1780
         * If true, then this "assignment" is the placeholder value given
1781
         * to an instance field before a method starts. This is a subset of
1782
         * {@link #isUnbound()}.
1783
         */
1784
        public boolean isFieldAssignmentAtStartOfMethod() {
1785
            return false;
1✔
1786
        }
1787

1788
        /**
1789
         * If true, then this "assignment" is the placeholder value given
1790
         * to a non-final instance field after a ctor ends. This is a subset of
1791
         * {@link #isUnbound()}.
1792
         */
1793
        public boolean isFieldAssignmentAtEndOfCtor() {
1794
            return false;
1✔
1795
        }
1796

1797
        @Override
1798
        public String toString() {
1799
            return var.getSimpleName() + " := " + rhs;
×
1800
        }
1801

1802
        @Override
1803
        public boolean equals(Object o) {
1804
            if (this == o) {
1!
1805
                return true;
×
1806
            }
1807
            if (o == null || getClass() != o.getClass()) {
1!
1808
                return false;
1✔
1809
            }
1810
            AssignmentEntry that = (AssignmentEntry) o;
1✔
1811
            return Objects.equals(var, that.var)
1!
1812
                && Objects.equals(rhs, that.rhs);
1!
1813
        }
1814

1815
        @Override
1816
        public int hashCode() {
1817
            return 31 * var.hashCode() + rhs.hashCode();
1✔
1818
        }
1819
    }
1820

1821
    static class UnboundAssignment extends AssignmentEntry {
1822

1823
        private final SpecialAssignmentKind kind;
1824

1825
        UnboundAssignment(JVariableSymbol var, ASTVariableId node, JavaNode rhs, SpecialAssignmentKind kind) {
1826
            super(var, node, rhs);
1✔
1827
            this.kind = kind;
1✔
1828
        }
1✔
1829

1830
        @Override
1831
        public boolean isUnbound() {
1832
            return true;
1✔
1833
        }
1834

1835
        @Override
1836
        public boolean isFieldAssignmentAtStartOfMethod() {
1837
            return kind == SpecialAssignmentKind.INITIAL_FIELD_VALUE;
1✔
1838
        }
1839

1840
        @Override
1841
        public boolean isFieldAssignmentAtEndOfCtor() {
1842
            return rhs instanceof ASTTypeDeclaration;
1✔
1843
        }
1844
    }
1845

1846
    enum SpecialAssignmentKind {
1✔
1847
        NOT_SPECIAL,
1✔
1848
        UNKNOWN_METHOD_CALL,
1✔
1849
        INITIAL_FIELD_VALUE;
1✔
1850

1851
        boolean shouldJoinWithPreviousAssignment() {
1852
            return this == UNKNOWN_METHOD_CALL;
1✔
1853
        }
1854
    }
1855
}
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