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

pmd / pmd / 19

29 May 2025 04:22PM UTC coverage: 77.723% (-0.03%) from 77.757%
19

push

github

adangel
Fix #5621: [java] Fix FPs with UnusedPrivateMethod (#5727)

Merge pull request #5727 from oowekyala:issue5621-unusedprivatemethod

17705 of 23734 branches covered (74.6%)

Branch coverage included in aggregate %.

50 of 52 new or added lines in 9 files covered. (96.15%)

81 existing lines in 6 files now uncovered.

38845 of 49024 relevant lines covered (79.24%)

0.8 hits per line

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

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

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

109
    private JavaAstUtils() {
110
        // utility class
111
    }
112

113

114
    public static boolean isConditional(JavaNode ifx) {
115
        return isInfixExprWithOperator(ifx, BinaryOp.CONDITIONAL_OPS);
1✔
116
    }
117

118
    public static int numAlternatives(ASTSwitchBranch n) {
119
        return n.isDefault() ? 1 : n.getLabel().getExprList().count();
1✔
120
    }
121

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

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

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

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

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

168
    public static boolean hasField(ASTTypeDeclaration node, String name) {
169
        if (node == null) {
1!
UNCOV
170
            return false;
×
171
        }
172

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

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

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

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

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

254
    private static boolean isCompoundAssignment(ASTNamedReferenceExpr expr) {
255
        return expr.getParent() instanceof ASTAssignmentExpression && ((ASTAssignmentExpression) expr.getParent()).isCompound();
1!
256
    }
257

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

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

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

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

291
    public static boolean isAnonymousClassCreation(@Nullable ASTExpression expression) {
292
        return expression instanceof ASTConstructorCall
1✔
293
                && ((ASTConstructorCall) expression).isAnonymousClass();
1✔
294
    }
295

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

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

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

347
    private static boolean areEqual(ASTExpression e1, ASTExpression e2) {
348
        return tokenEquals(e1, e2);
1✔
349
    }
350

351
    /**
352
     * Returns true if both nodes have exactly the same tokens.
353
     *
354
     * @param node First node
355
     * @param that Other node
356
     */
357
    public static boolean tokenEquals(JavaNode node, JavaNode that) {
358
        return tokenEquals(node, that, null);
1✔
359
    }
360

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

379
        Iterator<JavaccToken> thisIt = GenericToken.range(node.getFirstToken(), node.getLastToken()).iterator();
1✔
380
        Iterator<JavaccToken> thatIt = GenericToken.range(other.getFirstToken(), other.getLastToken()).iterator();
1✔
381
        int lastKind = 0;
1✔
382
        while (thisIt.hasNext()) {
1✔
383
            if (!thatIt.hasNext()) {
1!
UNCOV
384
                return false;
×
385
            }
386
            JavaccToken o1 = thisIt.next();
1✔
387
            JavaccToken o2 = thatIt.next();
1✔
388
            if (o1.kind != o2.kind) {
1✔
389
                return false;
1✔
390
            }
391

392
            String mappedImage = o1.getImage();
1✔
393
            if (varRenamer != null
1!
394
                && o1.kind == JavaTokenKinds.IDENTIFIER
395
                && lastKind != JavaTokenKinds.DOT
396
                && lastKind != JavaTokenKinds.METHOD_REF
397
                //method name
398
                && o1.getNext() != null && o1.getNext().kind != JavaTokenKinds.LPAREN) {
1!
399
                mappedImage = varRenamer.apply(mappedImage);
1✔
400
            }
401

402
            if (!o2.getImage().equals(mappedImage)) {
1✔
403
                return false;
1✔
404
            }
405

406
            lastKind = o1.kind;
1✔
407
        }
1✔
408
        return !thatIt.hasNext();
1!
409
    }
410

411
    public static boolean isNullLiteral(ASTExpression node) {
412
        return node instanceof ASTNullLiteral;
1✔
413
    }
414

415
    /** Returns true if the node is a boolean literal with any value. */
416
    public static boolean isBooleanLiteral(JavaNode e) {
417
        return e instanceof ASTBooleanLiteral;
1✔
418
    }
