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

pmd / pmd / 277

27 Nov 2025 01:37PM UTC coverage: 78.778% (+0.03%) from 78.749%
277

push

github

adangel
[java] UseArraysAsList: skip when if-statements (#6228)

18419 of 24233 branches covered (76.01%)

Branch coverage included in aggregate %.

40090 of 50038 relevant lines covered (80.12%)

0.81 hits per line

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

88.87
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/JavaAstUtils.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.ast.internal;
6

7
import static net.sourceforge.pmd.lang.java.ast.JavaTokenKinds.FORMAL_COMMENT;
8
import static net.sourceforge.pmd.lang.java.ast.JavaTokenKinds.MULTI_LINE_COMMENT;
9
import static net.sourceforge.pmd.lang.java.ast.JavaTokenKinds.SINGLE_LINE_COMMENT;
10

11
import java.lang.reflect.Modifier;
12
import java.util.ArrayList;
13
import java.util.Collection;
14
import java.util.Collections;
15
import java.util.Iterator;
16
import java.util.List;
17
import java.util.Objects;
18
import java.util.Set;
19
import java.util.function.Function;
20
import java.util.stream.Collectors;
21

22
import org.checkerframework.checker.nullness.qual.NonNull;
23
import org.checkerframework.checker.nullness.qual.Nullable;
24

25
import net.sourceforge.pmd.lang.ast.GenericToken;
26
import net.sourceforge.pmd.lang.ast.Node;
27
import net.sourceforge.pmd.lang.ast.NodeStream;
28
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
29
import net.sourceforge.pmd.lang.document.Chars;
30
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
31
import net.sourceforge.pmd.lang.java.ast.ASTArrayAllocation;
32
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
33
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.AccessType;
34
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression;
35
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
36
import net.sourceforge.pmd.lang.java.ast.ASTBodyDeclaration;
37
import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral;
38
import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
39
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
40
import net.sourceforge.pmd.lang.java.ast.ASTCatchClause;
41
import net.sourceforge.pmd.lang.java.ast.ASTClassType;
42
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
43
import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant;
44
import net.sourceforge.pmd.lang.java.ast.ASTExecutableDeclaration;
45
import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation;
46
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
47
import net.sourceforge.pmd.lang.java.ast.ASTExpressionStatement;
48
import net.sourceforge.pmd.lang.java.ast.ASTFieldAccess;
49
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
50
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
51
import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement;
52
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
53
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
54
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
55
import net.sourceforge.pmd.lang.java.ast.ASTInitializer;
56
import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement;
57
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
58
import net.sourceforge.pmd.lang.java.ast.ASTList;
59
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
60
import net.sourceforge.pmd.lang.java.ast.ASTLoopStatement;
61
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
62
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
63
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
64
import net.sourceforge.pmd.lang.java.ast.ASTNumericLiteral;
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.ASTSuperExpression;
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.ASTSwitchLike;
71
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
72
import net.sourceforge.pmd.lang.java.ast.ASTThisExpression;
73
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
74
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
75
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression;
76
import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
77
import net.sourceforge.pmd.lang.java.ast.ASTVariableId;
78
import net.sourceforge.pmd.lang.java.ast.Annotatable;
79
import net.sourceforge.pmd.lang.java.ast.AssignmentOp;
80
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
81
import net.sourceforge.pmd.lang.java.ast.JavaNode;
82
import net.sourceforge.pmd.lang.java.ast.JavaTokenKinds;
83
import net.sourceforge.pmd.lang.java.ast.ModifierOwner.Visibility;
84
import net.sourceforge.pmd.lang.java.ast.QualifiableExpression;
85
import net.sourceforge.pmd.lang.java.ast.ReturnScopeNode;
86
import net.sourceforge.pmd.lang.java.ast.TypeNode;
87
import net.sourceforge.pmd.lang.java.ast.UnaryOp;
88
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass;
89
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass.DataflowResult;
90
import net.sourceforge.pmd.lang.java.rule.internal.DataflowPass.ReachingDefinitionSet;
91
import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil;
92
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
93
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
94
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
95
import net.sourceforge.pmd.lang.java.symbols.internal.ast.AstLocalVarSym;
96
import net.sourceforge.pmd.lang.java.types.JMethodSig;
97
import net.sourceforge.pmd.lang.java.types.JPrimitiveType;
98
import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind;
99
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
100
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
101
import net.sourceforge.pmd.util.CollectionUtil;
102
import net.sourceforge.pmd.util.OptionalBool;
103

104
/**
105
 * Common utility functions to work with the Java AST. See also
106
 * {@link TypeTestUtil}. Only add here things that are not specific to
107
 * rules (use {@link JavaRuleUtil} for that). This API may be eventually
108
 * published.
109
 */
110
public final class JavaAstUtils {
111

112
    private JavaAstUtils() {
113
        // utility class
114
    }
115

116

117
    public static boolean isConditional(JavaNode ifx) {
118
        return isInfixExprWithOperator(ifx, BinaryOp.CONDITIONAL_OPS);
1✔
119
    }
120

121
    public static int numAlternatives(ASTSwitchBranch n) {
122
        return n.isDefault() ? 1 : n.getLabel().getExprList().count();
1✔
123
    }
124

125
    /**
126
     * Returns true if this is a numeric literal with the given int value.
127
     * This also considers long literals.
128
     */
129
    public static boolean isLiteralInt(JavaNode e, int value) {
130
        return e instanceof ASTNumericLiteral
1✔
131
                && ((ASTNumericLiteral) e).isIntegral()
1!
132
                && ((ASTNumericLiteral) e).getValueAsInt() == value;
1✔
133
    }
134

135
    /** This is type-aware, so will not pick up on numeric addition. */
136
    public static boolean isStringConcatExpr(@Nullable JavaNode e) {
137
        if (e instanceof ASTInfixExpression) {
1✔
138
            ASTInfixExpression infix = (ASTInfixExpression) e;
1✔
139
            return infix.getOperator() == BinaryOp.ADD && TypeTestUtil.isA(String.class, infix);
1✔
140
        }
141
        return false;
1✔
142
    }
143

144
    /**
145
     * If the parameter is an operand of a binary infix expression,
146
     * returns the other operand. Otherwise returns null.
147
     */
148
    public static @Nullable ASTExpression getOtherOperandIfInInfixExpr(@Nullable JavaNode e) {
149
        if (e != null && e.getParent() instanceof ASTInfixExpression) {
1!
150
            return (ASTExpression) e.getParent().getChild(1 - e.getIndexInParent());
1✔
151
        }
152
        return null;
×
153
    }
154

155
    public static @Nullable ASTExpression getOtherOperandIfInAssignmentExpr(@Nullable JavaNode e) {
156
        if (e != null && e.getParent() instanceof ASTAssignmentExpression) {
1!
157
            return (ASTExpression) e.getParent().getChild(1 - e.getIndexInParent());
1✔
158
        }
159
        return null;
1✔
160
    }
161

162
    /**
163
     * Returns true if the node is a {@link ASTMethodDeclaration} that
164
     * is a main method.
165
     */
166
    public static boolean isMainMethod(JavaNode node) {
167
        return node instanceof ASTMethodDeclaration
1✔
168
                && ((ASTMethodDeclaration) node).isMainMethod();
1✔
169
    }
170

171
    public static boolean hasField(ASTTypeDeclaration node, String name) {
172
        if (node == null) {
1!
173
            return false;
×
174
        }
175

176
        for (JFieldSymbol f : node.getSymbol().getDeclaredFields()) {
1✔
177
            String fname = f.getSimpleName();
1✔
178
            if (fname.startsWith("m_") || fname.startsWith("_")) {
1!
179
                fname = fname.substring(fname.indexOf('_') + 1);
1✔
180
            }
181
            if (fname.equalsIgnoreCase(name)) {
1✔
182
                return true;
1✔
183
            }
184
        }
1✔
185
        return false;
1✔
186
    }
187

188
    /**
189
     * Returns true if the formal parameters of the method or constructor
190
     * match the given types exactly. Note that for varargs methods, the
191
     * last param must have an array type (but it is not checked to be varargs).
192
     * This will return false if we're not sure.
193
     *
194
     * @param node  Method or ctor
195
     * @param types List of types to match (may be empty)
196
     *
197
     * @throws NullPointerException If any of the classes is null, or the node is null
198
     * @see TypeTestUtil#isExactlyA(Class, TypeNode)
199
     */
200
    public static boolean hasParameters(ASTExecutableDeclaration node, Class<?>... types) {
201
        ASTFormalParameters formals = node.getFormalParameters();
1✔
202
        if (formals.size() != types.length) {
1✔
203
            return false;
1✔
204
        }
205
        for (int i = 0; i < formals.size(); i++) {
1✔
206
            ASTFormalParameter fi = formals.get(i);
1✔
207
            if (!TypeTestUtil.isExactlyA(types[i], fi)) {
1✔
208
                return false;
1✔
209
            }
210
        }
211
        return true;
1✔
212
    }
213

214
    /**
215
     * Returns true if the {@code throws} declaration of the method or constructor
216
     * matches the given types exactly.
217
     *
218
     * @param node  Method or ctor
219
     * @param types List of exception types to match (may be empty)
220
     *
221
     * @throws NullPointerException If any of the classes is null, or the node is null
222
     * @see TypeTestUtil#isExactlyA(Class, TypeNode)
223
     */
224
    @SafeVarargs
225
    public static boolean hasExceptionList(ASTExecutableDeclaration node, Class<? extends Throwable>... types) {
226
        @NonNull List<ASTClassType> formals = ASTList.orEmpty(node.getThrowsList());
1✔
227
        if (formals.size() != types.length) {
1✔
228
            return false;
1✔
229
        }
230
        for (int i = 0; i < formals.size(); i++) {
1✔
231
            ASTClassType fi = formals.get(i);
1✔
232
            if (!TypeTestUtil.isExactlyA(types[i], fi)) {
1!
233
                return false;
×
234
            }
235
        }
236
        return true;
1✔
237
    }
238

239
    /**
240
     * True if the variable is never used. Note that the visibility of
241
     * the variable must be less than {@link Visibility#V_PRIVATE} for
242
     * us to be sure of it.
243
     */
244
    public static boolean isNeverUsed(ASTVariableId varId) {
245
        return CollectionUtil.none(varId.getLocalUsages(), JavaAstUtils::isReadUsage);
1✔
246
    }
247

248
    private static boolean isReadUsage(ASTNamedReferenceExpr expr) {
249
        return expr.getAccessType() == AccessType.READ
1✔
250
            // x++ as a method argument or used in other expression
251
            || ((expr.getParent() instanceof ASTUnaryExpression
1✔
252
            // compound assignments like '+=' have AccessType.WRITE, but can also be used in another expression
253
            || isCompoundAssignment(expr))
1✔
254
            && !(expr.getParent().getParent() instanceof ASTExpressionStatement));
1✔
255
    }
256

257
    private static boolean isCompoundAssignment(ASTNamedReferenceExpr expr) {
258
        return expr.getParent() instanceof ASTAssignmentExpression && ((ASTAssignmentExpression) expr.getParent()).isCompound();
1!
259
    }
260

261
    /**
262
     * True if the variable is incremented or decremented via a compound
263
     * assignment operator, or a unary increment/decrement expression.
264
     */
265
    public static boolean isVarAccessReadAndWrite(ASTNamedReferenceExpr expr) {
266
        return expr.getAccessType() == AccessType.WRITE
1✔
267
            && (!(expr.getParent() instanceof ASTAssignmentExpression)
1✔
268
            || ((ASTAssignmentExpression) expr.getParent()).getOperator().isCompound());
1!
269
    }
270

271
    /**
272
     * True if the variable access is a non-compound assignment.
273
     */
274
    public static boolean isVarAccessStrictlyWrite(ASTNamedReferenceExpr expr) {
275
        return expr.getParent() instanceof ASTAssignmentExpression
1✔
276
            && expr.getIndexInParent() == 0
1✔
277
            && !((ASTAssignmentExpression) expr.getParent()).getOperator().isCompound();
1✔
278
    }
279

280
    /**
281
     * Returns the set of labels on this statement.
282
     */
283
    public static Set<String> getStatementLabels(ASTStatement node) {
284
        if (!(node.getParent() instanceof ASTLabeledStatement)) {
1✔
285
            return Collections.emptySet();
1✔
286
        }
287

288
        return node.ancestors().takeWhile(it -> it instanceof ASTLabeledStatement)
1✔
289
                   .toStream()
1✔
290
                   .map(it -> ((ASTLabeledStatement) it).getLabel())
1✔
291
                   .collect(Collectors.toSet());
1✔
292
    }
293

294
    public static boolean isAnonymousClassCreation(@Nullable ASTExpression expression) {
295
        return expression instanceof ASTConstructorCall
1✔
296
                && ((ASTConstructorCall) expression).isAnonymousClass();
1✔
297
    }
298

299
    /**
300
     * Will cut through argument lists, except those of enum constants
301
     * and explicit invocation nodes.
302
     */
303
    public static @NonNull ASTExpression getTopLevelExpr(ASTExpression expr) {
304
        JavaNode last = expr.ancestorsOrSelf()
1✔
305
                            .takeWhile(it ->
1✔
306
                                    it instanceof ASTExpression && !(it instanceof ASTLambdaExpression)
1✔
307
                                        || it instanceof ASTArgumentList && it.getParent() instanceof ASTExpression
1!
308
                            )
309
                            .last();
1✔
310
        return (ASTExpression) Objects.requireNonNull(last);
1✔
311
    }
312

313
    /**
314
     * Returns the variable IDS corresponding to variables declared in
315
     * the init clause of the loop.
316
     */
317
    public static NodeStream<ASTVariableId> getLoopVariables(ASTForStatement loop) {
318
        return NodeStream.of(loop.getInit())
1✔
319
                         .filterIs(ASTLocalVariableDeclaration.class)
1✔
320
                         .flatMap(ASTLocalVariableDeclaration::getVarIds);
1✔
321
    }
322

323
    /**
324
     * Whether one expression is the boolean negation of the other. Many
325
     * forms are not yet supported. This method is symmetric so only needs
326
     * to be called once.
327
     */
328
    public static boolean areComplements(ASTExpression e1, ASTExpression e2) {
329
        if (isBooleanNegation(e1)) {
1✔
330
            return areEqual(unaryOperand(e1), e2);
1✔
331
        } else if (isBooleanNegation(e2)) {
1✔
332
            return areEqual(e1, unaryOperand(e2));
1✔
333
        } else if (e1 instanceof ASTInfixExpression && e2 instanceof ASTInfixExpression) {
1✔
334
            ASTInfixExpression ifx1 = (ASTInfixExpression) e1;
1✔
335
            ASTInfixExpression ifx2 = (ASTInfixExpression) e2;
1✔
336
            if (ifx1.getOperator().getComplement() != ifx2.getOperator()) {
1✔
337
                return false;
1✔
338
            }
339
            if (ifx1.getOperator().hasSamePrecedenceAs(BinaryOp.EQ)) {
1✔
340
                // NOT(a == b, a != b)
341
                // NOT(a == b, b != a)
342
                return areEqual(ifx1.getLeftOperand(), ifx2.getLeftOperand())
1!
343
                    && areEqual(ifx1.getRightOperand(), ifx2.getRightOperand())
1!
344
                    || areEqual(ifx2.getLeftOperand(), ifx1.getLeftOperand())
×
345
                    && areEqual(ifx2.getRightOperand(), ifx1.getRightOperand());
1!
346
            }
347
            // todo we could continue with de Morgan and such
348
        }
349
        return false;
1✔
350
    }
351

352
    private static boolean areEqual(ASTExpression e1, ASTExpression e2) {
353
        return tokenEquals(e1, e2);
1✔
354
    }
355

356
    /**
357
     * Returns true if both nodes have exactly the same tokens.
358
     *
359
     * @param node First node
360
     * @param that Other node
361
     */
362
    public static boolean tokenEquals(JavaNode node, JavaNode that) {
363
        return tokenEquals(node, that, null);
1✔
364
    }
365

366
    /**
367
     * Returns true if both nodes have the same tokens, modulo some renaming
368
     * function. The renaming function maps unqualified variables and type
369
     * identifiers of the first node to the other. This should be used
370
     * in nodes living in the same lexical scope, so that unqualified
371
     * names mean the same thing.
372
     *
373
     * @param node       First node
374
     * @param other      Other node
375
     * @param varRenamer A renaming function. If null, no renaming is applied.
376
     *                   Must not return null, if no renaming occurs, returns its argument.
377
     */
378
    public static boolean tokenEquals(@NonNull JavaNode node,
379
                                      @NonNull JavaNode other,
380
                                      @Nullable Function<String, @NonNull String> varRenamer) {
381
        // Since type and variable names obscure one another,
382
        // it's ok to use a single renaming function.
383

384
        Iterator<JavaccToken> thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()).iterator();
1✔
385
        Iterator<JavaccToken> thatIt = GenericToken.range(other.getFirstToken(), other.getLastToken()).iterator();
1✔
386
        int lastKind = 0;
1✔
387
        while (thisIt.hasNext()) {
1✔
388
            if (!thatIt.hasNext()) {
1✔
389
                return false;
1✔
390
            }
391
            JavaccToken o1 = thisIt.next();
1✔
392
            JavaccToken o2 = thatIt.next();
1✔
393
            if (o1.kind != o2.kind) {
1✔
394
                return false;
1✔
395
            }
396

397
            String mappedImage = o1.getImage();
1✔
398
            if (varRenamer != null
1!
399
                && o1.kind == JavaTokenKinds.IDENTIFIER
400
                && lastKind != JavaTokenKinds.DOT
401
                && lastKind != JavaTokenKinds.METHOD_REF
402
                //method name
403
                && o1.getNext() != null && o1.getNext().kind != JavaTokenKinds.LPAREN) {
1!
404
                mappedImage = varRenamer.apply(mappedImage);
1✔
405
            }
406

407
            if (!o2.getImage().equals(mappedImage)) {
1✔
408
                return false;
1✔
409
            }
410

411
            lastKind = o1.kind;
1✔
412
        }
1✔
413
        return !thatIt.hasNext();
1✔
414
    }
415

416
    public static boolean isNullLiteral(ASTExpression node) {
417
        return node instanceof ASTNullLiteral;
1✔
418
    }
419

420
    /** Returns true if the node is a boolean literal with any value. */
421
    public static boolean isBooleanLiteral(JavaNode e) {
422
        return e instanceof ASTBooleanLiteral;
1✔
423
    }
424

425
    /** Returns true if the node is a boolean literal with the given constant value. */
426
    public static boolean isBooleanLiteral(JavaNode e, boolean value) {
427
        return e instanceof ASTBooleanLiteral && ((ASTBooleanLiteral) e).isTrue() == value;
1✔
428
    }
429

430
    public static boolean isBooleanNegation(JavaNode e) {
431
        return e instanceof ASTUnaryExpression && ((ASTUnaryExpression) e).getOperator() == UnaryOp.NEGATION;
1✔
432
    }
433

434
    /**
435
     * If the argument is a unary expression, returns its operand, otherwise
436
     * returns null.
437
     */
438
    public static @Nullable ASTExpression unaryOperand(@Nullable ASTExpression e) {
439
        return e instanceof ASTUnaryExpression ? ((ASTUnaryExpression) e).getOperand()
1!
440
                                               : null;
×
441
    }
442

443
    /**
444
     * Whether the expression is an access to a field of this instance,
445
     * not inherited, qualified or not ({@code this.field} or just {@code field}).
446
     */
447
    public static boolean isThisFieldAccess(ASTExpression e) {
448
        if (!(e instanceof ASTNamedReferenceExpr)) {
1!
449
            return false;
×
450
        }
451
        JVariableSymbol sym = ((ASTNamedReferenceExpr) e).getReferencedSym();
1✔
452
        return sym instanceof JFieldSymbol
1!
453
                && !((JFieldSymbol) sym).isStatic()
1✔
454
                // not inherited
455
                && ((JFieldSymbol) sym).getEnclosingClass().equals(e.getEnclosingType().getSymbol())
1✔
456
                // correct syntactic form
457
                && (e instanceof ASTVariableAccess || isSyntacticThisFieldAccess(e));
1✔
458
    }
459

460
    /**
461
     * Whether the expression is a {@code this.field}, with no outer
462
     * instance qualifier ({@code Outer.this.field}). The field symbol
463
     * is not checked to resolve to a field declared in this class (it
464
     * may be inherited)
465
     */
466
    public static boolean isSyntacticThisFieldAccess(ASTExpression e) {
467
        if (e instanceof ASTFieldAccess) {
1✔
468
            ASTExpression qualifier = ((ASTFieldAccess) e).getQualifier();
1✔
469
            if (qualifier instanceof ASTThisExpression) {
1✔
470
                // unqualified this
471
                return ((ASTThisExpression) qualifier).getQualifier() == null;
1!
472
            }
473
        }
474
        return false;
1✔
475
    }
476

477
    public static boolean hasAnyAnnotation(Annotatable node, Collection<String> qualifiedNames) {
478
        return node != null && qualifiedNames.stream().anyMatch(node::isAnnotationPresent);
1!
479
    }
480

481
    /**
482
     * Returns true if the expression is the default field value for
483
     * the given type.
484
     */
485
    public static boolean isDefaultValue(JTypeMirror type, ASTExpression expr) {
486
        if (type.isPrimitive()) {
1✔
487
            if (type.isPrimitive(PrimitiveTypeKind.BOOLEAN)) {
1✔
488
                return expr instanceof ASTBooleanLiteral && !((ASTBooleanLiteral) expr).isTrue();
1✔
489
            } else {
490
                Object constValue = expr.getConstValue();
1✔
491
                return constValue instanceof Number && ((Number) constValue).doubleValue() == 0d
1✔
492
                    || constValue instanceof Character && constValue.equals('\u0000');
1✔
493
            }
494
        } else {
495
            return expr instanceof ASTNullLiteral;
1✔
496
        }
497
    }
498

499
    /**
500
     * Returns true if the expression is a {@link ASTNamedReferenceExpr}
501
     * that references the symbol.
502
     */
503
    public static boolean isReferenceToVar(@Nullable ASTExpression expression, @NonNull JVariableSymbol symbol) {
504
        return expression instanceof ASTNamedReferenceExpr
1✔
505
            && symbol.equals(((ASTNamedReferenceExpr) expression).getReferencedSym());
1✔
506
    }
507

508
    public static boolean isUnqualifiedThis(ASTExpression e) {
509
        return e instanceof ASTThisExpression && ((ASTThisExpression) e).getQualifier() == null;
1✔
510
    }
511

512
    public static boolean isUnqualifiedSuper(ASTExpression e) {
513
        return e instanceof ASTSuperExpression && ((ASTSuperExpression) e).getQualifier() == null;
1✔
514
    }
515

516
    public static boolean isUnqualifiedThisOrSuper(ASTExpression e) {
517
        return isUnqualifiedSuper(e) || isUnqualifiedThis(e);
1✔
518
    }
519

520
    /**
521
     * Returns true if the expression is a {@link ASTNamedReferenceExpr}
522
     * that references any of the symbol in the set.
523
     */
524
    public static boolean isReferenceToVar(@Nullable ASTExpression expression, @NonNull Set<? extends JVariableSymbol> symbols) {
525
        return expression instanceof ASTNamedReferenceExpr
1!
526
            && symbols.contains(((ASTNamedReferenceExpr) expression).getReferencedSym());
1✔
527
    }
528

529
    /**
530
     * Returns true if both expressions refer to the same variable.
531
     * A "variable" here can also means a field path, eg, {@code this.field.a}.
532
     * This method unifies {@code this.field} and {@code field} if possible,
533
     * and also considers {@code this}.
534
     *
535
     * <p>Note that while this is more useful than just checking whether
536
     * both expressions access the same symbol, it still does not mean that
537
     * they both access the same <i>value</i>. The actual value is data-flow
538
     * dependent.
539
     */
540
    public static boolean isReferenceToSameVar(ASTExpression e1, ASTExpression e2) {
541
        if (e1 instanceof ASTNamedReferenceExpr && e2 instanceof ASTNamedReferenceExpr) {
1✔
542
            if (OptionalBool.YES != referenceSameSymbol((ASTNamedReferenceExpr) e1, (ASTNamedReferenceExpr) e2)) {
1✔
543
                return false;
1✔
544
            }
545

546
            if (e1.getClass() != e2.getClass()) {
1✔
547
                // unify `this.f` and `f`
548
                // note, we already know that the symbol is the same so there's no scoping problem
549
                return isSyntacticThisFieldAccess(e1) || isSyntacticThisFieldAccess(e2);
1!
550
            } else if (e1 instanceof ASTFieldAccess && e2 instanceof ASTFieldAccess) {
1!
551
                return isReferenceToSameVar(((ASTFieldAccess) e1).getQualifier(),
1✔
552
                                            ((ASTFieldAccess) e2).getQualifier());
1✔
553
            }
554
            return e1 instanceof ASTVariableAccess && e2 instanceof ASTVariableAccess;
1!
555
        } else if (e1 instanceof ASTThisExpression || e2 instanceof ASTThisExpression) {
1✔
556
            return e1.getClass() == e2.getClass();
1!
557
        }
558
        return false;
1✔
559
    }
560

561
    private static OptionalBool referenceSameSymbol(ASTNamedReferenceExpr e1, ASTNamedReferenceExpr e2) {
562
        if (!e1.getName().equals(e2.getName())) {
1✔
563
            return OptionalBool.NO;
1✔
564
        }
565
        JVariableSymbol ref1 = e1.getReferencedSym();
1✔
566
        JVariableSymbol ref2 = e2.getReferencedSym();
1✔
567
        if (ref1 == null || ref2 == null) {
1!
568
            return OptionalBool.UNKNOWN;
1✔
569
        }
570
        return OptionalBool.definitely(ref1.equals(ref2));
1✔
571
    }
572

573
    /**
574
     * Returns true if the expression is a reference to a local variable.
575
     */
576
    public static boolean isReferenceToLocal(ASTExpression expr) {
577
        return expr instanceof ASTVariableAccess
1✔
578
                && ((ASTVariableAccess) expr).getReferencedSym() instanceof AstLocalVarSym;
1✔
579
    }
580

581
    /**
582
     * Returns true if the expression has the form `field`, or `this.field`,
583
     * where `field` is a field declared in the enclosing class. Considers
584
     * inherited fields. Assumes we're not in a static context.
585
     */
586
    public static boolean isRefToFieldOfThisInstance(ASTExpression usage) {
587
        if (!(usage instanceof ASTNamedReferenceExpr)) {
1!
588
            return false;
×
589
        }
590
        JVariableSymbol symbol = ((ASTNamedReferenceExpr) usage).getReferencedSym();
1✔
591
        if (!(symbol instanceof JFieldSymbol)) {
1✔
592
            return false;
1✔
593
        }
594

595
        if (usage instanceof ASTVariableAccess) {
1✔
596
            return !Modifier.isStatic(((JFieldSymbol) symbol).getModifiers());
1✔
597
        } else if (usage instanceof ASTFieldAccess) {
1!
598
            return isUnqualifiedThisOrSuper(((ASTFieldAccess) usage).getQualifier());
1✔
599
        }
600
        return false;
×
601
    }
602

603
    /**
604
     * Returns true if the expression is a reference to a field declared
605
     * in this class (not a superclass), on any instance (not just `this`).
606
     */
607
    public static boolean isRefToFieldOfThisClass(ASTExpression usage) {
608
        if (!(usage instanceof ASTNamedReferenceExpr)) {
1!
609
            return false;
×
610
        }
611
        JVariableSymbol symbol = ((ASTNamedReferenceExpr) usage).getReferencedSym();
1✔
612
        if (!(symbol instanceof JFieldSymbol)) {
1✔
613
            return false;
1✔
614
        }
615

616
        if (usage instanceof ASTVariableAccess) {
1✔
617
            return !Modifier.isStatic(((JFieldSymbol) symbol).getModifiers());
1✔
618
        } else if (usage instanceof ASTFieldAccess) {
1!
619
            if (usage.getEnclosingType() != null) {
1!
620
                return Objects.equals(((JFieldSymbol) symbol).getEnclosingClass(),
1✔
621
                        usage.getEnclosingType().getSymbol());
1✔
622
            }
623
        }
624
        return false;
×
625
    }
626

627
    /**
628
     * Return whether the method call is a call whose receiver is the
629
     * {@code this} object. This is the case also if the method is called
630
     * with a {@code super} qualifier, or if it is not syntactically
631
     * qualified but refers to a non-static method of the enclosing class.
632
     */
633
    public static OptionalBool isCallOnThisInstance(ASTMethodCall call) {
634
        // syntactic approach.
635
        if (call.getQualifier() != null) {
1✔
636
            return OptionalBool.definitely(isUnqualifiedThisOrSuper(call.getQualifier()));
1✔
637
        }
638

639
        // unqualified call
640
        JMethodSig mtype = call.getMethodType();
1✔
641
        JExecutableSymbol methodSym = mtype.getSymbol();
1✔
642
        if (methodSym.isUnresolved()) {
1✔
643
            return OptionalBool.UNKNOWN;
1✔
644
        }
645
        return OptionalBool.definitely(
1✔
646
            !methodSym.isStatic()
1✔
647
                && methodSym.getEnclosingClass().equals(call.getEnclosingType().getSymbol())
1✔
648
        );
649
    }
650

651
    public static ASTClassType getThisOrSuperQualifier(ASTExpression expr) {
652
        if (expr instanceof ASTThisExpression) {
×
653
            return ((ASTThisExpression) expr).getQualifier();
×
654
        } else if (expr instanceof ASTSuperExpression) {
×
655
            return ((ASTSuperExpression) expr).getQualifier();
×
656
        }
657
        return null;
×
658
    }
659

660
    public static boolean isThisOrSuper(ASTExpression expr) {
661
        return expr instanceof ASTThisExpression || expr instanceof ASTSuperExpression;
1✔
662
    }
663

664
    /**
665
     * Return a node stream containing all the operands of an addition expression.
666
     * For instance, {@code a+b+c} will be parsed as a tree with two levels.
667
     * This method will return a flat node stream containing {@code a, b, c}.
668
     *
669
     * @param e An expression, if it is not a string concatenation expression,
670
     *          then returns an empty node stream.
671
     */
672
    public static NodeStream<ASTExpression> flattenOperands(ASTExpression e) {
673
        List<ASTExpression> result = new ArrayList<>();
1✔
674
        flattenOperandsRec(e, result);
1✔
675
        return NodeStream.fromIterable(result);
1✔
676
    }
677

678
    private static void flattenOperandsRec(ASTExpression e, List<ASTExpression> result) {
679
        if (isStringConcatExpr(e)) {
1✔
680
            ASTInfixExpression infix = (ASTInfixExpression) e;
1✔
681
            flattenOperandsRec(infix.getLeftOperand(), result);
1✔
682
            flattenOperandsRec(infix.getRightOperand(), result);
1✔
683
        } else {
1✔
684
            result.add(e);
1✔
685
        }
686
    }
1✔
687

688
    /**
689
     * Returns true if the node is the last child of its parent. Returns
690
     * false if this is the root node.
691
     */
692
    public static boolean isLastChild(Node it) {
693
        Node parent = it.getParent();
1✔
694
        return parent != null && it.getIndexInParent() == parent.getNumChildren() - 1;
1!
695
    }
696

697
    /**
698
     * Returns a node stream of enclosing expressions in the same call chain.
699
     * For instance in {@code a.b().c().d()}, called on {@code a}, this will
700
     * yield {@code a.b()}, and {@code a.b().c()}.
701
     */
702
    @SuppressWarnings({"unchecked", "rawtypes"})
703
    public static NodeStream<QualifiableExpression> followingCallChain(ASTExpression expr) {
704
        return (NodeStream) expr.ancestors().takeWhile(it -> it instanceof QualifiableExpression);
1✔
705
    }
706

707
    public static ASTExpression peelCasts(@Nullable ASTExpression expr) {
708
        while (expr instanceof ASTCastExpression) {
1✔
709
            expr = ((ASTCastExpression) expr).getOperand();
1✔
710
        }
711
        return expr;
1✔
712
    }
713

714
    public static boolean isArrayInitializer(ASTExpression expr) {
715
        return expr instanceof ASTArrayAllocation && ((ASTArrayAllocation) expr).getArrayInitializer() != null;
1!
716
    }
717

718
    public static boolean isCloneMethod(ASTMethodDeclaration node) {
719
        // this is enough as in valid code, this signature overrides Object#clone
720
        // and the other things like visibility are checked by the compiler
721
        return "clone".equals(node.getName())
1✔
722
            && node.getArity() == 0
1✔
723
            && !node.isStatic();
1!
724
    }
725

726
    public static boolean isEqualsMethod(ASTMethodDeclaration node) {
727
        return "equals".equals(node.getName())
1✔
728
            && node.getArity() == 1
1✔
729
            && node.getFormalParameters().get(0).getTypeMirror().isTop()
1✔
730
            && !node.isStatic();
1!
731
    }
732

733
    public static boolean isHashCodeMethod(ASTMethodDeclaration node) {
734
        return "hashCode".equals(node.getName())
1✔
735
            && node.getArity() == 0
1✔
736
            && !node.isStatic();
1!
737
    }
738

739
    public static boolean isCompareToMethod(ASTMethodDeclaration method) {
740
        return "compareTo".equals(method.getName())
1✔
741
                && method.getArity() == 1
1!
742
                && method.getResultTypeNode().getTypeMirror().isPrimitive(JPrimitiveType.PrimitiveTypeKind.INT)
1!
743
                && !method.isStatic();
1!
744
    }
745

746
    public static boolean isArrayLengthFieldAccess(ASTExpression node) {
747
        if (node instanceof ASTFieldAccess) {
1✔
748
            ASTFieldAccess field = (ASTFieldAccess) node;
1✔
749
            return "length".equals(field.getName())
1✔
750
                && field.getQualifier().getTypeMirror().isArray();
1!
751
        }
752
        return false;
1✔
753
    }
754

755
    /**
756
     * @see ASTBreakStatement#getTarget()
757
     */
758
    public static boolean mayBeBreakTarget(JavaNode it) {
759
        return it instanceof ASTLoopStatement
1!
760
            || it instanceof ASTSwitchStatement
761
            || it instanceof ASTLabeledStatement;
762
    }
763

764
    /**
765
     * Tests if the node is an {@link ASTInfixExpression} with one of the given operators.
766
     */
767
    public static boolean isInfixExprWithOperator(@Nullable JavaNode e, Set<BinaryOp> operators) {
768
        if (e instanceof ASTInfixExpression) {
1✔
769
            ASTInfixExpression infix = (ASTInfixExpression) e;
1✔
770
            return operators.contains(infix.getOperator());
1✔
771
        }
772
        return false;
1✔
773
    }
774

775
    /**
776
     * Tests if the node is an {@link ASTInfixExpression} with the given operator.
777
     */
778
    public static boolean isInfixExprWithOperator(@Nullable JavaNode e, BinaryOp operator) {
779
        if (e instanceof ASTInfixExpression) {
1✔
780
            ASTInfixExpression infix = (ASTInfixExpression) e;
1✔
781
            return operator == infix.getOperator();
1✔
782
        }
783
        return false;
1✔
784
    }
785

786

787
    /**
788
     * Tests if the node is an {@link ASTAssignmentExpression} with the given operator.
789
     */
790
    public static boolean isAssignmentExprWithOperator(@Nullable JavaNode e, AssignmentOp operator) {
791
        if (e instanceof ASTAssignmentExpression) {
1!
792
            ASTAssignmentExpression infix = (ASTAssignmentExpression) e;
1✔
793
            return operator == infix.getOperator();
1!
794
        }
795
        return false;
×
796
    }
797

798
    /**
799
     * Returns true if the given token is a Java comment.
800
     */
801
    public static boolean isComment(JavaccToken t) {
802
        switch (t.kind) {
1✔
803
        case FORMAL_COMMENT:
804
        case MULTI_LINE_COMMENT:
805
        case SINGLE_LINE_COMMENT:
806
            return true;
1✔
807
        default:
808
            return false;
1✔
809
        }
810
    }
811

812
    public static boolean isMarkdownComment(JavaccToken token) {
813
        Chars text = token.getText();
1✔
814
        return token.kind == JavaTokenKinds.SINGLE_LINE_COMMENT && text.length() > 2 && text.charAt(2) == '/';
1✔
815
    }
816

817
    /**
818
     * Return true if the catch clause just rethrows the caught exception
819
     * immediately.
820
     */
821
    public static boolean isJustRethrowException(ASTCatchClause clause) {
822
        ASTBlock body = clause.getBody();
1✔
823
        if (body.size() == 1) {
1✔
824
            ASTStatement stmt = body.get(0);
1✔
825
            return stmt instanceof ASTThrowStatement
1✔
826
                && isReferenceToVar(((ASTThrowStatement) stmt).getExpr(),
1✔
827
                                    clause.getParameter().getVarId().getSymbol());
1✔
828
        }
829
        return false;
1✔
830
    }
831

832
    /**
833
     * Return true if the node is in static context relative to the deepest enclosing class.
834
     */
835
    public static boolean isInStaticCtx(JavaNode node) {
836
        return node.ancestorsOrSelf()
1✔
837
                   .map(NodeStream.asInstanceOf(ASTBodyDeclaration.class, ASTExplicitConstructorInvocation.class))
1✔
838
                   .take(1)
1✔
839
                   .any(it -> it instanceof ASTExecutableDeclaration && ((ASTExecutableDeclaration) it).isStatic()
1✔
840
                       || it instanceof ASTFieldDeclaration && ((ASTFieldDeclaration) it).isStatic()
1!
841
                       || it instanceof ASTInitializer && ((ASTInitializer) it).isStatic()
1!
842
                       || it instanceof ASTEnumConstant
843
                       || it instanceof ASTExplicitConstructorInvocation
844
                   );
845
    }
846

847
    /**
848
     * Return the target of the return.
849
     */
850
    public static @Nullable ReturnScopeNode getReturnTarget(ASTReturnStatement stmt) {
851
        return stmt.ancestors().first(ReturnScopeNode.class);
1✔
852
    }
853

854

855
    /**
856
     * Return true if the variable is effectively final. This means
857
     * the variable is never reassigned.
858
     */
859
    public static boolean isEffectivelyFinal(ASTVariableId var) {
860
        if (var.getInitializer() == null && var.isLocalVariable()) {
1✔
861
            // blank variables may be assigned on several paths
862
            DataflowResult dataflow = DataflowPass.getDataflowResult(var.getRoot());
1✔
863
            for (ASTNamedReferenceExpr usage : var.getLocalUsages()) {
1✔
864
                if (usage.getAccessType() == AccessType.WRITE) {
1✔
865
                    ReachingDefinitionSet reaching = dataflow.getReachingDefinitions(usage);
1✔
866
                    if (reaching.isNotFullyKnown() || !reaching.getReaching().isEmpty()) {
1!
867
                        // If the reaching def is not empty, then that means
868
                        // the assignment is killing another one, ie it is a reassignment.
869
                        return false;
1✔
870
                    }
871
                }
872
            }
1✔
873
            return true;
1✔
874
        }
875
        for (ASTNamedReferenceExpr usage : var.getLocalUsages()) {
1✔
876
            if (usage.getAccessType() == AccessType.WRITE) {
1✔
877
                return false;
1✔
878
            }
879
        }
1✔
880
        return true;
1✔
881
    }
882

883
    public static boolean isUnconditionalLoop(ASTLoopStatement loop) {
884
        return !(loop instanceof ASTForeachStatement)
1✔
885
                && (loop.getCondition() == null || isBooleanLiteral(loop.getCondition(), true));
1!
886
    }
887

888
    /**
889
     * Return true if this switch is total with respect to the scrutinee
890
     * type. This means, one of the branches will always be taken, and the
891
     * switch will never "not match". A switch with a default case is always
892
     * total. A switch expression is checked by the compiler for exhaustivity,
893
     * and we assume it is correct.
894
     */
895
    public static boolean isTotalSwitch(ASTSwitchLike switchLike) {
896
        if (switchLike instanceof ASTSwitchExpression || switchLike.hasDefaultCase()) {
1✔
897
            return true;
1✔
898
        }
899
        return switchLike.isExhaustive();
1✔
900
    }
901
}
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