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

pmd / pmd / 500

07 May 2026 02:03PM UTC coverage: 79.06% (+5.3%) from 73.787%
500

push

github

adangel
[java] Fix #4288: Document that CallSuperFirst and CallSuperLast are android only (#6601)

18785 of 24683 branches covered (76.11%)

Branch coverage included in aggregate %.

40922 of 50838 relevant lines covered (80.49%)

0.81 hits per line

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

88.69
/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
     * Given a statement inside a method, returns the ancestor statement that is directly below the method body.
315
     */
316
    public static @NonNull ASTStatement getMethodLevelStatement(ASTStatement expr) {
317
        JavaNode last = expr
1✔
318
                .ancestorsOrSelf()
1✔
319
                .takeWhile(node -> !(node.getParent() instanceof ASTMethodDeclaration))
1✔
320
                .last();
1✔
321
        return (ASTStatement) Objects.requireNonNull(last);
1✔
322
    }
323

324
    /**
325
     * Returns the variable IDS corresponding to variables declared in
326
     * the init clause of the loop.
327
     */
328
    public static NodeStream<ASTVariableId> getLoopVariables(ASTForStatement loop) {
329
        return NodeStream.of(loop.getInit())
1✔
330
                         .filterIs(ASTLocalVariableDeclaration.class)
1✔
331
                         .flatMap(ASTLocalVariableDeclaration::getVarIds);
1✔
332
    }
333

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

363
    private static boolean areEqual(ASTExpression e1, ASTExpression e2) {
364
        return tokenEquals(e1, e2);
1✔
365
    }
366

367
    /**
368
     * Returns true if both nodes have exactly the same tokens.
369
     *
370
     * @param node First node
371
     * @param that Other node
372
     */
373
    public static boolean tokenEquals(JavaNode node, JavaNode that) {
374
        return tokenEquals(node, that, null);
1✔
375
    }
376

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

395
        Iterator<JavaccToken> thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()).iterator();
1✔
396
        Iterator<JavaccToken> thatIt = GenericToken.range(other.getFirstToken(), other.getLastToken()).iterator();
1✔
397
        int lastKind = 0;
1✔
398
        while (thisIt.hasNext()) {
1✔
399
            if (!thatIt.hasNext()) {
1✔
400
                return false;
1✔
401
            }
402
            JavaccToken o1 = thisIt.next();
1✔
403
            JavaccToken o2 = thatIt.next();
1✔
404
            if (o1.kind != o2.kind) {
1✔
405
                return false;
1✔
406
            }
407

408
            String mappedImage = o1.getImage();
1✔
409
            if (varRenamer != null
1!
410
                && o1.kind == JavaTokenKinds.IDENTIFIER
411
                && lastKind != JavaTokenKinds.DOT
412
                && lastKind != JavaTokenKinds.METHOD_REF
413
                //method name
414
                && o1.getNext() != null && o1.getNext().kind != JavaTokenKinds.LPAREN) {
1!
415
                mappedImage = varRenamer.apply(mappedImage);
1✔
416
            }
417

418
            if (!o2.getImage().equals(mappedImage)) {
1✔
419
                return false;
1✔
420
            }
421

422
            lastKind = o1.kind;
1✔
423
        }
1✔
424
        return !thatIt.hasNext();
1✔
425
    }
426

427
    public static boolean isNullLiteral(ASTExpression node) {
428
        return node instanceof ASTNullLiteral;
1✔
429
    }
430

431
    /** Returns true if the node is a boolean literal with any value. */
432
    public static boolean isBooleanLiteral(JavaNode e) {
433
        return e instanceof ASTBooleanLiteral;
1✔
434
    }
435

436
    /** Returns true if the node is a boolean literal with the given constant value. */
437
    public static boolean isBooleanLiteral(JavaNode e, boolean value) {
438
        return e instanceof ASTBooleanLiteral && ((ASTBooleanLiteral) e).isTrue() == value;
1✔
439
    }
440

441
    public static boolean isBooleanNegation(JavaNode e) {
442
        return e instanceof ASTUnaryExpression && ((ASTUnaryExpression) e).getOperator() == UnaryOp.NEGATION;
1✔
443
    }
444

445
    /**
446
     * If the argument is a unary expression, returns its operand, otherwise
447
     * returns null.
448
     */
449
    public static @Nullable ASTExpression unaryOperand(@Nullable ASTExpression e) {
450
        return e instanceof ASTUnaryExpression ? ((ASTUnaryExpression) e).getOperand()
1!
451
                                               : null;
×
452
    }
453

454
    /**
455
     * Whether the expression is an access to a field of this instance,
456
     * not inherited, qualified or not ({@code this.field} or just {@code field}).
457
     */