419

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

425
    public static boolean isBooleanNegation(JavaNode e) {
426
        return e instanceof ASTUnaryExpression && ((ASTUnaryExpression) e).getOperator() == UnaryOp.NEGATION;
1✔
427
    }
428

429
    /**
430
     * If the argument is a unary expression, returns its operand, otherwise
431
     * returns null.
432
     */
433
    public static @Nullable ASTExpression unaryOperand(@Nullable ASTExpression e) {
434
        return e instanceof ASTUnaryExpression ? ((ASTUnaryExpression) e).getOperand()
1!
UNCOV
435
                                               : null;
×
436
    }
437

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

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

472
    public static boolean hasAnyAnnotation(Annotatable node, Collection<String> qualifiedNames) {
473
        return node != null && qualifiedNames.stream().anyMatch(node::isAnnotationPresent);
1!
474
    }
475

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

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

503
    public static boolean isUnqualifiedThis(ASTExpression e) {
504
        return e instanceof ASTThisExpression && ((ASTThisExpression) e).getQualifier() == null;
1✔
505
    }
506

507
    public static boolean isUnqualifiedSuper(ASTExpression e) {
508
        return e instanceof ASTSuperExpression && ((ASTSuperExpression) e).getQualifier() == null;
1✔
509
    }
510

511
    public static boolean isUnqualifiedThisOrSuper(ASTExpression e) {
512
        return isUnqualifiedSuper(e) || isUnqualifiedThis(e);
1✔
513
    }
514

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

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

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

556
    private static OptionalBool referenceSameSymbol(ASTNamedReferenceExpr e1, ASTNamedReferenceExpr e2) {
557
        if (!e1.getName().equals(e2.getName())) {
1✔
558
            return OptionalBool.NO;
1✔
559
        }
560
        JVariableSymbol ref1 = e1.getReferencedSym();
1✔
561
        JVariableSymbol ref2 = e2.getReferencedSym();
1✔
562
        if (ref1 == null || ref2 == null) {
1!
563
            return OptionalBool.UNKNOWN;
1✔
564
        }
565
        return OptionalBool.definitely(ref1.equals(ref2));
1✔
566
    }
567

568
    /**
569
     * Returns true if the expression is a reference to a local variable.
570
     */
571
    public static boolean isReferenceToLocal(ASTExpression expr) {
572
        return expr instanceof ASTVariableAccess
1✔
573
                && ((ASTVariableAccess) expr).getReferencedSym() instanceof AstLocalVarSym;
1✔
574
    }
575

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

590
        if (usage instanceof ASTVariableAccess) {
1✔
591
            return !Modifier.isStatic(((JFieldSymbol) symbol).getModifiers());
1✔
592
        } else if (usage instanceof ASTFieldAccess) {
1!
593
            return isUnqualifiedThisOrSuper(((ASTFieldAccess) usage).getQualifier());
1✔
594
        }
UNCOV
595
        return false;
×
596
    }
597

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

611
        if (usage instanceof ASTVariableAccess) {
1✔
612
            return !Modifier.isStatic(((JFieldSymbol) symbol).getModifiers());
1✔
613
        } else if (usage instanceof ASTFieldAccess) {
1!
614
            if (usage.getEnclosingType() != null) {
1!
615
                return Objects.equals(((JFieldSymbol) symbol).getEnclosingClass(),
1✔
616
                        usage.getEnclosingType().getSymbol());
1✔
617
            }
618
        }
UNCOV
619
        return false;
×
620
    }
621

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

634
        // unqualified call
635
        JMethodSig mtype = call.getMethodType();
1✔
636
        JExecutableSymbol methodSym = mtype.getSymbol();
1✔
637
        if (methodSym.isUnresolved()) {
1✔
638
            return OptionalBool.UNKNOWN;
1✔
639
        }
640
        return OptionalBool.definitely(
1✔
641
            !methodSym.isStatic()
1✔
642
                && methodSym.getEnclosingClass().equals(call.getEnclosingType().getSymbol())
1✔
643
        );
644
    }
645

646
    public static ASTClassType getThisOrSuperQualifier(ASTExpression expr) {
647
        if (expr instanceof ASTThisExpression) {
×
648
            return ((ASTThisExpression) expr).getQualifier();
×
649
        } else if (expr instanceof ASTSuperExpression) {
×
UNCOV
650
            return ((ASTSuperExpression) expr).getQualifier();
×
651
        }
UNCOV
652
        return null;
×
653
    }
654

655
    public static boolean isThisOrSuper(ASTExpression expr) {
656
        return expr instanceof ASTThisExpression || expr instanceof ASTSuperExpression;
1✔
657
    }
658

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

673
    private static void flattenOperandsRec(ASTExpression e, List<ASTExpression> result) {
674
        if (isStringConcatExpr(e)) {
1✔
675
            ASTInfixExpression infix = (ASTInfixExpression) e;
1✔
676
            flattenOperandsRec(infix.getLeftOperand(), result);
1✔
677
            flattenOperandsRec(infix.getRightOperand(), result);
1✔
678
        } else {
1✔
679
            result.add(e);
1✔
680
        }
681
    }
1✔
682

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

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

702
    public static ASTExpression peelCasts(@Nullable ASTExpression expr) {
703
        while (expr instanceof ASTCastExpression) {
1✔
704
            expr = ((ASTCastExpression) expr).getOperand();
1✔
705
        }
706
        return expr;
1✔
707
    }
708

709
    public static boolean isArrayInitializer(ASTExpression expr) {
710
        return expr instanceof ASTArrayAllocation && ((ASTArrayAllocation) expr).getArrayInitializer() != null;
1!
711
    }
712

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

721
    public static boolean isEqualsMethod(ASTMethodDeclaration node) {
722
        return "equals".equals(node.getName())
1✔
723
            && node.getArity() == 1
1✔
724
            && node.getFormalParameters().get(0).getTypeMirror().isTop()
1✔
725
            && !node.isStatic();
1!
726
    }
727

728
    public static boolean isHashCodeMethod(ASTMethodDeclaration node) {
729
        return "hashCode".equals(node.getName())
1✔
730
            && node.getArity() == 0
1✔
731
            && !node.isStatic();
1!
732
    }
733

734
    public static boolean isArrayLengthFieldAccess(ASTExpression node) {
735
        if (node instanceof ASTFieldAccess) {
1✔
736
            ASTFieldAccess field = (ASTFieldAccess) node;
1✔
737
            return "length".equals(field.getName())
1✔
738
                && field.getQualifier().getTypeMirror().isArray();
1!
739
        }
740
        return false;
1✔
741
    }
742

743
    /**
744
     * @see ASTBreakStatement#getTarget()
745
     */
746
    public static boolean mayBeBreakTarget(JavaNode it) {
747
        return it instanceof ASTLoopStatement
1!
748
            || it instanceof ASTSwitchStatement
749
            || it instanceof ASTLabeledStatement;
750
    }
751

752
    /**
753
     * Tests if the node is an {@link ASTInfixExpression} with one of the given operators.
754
     */
755
    public static boolean isInfixExprWithOperator(@Nullable JavaNode e, Set<BinaryOp> operators) {
756
        if (e instanceof ASTInfixExpression) {
1✔
757
            ASTInfixExpression infix = (ASTInfixExpression) e;
1✔
758
            return operators.contains(infix.getOperator());
1✔
759
        }
760
        return false;
1✔
761
    }
762

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

774

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

786
    /**
787
     * Returns true if the given token is a Java comment.
788
     */
789
    public static boolean isComment(JavaccToken t) {
790
        switch (t.kind) {
1✔
791
        case FORMAL_COMMENT:
792
        case MULTI_LINE_COMMENT:
793
        case SINGLE_LINE_COMMENT:
794
            return true;
1✔
795
        default:
796
            return false;
1✔
797
        }
798
    }
799

800
    public static boolean isMarkdownComment(JavaccToken token) {
801
        return token.kind == JavaTokenKinds.SINGLE_LINE_COMMENT && token.getText().charAt(2) == '/';
1✔
802
    }