458
    public static boolean isThisFieldAccess(ASTExpression e) {
459
        if (!(e instanceof ASTNamedReferenceExpr)) {
1!
460
            return false;
×
461
        }
462
        JVariableSymbol sym = ((ASTNamedReferenceExpr) e).getReferencedSym();
1✔
463
        return sym instanceof JFieldSymbol
1!
464
                && !((JFieldSymbol) sym).isStatic()
1✔
465
                // not inherited
466
                && ((JFieldSymbol) sym).getEnclosingClass().equals(e.getEnclosingType().getSymbol())
1✔
467
                // correct syntactic form
468
                && (e instanceof ASTVariableAccess || isSyntacticThisFieldAccess(e));
1✔
469
    }
470

471
    /**
472
     * Whether the expression is a {@code this.field}, with no outer
473
     * instance qualifier ({@code Outer.this.field}). The field symbol
474
     * is not checked to resolve to a field declared in this class (it
475
     * may be inherited)
476
     */
477
    public static boolean isSyntacticThisFieldAccess(ASTExpression e) {
478
        if (e instanceof ASTFieldAccess) {
1✔
479
            ASTExpression qualifier = ((ASTFieldAccess) e).getQualifier();
1✔
480
            if (qualifier instanceof ASTThisExpression) {
1✔
481
                // unqualified this
482
                return ((ASTThisExpression) qualifier).getQualifier() == null;
1!
483
            }
484
        }
485
        return false;
1✔
486
    }
487

488
    public static boolean hasAnyAnnotation(Annotatable node, Collection<String> qualifiedNames) {
489
        return node != null && qualifiedNames.stream().anyMatch(node::isAnnotationPresent);
1!
490
    }
491

492
    /**
493
     * Returns true if the expression is the default field value for
494
     * the given type.
495
     */
496
    public static boolean isDefaultValue(JTypeMirror type, ASTExpression expr) {
497
        if (type.isPrimitive()) {
1✔
498
            if (type.isPrimitive(PrimitiveTypeKind.BOOLEAN)) {
1✔
499
                return expr instanceof ASTBooleanLiteral && !((ASTBooleanLiteral) expr).isTrue();
1✔
500
            } else {
501
                Object constValue = expr.getConstValue();
1✔
502
                return constValue instanceof Number && ((Number) constValue).doubleValue() == 0d
1✔
503
                    || constValue instanceof Character && constValue.equals('\u0000');
1✔
504
            }
505
        } else {
506
            return expr instanceof ASTNullLiteral;
1✔
507
        }
508
    }
509

510
    /**
511
     * Returns true if the expression is a {@link ASTNamedReferenceExpr}
512
     * that references the symbol.
513
     */
514
    public static boolean isReferenceToVar(@Nullable ASTExpression expression, @NonNull JVariableSymbol symbol) {
515
        return expression instanceof ASTNamedReferenceExpr
1✔
516
            && symbol.equals(((ASTNamedReferenceExpr) expression).getReferencedSym());
1✔
517
    }
518

519
    public static boolean isUnqualifiedThis(ASTExpression e) {
520
        return e instanceof ASTThisExpression && ((ASTThisExpression) e).getQualifier() == null;
1✔
521
    }
522

523
    public static boolean isUnqualifiedSuper(ASTExpression e) {
524
        return e instanceof ASTSuperExpression && ((ASTSuperExpression) e).getQualifier() == null;
1✔
525
    }
526

527
    public static boolean isUnqualifiedThisOrSuper(ASTExpression e) {
528
        return isUnqualifiedSuper(e) || isUnqualifiedThis(e);
1✔
529
    }
530

531
    /**
532
     * Returns true if the expression is a {@link ASTNamedReferenceExpr}
533
     * that references any of the symbol in the set.
534
     */
535
    public static boolean isReferenceToVar(@Nullable ASTExpression expression, @NonNull Set<? extends JVariableSymbol> symbols) {
536
        return expression instanceof ASTNamedReferenceExpr
1!
537
            && symbols.contains(((ASTNamedReferenceExpr) expression).getReferencedSym());
1✔
538
    }
539

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

557
            if (e1.getClass() != e2.getClass()) {
1✔
558
                // unify `this.f` and `f`
559
                // note, we already know that the symbol is the same so there's no scoping problem
560
                return isSyntacticThisFieldAccess(e1) || isSyntacticThisFieldAccess(e2);
1!
561
            } else if (e1 instanceof ASTFieldAccess && e2 instanceof ASTFieldAccess) {
1!
562
                return isReferenceToSameVar(((ASTFieldAccess) e1).getQualifier(),
1✔
563
                                            ((ASTFieldAccess) e2).getQualifier());
1✔
564
            }
565
            return e1 instanceof ASTVariableAccess && e2 instanceof ASTVariableAccess;
1!
566
        } else if (e1 instanceof ASTThisExpression || e2 instanceof ASTThisExpression) {
1✔
567
            return e1.getClass() == e2.getClass();
1!
568
        }
569
        return false;
1✔
570
    }
571

572
    private static OptionalBool referenceSameSymbol(ASTNamedReferenceExpr e1, ASTNamedReferenceExpr e2) {
573
        if (!e1.getName().equals(e2.getName())) {
1✔
574
            return OptionalBool.NO;
1✔
575
        }
576
        JVariableSymbol ref1 = e1.getReferencedSym();
1✔
577
        JVariableSymbol ref2 = e2.getReferencedSym();
1✔
578
        if (ref1 == null || ref2 == null) {
1!
579
            return OptionalBool.UNKNOWN;
1✔
580
        }
581
        return OptionalBool.definitely(ref1.equals(ref2));
1✔
582
    }
583

584
    /**
585
     * Returns true if the expression is a reference to a local variable.
586
     */
587
    public static boolean isReferenceToLocal(ASTExpression expr) {
588
        return expr instanceof ASTVariableAccess
1✔
589
                && ((ASTVariableAccess) expr).getReferencedSym() instanceof AstLocalVarSym;
1✔
590
    }
591

592
    /**
593
     * Returns true if the expression has the form `field`, or `this.field`,
594
     * where `field` is a field declared in the enclosing class. Considers
595
     * inherited fields. Assumes we're not in a static context.
596
     */
597
    public static boolean isRefToFieldOfThisInstance(ASTExpression usage) {
598
        if (!(usage instanceof ASTNamedReferenceExpr)) {
1!
599
            return false;
×
600
        }
601
        JVariableSymbol symbol = ((ASTNamedReferenceExpr) usage).getReferencedSym();
1✔
602
        if (!(symbol instanceof JFieldSymbol)) {
1✔
603
            return false;
1✔
604
        }
605

606
        if (usage instanceof ASTVariableAccess) {
1✔
607
            return !Modifier.isStatic(((JFieldSymbol) symbol).getModifiers());
1✔
608
        } else if (usage instanceof ASTFieldAccess) {
1!
609
            return isUnqualifiedThisOrSuper(((ASTFieldAccess) usage).getQualifier());
1✔
610
        }
611
        return false;
×
612
    }
613

614
    /**
615
     * Returns true if the expression is a reference to a field declared
616
     * in this class (not a superclass), on any instance (not just `this`).
617
     */
618
    public static boolean isRefToFieldOfThisClass(ASTExpression usage) {
619
        if (!(usage instanceof ASTNamedReferenceExpr)) {
1!
620
            return false;
×
621
        }
622
        JVariableSymbol symbol = ((ASTNamedReferenceExpr) usage).getReferencedSym();
1✔
623
        if (!(symbol instanceof JFieldSymbol)) {
1✔
624
            return false;
1✔
625
        }
626

627
        if (usage instanceof ASTVariableAccess) {
1✔
628
            return !Modifier.isStatic(((JFieldSymbol) symbol).getModifiers());
1✔
629
        } else if (usage instanceof ASTFieldAccess) {
1!
630
            if (usage.getEnclosingType() != null) {
1!
631
                return Objects.equals(((JFieldSymbol) symbol).getEnclosingClass(),
1✔
632
                        usage.getEnclosingType().getSymbol());
1✔
633
            }
634
        }
635
        return false;
×
636
    }
637

638
    /**
639
     * Return whether the method call is a call whose receiver is the
640
     * {@code this} object. This is the case also if the method is called
641
     * with a {@code super} qualifier, or if it is not syntactically
642
     * qualified but refers to a non-static method of the enclosing class.
643
     */
644
    public static OptionalBool isCallOnThisInstance(ASTMethodCall call) {
645
        // syntactic approach.
646
        if (call.getQualifier() != null) {
1✔
647
            return OptionalBool.definitely(isUnqualifiedThisOrSuper(call.getQualifier()));
1✔
648
        }
649

650
        // unqualified call
651
        JMethodSig mtype = call.getMethodType();
1✔
652
        JExecutableSymbol methodSym = mtype.getSymbol();
1✔
653
        if (methodSym.isUnresolved()) {
1✔
654
            return OptionalBool.UNKNOWN;
1✔
655
        }
656
        return OptionalBool.definitely(
1✔
657
            !methodSym.isStatic()
1✔
658
                && methodSym.getEnclosingClass().equals(call.getEnclosingType().getSymbol())
1✔
659
        );
660
    }