803

804
    /**
805
     * Return true if the catch clause just rethrows the caught exception
806
     * immediately.
807
     */
808
    public static boolean isJustRethrowException(ASTCatchClause clause) {
809
        ASTBlock body = clause.getBody();
1✔
810
        if (body.size() == 1) {
1✔
811
            ASTStatement stmt = body.get(0);
1✔
812
            return stmt instanceof ASTThrowStatement
1!
813
                && isReferenceToVar(((ASTThrowStatement) stmt).getExpr(),
1!
814
                                    clause.getParameter().getVarId().getSymbol());
1✔
815
        }
816
        return false;
1✔
817
    }
818

819
    /**
820
     * Return true if the node is in static context relative to the deepest enclosing class.
821
     */
822
    public static boolean isInStaticCtx(JavaNode node) {
823
        return node.ancestorsOrSelf()
1✔
824
                   .map(NodeStream.asInstanceOf(ASTBodyDeclaration.class, ASTExplicitConstructorInvocation.class))
1✔
825
                   .take(1)
1✔
826
                   .any(it -> it instanceof ASTExecutableDeclaration && ((ASTExecutableDeclaration) it).isStatic()
1✔
827
                       || it instanceof ASTFieldDeclaration && ((ASTFieldDeclaration) it).isStatic()
1!
828
                       || it instanceof ASTInitializer && ((ASTInitializer) it).isStatic()
1!
829
                       || it instanceof ASTEnumConstant
830
                       || it instanceof ASTExplicitConstructorInvocation
831
                   );
832
    }
833

834
    /**
835
     * Return the target of the return.
836
     */
837
    public static @Nullable ReturnScopeNode getReturnTarget(ASTReturnStatement stmt) {
838
        return stmt.ancestors().first(ReturnScopeNode.class);
1✔
839
    }
840

841

842
    /**
843
     * Return true if the variable is effectively final. This means
844
     * the variable is never reassigned.
845
     */
846
    public static boolean isEffectivelyFinal(ASTVariableId var) {
847
        if (var.getInitializer() == null && var.isLocalVariable()) {
1✔
848
            // blank variables may be assigned on several paths
849
            DataflowResult dataflow = DataflowPass.getDataflowResult(var.getRoot());
1✔
850
            for (ASTNamedReferenceExpr usage : var.getLocalUsages()) {
1✔
851
                if (usage.getAccessType() == AccessType.WRITE) {
1✔
852
                    ReachingDefinitionSet reaching = dataflow.getReachingDefinitions(usage);
1✔
853
                    if (reaching.isNotFullyKnown() || !reaching.getReaching().isEmpty()) {
1!
854
                        // If the reaching def is not empty, then that means
855
                        // the assignment is killing another one, ie it is a reassignment.
856
                        return false;
1✔
857
                    }
858
                }
859
            }
1✔
860
            return true;
1✔
861
        }
862
        for (ASTNamedReferenceExpr usage : var.getLocalUsages()) {
1✔
863
            if (usage.getAccessType() == AccessType.WRITE) {
1✔
864
                return false;
1✔
865
            }
866
        }
1✔
867
        return true;
1✔
868
    }
869

870
    public static boolean isUnconditionalLoop(ASTLoopStatement loop) {
871
        return !(loop instanceof ASTForeachStatement)
1✔
872
                && (loop.getCondition() == null || isBooleanLiteral(loop.getCondition(), true));
1!
873
    }
874

875
    /**
876
     * Return true if this switch is total with respect to the scrutinee
877
     * type. This means, one of the branches will always be taken, and the
878
     * switch will never "not match". A switch with a default case is always
879
     * total. A switch expression is checked by the compiler for exhaustivity,
880
     * and we assume it is correct.
881
     */
882
    public static boolean isTotalSwitch(ASTSwitchLike switchLike) {
883
        if (switchLike instanceof ASTSwitchExpression || switchLike.hasDefaultCase()) {
1✔
884
            return true;
1✔
885
        }
886
        return switchLike.isExhaustive();
1✔
887
    }
888
}
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