661

662
    public static ASTClassType getThisOrSuperQualifier(ASTExpression expr) {
663
        if (expr instanceof ASTThisExpression) {
×
664
            return ((ASTThisExpression) expr).getQualifier();
×
665
        } else if (expr instanceof ASTSuperExpression) {
×
666
            return ((ASTSuperExpression) expr).getQualifier();
×
667
        }
668
        return null;
×
669
    }
670

671
    public static boolean isThisOrSuper(ASTExpression expr) {
672
        return expr instanceof ASTThisExpression || expr instanceof ASTSuperExpression;
1✔
673
    }
674

675
    /**
676
     * Return a node stream containing all the operands of an addition expression.
677
     * For instance, {@code a+b+c} will be parsed as a tree with two levels.
678
     * This method will return a flat node stream containing {@code a, b, c}.
679
     *
680
     * @param e An expression, if it is not a string concatenation expression,
681
     *          then returns an empty node stream.
682
     */
683
    public static NodeStream<ASTExpression> flattenOperands(ASTExpression e) {
684
        List<ASTExpression> result = new ArrayList<>();
1✔
685
        flattenOperandsRec(e, result);
1✔
686
        return NodeStream.fromIterable(result);
1✔
687
    }
688

689
    private static void flattenOperandsRec(ASTExpression e, List<ASTExpression> result) {
690
        if (isStringConcatExpr(e)) {
1✔
691
            ASTInfixExpression infix = (ASTInfixExpression) e;
1✔
692
            flattenOperandsRec(infix.getLeftOperand(), result);
1✔
693
            flattenOperandsRec(infix.getRightOperand(), result);
1✔
694
        } else {
1✔
695
            result.add(e);
1✔
696
        }
697
    }
1✔
698

699
    /**
700
     * Returns true if the node is the last child of its parent. Returns
701
     * false if this is the root node.
702
     */
703
    public static boolean isLastChild(Node it) {
704
        Node parent = it.getParent();
1✔
705
        return parent != null && it.getIndexInParent() == parent.getNumChildren() - 1;
1!
706
    }
707

708
    /**
709
     * Returns a node stream of enclosing expressions in the same call chain.
710
     * For instance in {@code a.b().c().d()}, called on {@code a}, this will
711
     * yield {@code a.b()}, and {@code a.b().c()}.
712
     */
713
    @SuppressWarnings({"unchecked", "rawtypes"})
714
    public static NodeStream<QualifiableExpression> followingCallChain(ASTExpression expr) {
715
        return (NodeStream) expr.ancestors().takeWhile(it -> it instanceof QualifiableExpression);
1✔
716
    }
717

718
    public static ASTExpression peelCasts(@Nullable ASTExpression expr) {
719
        while (expr instanceof ASTCastExpression) {
1✔
720
            expr = ((ASTCastExpression) expr).getOperand();
1✔
721
        }
722
        return expr;
1✔
723
    }
724

725
    public static boolean isArrayInitializer(ASTExpression expr) {
726
        return expr instanceof ASTArrayAllocation && ((ASTArrayAllocation) expr).getArrayInitializer() != null;
1!
727
    }
728

729
    public static boolean isCloneMethod(ASTMethodDeclaration node) {
730
        // this is enough as in valid code, this signature overrides Object#clone
731
        // and the other things like visibility are checked by the compiler
732
        return "clone".equals(node.getName())
1✔
733
            && node.getArity() == 0
1✔
734
            && !node.isStatic();
1!
735
    }
736

737
    public static boolean isEqualsMethod(ASTMethodDeclaration node) {
738
        return "equals".equals(node.getName())
1✔
739
            && node.getArity() == 1
1✔
740
            && node.getFormalParameters().get(0).getTypeMirror().isTop()
1✔
741
            && !node.isStatic();
1!
742
    }
743

744
    public static boolean isHashCodeMethod(ASTMethodDeclaration node) {
745
        return "hashCode".equals(node.getName())
1✔
746
            && node.getArity() == 0
1✔
747
            && !node.isStatic();
1!
748
    }
749

750
    public static boolean isCompareToMethod(ASTMethodDeclaration method) {
751
        return "compareTo".equals(method.getName())
1✔
752
                && method.getArity() == 1
1!
753
                && method.getResultTypeNode().getTypeMirror().isPrimitive(JPrimitiveType.PrimitiveTypeKind.INT)
1!
754
                && !method.isStatic();
1!
755
    }
756

757
    public static boolean isArrayLengthFieldAccess(ASTExpression node) {
758
        if (node instanceof ASTFieldAccess) {
1✔
759
            ASTFieldAccess field = (ASTFieldAccess) node;
1✔
760
            return "length".equals(field.getName())
1✔
761
                && field.getQualifier().getTypeMirror().isArray();
1!
762
        }
763
        return false;
1✔
764
    }
765

766
    /**
767
     * @see ASTBreakStatement#getTarget()
768
     */
769
    public static boolean mayBeBreakTarget(JavaNode it) {
770
        return it instanceof ASTLoopStatement
1!
771
            || it instanceof ASTSwitchStatement
772
            || it instanceof ASTLabeledStatement;
773
    }
774

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

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

797

798
    /**
799
     * Tests if the node is an {@link ASTAssignmentExpression} with the given operator.
800
     */
801
    public static boolean isAssignmentExprWithOperator(@Nullable JavaNode e, AssignmentOp operator) {
802
        if (e instanceof ASTAssignmentExpression) {
1!
803
            ASTAssignmentExpression infix = (ASTAssignmentExpression) e;
1✔
804
            return operator == infix.getOperator();
1!
805
        }
806
        return false;
×
807
    }
808

809
    /**
810
     * Returns true if the given token is a Java comment.
811
     */
812
    public static boolean isComment(JavaccToken t) {
813
        switch (t.kind) {
1✔
814
        case FORMAL_COMMENT:
815
        case MULTI_LINE_COMMENT:
816
        case SINGLE_LINE_COMMENT:
817
            return true;
1✔
818
        default:
819
            return false;
1✔
820
        }
821
    }
822

823
    public static boolean isMarkdownComment(JavaccToken token) {
824
        Chars text = token.getText();
1✔
825
        return token.kind == JavaTokenKinds.SINGLE_LINE_COMMENT && text.length() > 2 && text.charAt(2) == '/';
1✔
826
    }
827

828
    /**
829
     * Return true if the catch clause just rethrows the caught exception
830
     * immediately.
831
     */
832
    public static boolean isJustRethrowException(ASTCatchClause clause) {
833
        ASTBlock body = clause.getBody();
1✔
834
        if (body.size() == 1) {
1✔
835
            ASTStatement stmt = body.get(0);
1✔
836
            return stmt instanceof ASTThrowStatement
1✔
837
                && isReferenceToVar(((ASTThrowStatement) stmt).getExpr(),
1✔
838
                                    clause.getParameter().getVarId().getSymbol());
1✔
839
        }
840
        return false;
1✔
841
    }
842

843
    /**
844
     * Return true if the node is in static context relative to the deepest enclosing class.
845
     */
846
    public static boolean isInStaticCtx(JavaNode node) {
847
        return node.ancestorsOrSelf()
1✔
848
                   .map(NodeStream.asInstanceOf(ASTBodyDeclaration.class, ASTExplicitConstructorInvocation.class))
1✔
849
                   .take(1)
1✔
850
                   .any(it -> it instanceof ASTExecutableDeclaration && ((ASTExecutableDeclaration) it).isStatic()
1✔
851
                       || it instanceof ASTFieldDeclaration && ((ASTFieldDeclaration) it).isStatic()
1!
852
                       || it instanceof ASTInitializer && ((ASTInitializer) it).isStatic()
1!
853
                       || it instanceof ASTEnumConstant
854
                       || it instanceof ASTExplicitConstructorInvocation
855
                   );
856
    }
857

858
    /**
859
     * Return the target of the return.
860
     */
861
    public static @Nullable ReturnScopeNode getReturnTarget(ASTReturnStatement stmt) {
862
        return stmt.ancestors().first(ReturnScopeNode.class);
1✔
863
    }
864

865

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

894
    public static boolean isUnconditionalLoop(ASTLoopStatement loop) {
895
        return !(loop instanceof ASTForeachStatement)
1✔
896
                && (loop.getCondition() == null || isBooleanLiteral(loop.getCondition(), true));
1!
897
    }
898

899
    /**
900
     * Return true if this switch is total with respect to the scrutinee
901
     * type. This means, one of the branches will always be taken, and the
902
     * switch will never "not match". A switch with a default case is always
903
     * total. A switch expression is checked by the compiler for exhaustivity,
904
     * and we assume it is correct.
905
     */
906
    public static boolean isTotalSwitch(ASTSwitchLike switchLike) {
907
        if (switchLike instanceof ASTSwitchExpression || switchLike.hasDefaultCase()) {
1✔
908
            return true;
1✔
909
        }
910
        return switchLike.isExhaustive();
1✔
911
    }
912

913
}
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