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

pmd / pmd / 4478

28 Feb 2025 09:39AM UTC coverage: 77.731% (+0.01%) from 77.717%
4478

push

github

adangel
[doc] Update contributors for 7.11.0 (#5551)

Merge pull request #5551 from adangel:doc-update-contributors-7.11.0

17473 of 23424 branches covered (74.59%)

Branch coverage included in aggregate %.

38247 of 48259 relevant lines covered (79.25%)

0.8 hits per line

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

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

7

8
import org.apache.commons.lang3.tuple.Pair;
9
import org.checkerframework.checker.nullness.qual.NonNull;
10
import org.checkerframework.checker.nullness.qual.Nullable;
11

12
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
13
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
14
import net.sourceforge.pmd.lang.java.types.JPrimitiveType;
15
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
16
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
17
import net.sourceforge.pmd.util.AssertionUtil;
18

19
/**
20
 * Computes constant expression values.
21
 */
22
// strictfp because constant expressions are FP-strict (not sure if this is really important)
23
final strictfp class ConstantFolder extends JavaVisitorBase<Void, Object> {
24

25
    static final ConstantFolder INSTANCE = new ConstantFolder();
1✔
26
    private static final Pair<Object, Object> FAILED_BIN_PROMOTION = Pair.of(null, null);
1✔
27

28
    private ConstantFolder() {
29

30
    }
31

32
    @Override
33
    public Object visitJavaNode(JavaNode node, Void data) {
34
        return null;
1✔
35
    }
36

37
    @Override
38
    public @NonNull Number visitLiteral(ASTLiteral num, Void data) {
39
        throw new AssertionError("Literal nodes implement getConstValue directly");
×
40
    }
41

42
    @Override
43
    public Object visit(ASTVariableAccess node, Void data) {
44
        JVariableSymbol symbol = node.getReferencedSym();
1✔
45
        if (symbol == null || !symbol.isFinal()) {
1✔
46
            return null;
1✔
47
        }
48
        @Nullable
49
        ASTVariableId declaratorId = symbol.tryGetNode();
1✔
50
        if (declaratorId != null) {
1✔
51
            ASTExpression initializer = declaratorId.getInitializer();
1✔
52
            if (initializer != null) {
1✔
53
                return initializer.getConstValue();
1✔
54
            }
55
        }
56

57
        return null;
1✔
58
    }
59

60
    @Override
61
    public Object visit(ASTFieldAccess node, Void data) {
62
        JFieldSymbol symbol = node.getReferencedSym();
1✔
63
        if (symbol != null) {
1✔
64
            return symbol.getConstValue();
1✔
65
        }
66
        return null;
1✔
67
    }
68

69
    @Override
70
    public Object visit(ASTArrayInitializer node, Void data) {
71
        int length = node.length();
1✔
72
        Object[] result = new Object[length];
1✔
73
        int index = 0;
1✔
74
        for (ASTExpression expr : node) {
1✔
75
            if (!expr.isCompileTimeConstant()) {
1✔
76
                return null;
1✔
77
            }
78
            result[index++] = expr.getConstValue();
1✔
79
        }
1✔
80

81
        return result;
1✔
82
    }
83

84
    @Override
85
    public Object visit(ASTConditionalExpression node, Void data) {
86
        Object condition = node.getCondition().getConstValue();
1✔
87
        if (condition instanceof Boolean) {
1✔
88
            Object thenValue = node.getThenBranch().getConstValue();
1✔
89
            Object elseValue = node.getElseBranch().getConstValue();
1✔
90
            if (thenValue == null || elseValue == null) {
1✔
91
                return null; // not a constexpr
1✔
92
            }
93
            if ((Boolean) condition) {
1✔
94
                return thenValue;
1✔
95
            } else {
96
                return elseValue;
1✔
97
            }
98
        }
99
        return null;
1✔
100
    }
101

102
    @Override
103
    public Object visit(ASTCastExpression node, Void data) {
104
        JTypeMirror t = node.getCastType().getTypeMirror();
1✔
105
        if (t.isNumeric()) {
1✔
106
            return numericCoercion(node.getOperand().getConstValue(), t);
1✔
107
        } else if (TypeTestUtil.isExactlyA(String.class, node.getCastType())) {
1✔
108
            return stringCoercion(node.getOperand().getConstValue());
1✔
109
        }
110
        return null;
1✔
111
    }
112

113
    @Override
114
    public Object visit(ASTUnaryExpression node, Void data) {
115
        UnaryOp operator = node.getOperator();
1✔
116
        if (!operator.isPure()) {
1✔
117
            return null;
1✔
118
        }
119

120
        ASTExpression operand = node.getOperand();
1✔
121
        Object operandValue = operand.getConstValue();
1✔
122
        if (operandValue == null) {
1✔
123
            return null;
1✔
124
        }
125

126
        switch (operator) {
1!
127
        case UNARY_PLUS:
128
            return unaryPromotion(operandValue);
1✔
129
        case UNARY_MINUS: {
130
            Number promoted = unaryPromotion(operandValue);
1✔
131
            if (promoted == null) {
1✔
132
                return null; // compile-time error
1✔
133
            } else if (promoted instanceof Integer) {
1✔
134
                return -promoted.intValue();
1✔
135
            } else if (promoted instanceof Long) {
1✔
136
                return -promoted.longValue();
1✔
137
            } else if (promoted instanceof Float) {
1✔
138
                return -promoted.floatValue();
1✔
139
            } else {
140
                assert promoted instanceof Double;
1!
141
                return -promoted.doubleValue();
1✔
142
            }
143
        }
144
        case COMPLEMENT: {
145
            Number promoted = unaryPromotion(operandValue);
1✔
146
            if (promoted instanceof Integer) {
1✔
147
                return ~promoted.intValue();
1✔
148
            } else if (promoted instanceof Long) {
1✔
149
                return ~promoted.longValue();
1✔
150
            } else {
151
                return null; // compile-time error
1✔
152
            }
153
        }
154
        case NEGATION: {
155
            return booleanInvert(operandValue);
1✔
156
        }
157

158
        default: // increment ops
159
            throw new AssertionError("unreachable");
×
160
        }
161
    }
162

163
    @Override
164
    public strictfp Object visit(ASTInfixExpression node, Void data) {
165
        Object left = node.getLeftOperand().getConstValue();
1✔
166
        Object right = node.getRightOperand().getConstValue();
1✔
167
        if (left == null || right == null) {
1✔
168
            return null;
1✔
169
        }
170

171
        switch (node.getOperator()) {
1!
172
        case CONDITIONAL_OR: {
173
            if (left instanceof Boolean && right instanceof Boolean) {
1✔
174
                return (Boolean) left || (Boolean) right;
1✔
175
            }
176
            return null;
1✔
177
        }
178

179
        case CONDITIONAL_AND: {
180
            if (left instanceof Boolean && right instanceof Boolean) {
1✔
181
                return (Boolean) left && (Boolean) right;
1✔
182
            }
183
            return null;
1✔
184
        }
185

186
        case OR: {
187
            Pair<Object, Object> promoted = booleanAwareBinaryPromotion(left, right);
1✔
188
            left = promoted.getLeft();
1✔
189
            right = promoted.getRight();
1✔
190

191
            if (left instanceof Integer) {
1✔
192
                return intValue(left) | intValue(right);
1✔
193
            } else if (left instanceof Long) {
1✔
194
                return longValue(left) | longValue(right);
1✔
195
            } else if (left instanceof Boolean) {
1✔
196
                return booleanValue(left) | booleanValue(right);
1✔
197
            }
198
            return null;
1✔
199
        }
200
        case XOR: {
201
            Pair<Object, Object> promoted = booleanAwareBinaryPromotion(left, right);
1✔
202
            left = promoted.getLeft();
1✔
203
            right = promoted.getRight();
1✔
204

205
            if (left instanceof Integer) {
1✔
206
                return intValue(left) ^ intValue(right);
1✔
207
            } else if (left instanceof Long) {
1✔
208
                return longValue(left) ^ longValue(right);
1✔
209
            } else if (left instanceof Boolean) {
1✔
210
                return booleanValue(left) ^ booleanValue(right);
1✔
211
            }
212
            return null;
1✔
213
        }
214
        case AND: {
215
            Pair<Object, Object> promoted = booleanAwareBinaryPromotion(left, right);
1✔
216
            left = promoted.getLeft();
1✔
217
            right = promoted.getRight();
1✔
218

219
            if (left instanceof Integer) {
1✔
220
                return intValue(left) & intValue(right);
1✔
221
            } else if (left instanceof Long) {
1✔
222
                return longValue(left) & longValue(right);
1✔
223
            } else if (left instanceof Boolean) {
1✔
224
                return booleanValue(left) & booleanValue(right);
1✔
225
            }
226
            return null;
1✔
227
        }
228

229
        case EQ:
230
            return eqResult(left, right);
1✔
231
        case NE:
232
            return booleanInvert(eqResult(left, right));
1✔
233

234
        case LE:
235
            return compLE(left, right);
1✔
236
        case GT:
237
            return booleanInvert(compLE(left, right));
1✔
238
        case LT:
239
            return compLT(left, right);
1✔
240
        case GE:
241
            return booleanInvert(compLT(left, right));
1✔
242

243
        case INSTANCEOF:
244
            // disallowed, actually dead code because the
245
            // right operand is the type, which is no constexpr
246
            return null;
×
247

248
        // for shift operators, unary promotion is performed on operators separately
249
        case LEFT_SHIFT: {
250
            left = unaryPromotion(left);
1✔
251
            right = unaryPromotion(right);
1✔
252
            if (!(right instanceof Integer) && !(right instanceof Long)) {
1✔
253
                return null; // shift distance must be integral
1✔
254
            }
255

256
            // only use intValue for the left operand
257
            if (left instanceof Integer) {
1✔
258
                return intValue(left) << intValue(right);
1✔
259
            } else if (left instanceof Long) {
1✔
260
                return longValue(left) << intValue(right);
1✔
261
            }
262
            return null;
1✔
263
        }
264
        case RIGHT_SHIFT: {
265
            left = unaryPromotion(left);
1✔
266
            right = unaryPromotion(right);
1✔
267
            if (!(right instanceof Integer) && !(right instanceof Long)) {
1✔
268
                return null; // shift distance must be integral
1✔
269
            }
270

271
            // only use intValue for the left operand
272
            if (left instanceof Integer) {
1✔
273
                return intValue(left) >> intValue(right);
1✔
274
            } else if (left instanceof Long) {
1✔
275
                return longValue(left) >> intValue(right);
1✔
276
            }
277
            return null;
1✔
278
        }
279
        case UNSIGNED_RIGHT_SHIFT: {
280
            left = unaryPromotion(left);
1✔
281
            right = unaryPromotion(right);
1✔
282
            if (!(right instanceof Integer) && !(right instanceof Long)) {
1✔
283
                return null; // shift distance must be integral
1✔
284
            }
285

286
            // only use intValue for the left operand
287
            if (left instanceof Integer) {
1✔
288
                return intValue(left) >>> intValue(right);
1✔
289
            } else if (left instanceof Long) {
1✔
290
                return longValue(left) >>> intValue(right);
1✔
291
            }
292
            return null;
1✔
293
        }
294
        case ADD: {
295
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
296
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
297
                left = promoted.getLeft();
1✔
298
                right = promoted.getRight();
1✔
299

300
                if (left instanceof Integer) {
1✔
301
                    return intValue(left) + intValue(right);
1✔
302
                } else if (left instanceof Long) {
1✔
303
                    return longValue(left) + longValue(right);
1✔
304
                } else if (left instanceof Float) {
1✔
305
                    return floatValue(left) + floatValue(right);
1✔
306
                } else {
307
                    return doubleValue(left) + doubleValue(right);
1✔
308
                }
309
            } else if (left instanceof String) {
1✔
310
                // string concat
311
                return (String) left + right;
1✔
312
            } else if (right instanceof String) {
1✔
313
                // string concat
314
                return left + (String) right;
1✔
315
            }
316
            return null;
1✔
317
        }
318
        case SUB: {
319
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1!
320
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
321
                left = promoted.getLeft();
1✔
322
                right = promoted.getRight();
1✔
323

324
                if (left instanceof Integer) {
1✔
325
                    return intValue(left) - intValue(right);
1✔
326
                } else if (left instanceof Long) {
1✔
327
                    return longValue(left) - longValue(right);
1✔
328
                } else if (left instanceof Float) {
1✔
329
                    return floatValue(left) - floatValue(right);
1✔
330
                } else {
331
                    return doubleValue(left) - doubleValue(right);
1✔
332
                }
333
            }
334
            return null;
1✔
335
        }
336
        case MUL: {
337
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
338
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
339
                left = promoted.getLeft();
1✔
340
                right = promoted.getRight();
1✔
341

342
                if (left instanceof Integer) {
1✔
343
                    return intValue(left) * intValue(right);
1✔
344
                } else if (left instanceof Long) {
1✔
345
                    return longValue(left) * longValue(right);
1✔
346
                } else if (left instanceof Float) {
1✔
347
                    return floatValue(left) * floatValue(right);
1✔
348
                } else {
349
                    return doubleValue(left) * doubleValue(right);
1✔
350
                }
351
            }
352
            return null;
1✔
353
        }
354
        case DIV: {
355
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
356
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
357
                left = promoted.getLeft();
1✔
358
                right = promoted.getRight();
1✔
359

360
                if (left instanceof Integer) {
1✔
361
                    return intValue(left) / intValue(right);
1✔
362
                } else if (left instanceof Long) {
1✔
363
                    return longValue(left) / longValue(right);
1✔
364
                } else if (left instanceof Float) {
1✔
365
                    return floatValue(left) / floatValue(right);
1✔
366
                } else {
367
                    return doubleValue(left) / doubleValue(right);
1✔
368
                }
369
            }
370
            return null;
1✔
371
        }
372
        case MOD: {
373
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
374
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
375
                left = promoted.getLeft();
1✔
376
                right = promoted.getRight();
1✔
377

378
                if (left instanceof Integer) {
1✔
379
                    return intValue(left) % intValue(right);
1✔
380
                } else if (left instanceof Long) {
1✔
381
                    return longValue(left) % longValue(right);
1✔
382
                } else if (left instanceof Float) {
1✔
383
                    return floatValue(left) % floatValue(right);
1✔
384
                } else {
385
                    return doubleValue(left) % doubleValue(right);
1✔
386
                }
387
            }
388
            return null;
1✔
389
        }
390
        default:
391
            throw AssertionUtil.shouldNotReachHere("Unknown operator '" + node.getOperator() + "' in " + node);
×
392
        }
393
    }
394

395
    private static @Nullable Object compLE(Object left, Object right) {
396
        if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
397
            Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
398
            left = promoted.getLeft();
1✔
399
            right = promoted.getRight();
1✔
400

401
            if (left instanceof Integer) {
1✔
402
                return intValue(left) <= intValue(right);
1✔
403
            } else if (left instanceof Long) {
1✔
404
                return longValue(left) <= longValue(right);
1!
405
            } else if (left instanceof Float) {
1✔
406
                return floatValue(left) <= floatValue(right);
1!
407
            } else if (left instanceof Double) {
1!
408
                return doubleValue(left) <= doubleValue(right);
1✔
409
            }
410
        }
411
        return null;
1✔
412
    }
413

414
    private static @Nullable Boolean compLT(Object left, Object right) {
415
        if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
416
            Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
417
            left = promoted.getLeft();
1✔
418
            right = promoted.getRight();
1✔
419

420
            if (left instanceof Integer) {
1✔
421
                return intValue(left) < intValue(right);
1✔
422
            } else if (left instanceof Long) {
1✔
423
                return longValue(left) < longValue(right);
1!
424
            } else if (left instanceof Float) {
1✔
425
                return floatValue(left) < floatValue(right);
1!
426
            } else if (left instanceof Double) {
1!
427
                return doubleValue(left) < doubleValue(right);
1!
428
            }
429
        }
430
        return null;
1✔
431
    }
432

433
    private static @Nullable Boolean booleanInvert(@Nullable Object b) {
434
        if (b instanceof Boolean) {
1✔
435
            return !(Boolean) b;
1✔
436
        }
437
        return null;
1✔
438
    }
439

440
    private static @Nullable Boolean eqResult(Object left, Object right) {
441
        if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
442
            Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
443
            return promoted.getLeft().equals(promoted.getRight()); // fixme Double.NaN
1✔
444
        } else {
445
            return null; // not a constant expr on reference types
1✔
446
        }
447
    }
448

449
    private static boolean isConvertibleToNumber(Object o) {
450
        return o instanceof Number || o instanceof Character;
1✔
451
    }
452

453

454
    private static @Nullable Number unaryPromotion(Object t) {
455
        if (t instanceof Character) {
1✔
456
            return (int) (Character) t;
1✔
457
        } else if (t instanceof Number) {
1✔
458
            if (t instanceof Byte || t instanceof Short) {
1!
459
                return intValue(t);
×
460
            } else {
461
                return (Number) t;
1✔
462
            }
463
        }
464
        return null;
1✔
465
    }
466

467
    /**
468
     * This returns a pair in which both numbers have the dynamic type.
469
     * Both right and left need to be {@link #isConvertibleToNumber(Object)},
470
     * otherwise fails with ClassCastException.
471
     */
472
    private static Pair<Object, Object> binaryNumericPromotion(Object left, Object right) {
473
        left = projectCharOntoInt(left);
1✔
474
        right = projectCharOntoInt(right);
1✔
475
        if (left instanceof Double || right instanceof Double) {
1✔
476
            return Pair.of(doubleValue(left), doubleValue(right));
1✔
477
        } else if (left instanceof Float || right instanceof Float) {
1✔
478
            return Pair.of(floatValue(left), floatValue(right));
1✔
479
        } else if (left instanceof Long || right instanceof Long) {
1✔
480
            return Pair.of(longValue(left), longValue(right));
1✔
481
        } else {
482
            return Pair.of(intValue(left), intValue(right));
1✔
483
        }
484
    }
485

486
    private static Pair<Object, Object> booleanAwareBinaryPromotion(Object left, Object right) {
487
        if (left instanceof Boolean || right instanceof Boolean) {
1!
488
            if (left instanceof Boolean && right instanceof Boolean) {
1!
489
                return Pair.of(left, right);
1✔
490
            }
491
            return FAILED_BIN_PROMOTION;
1✔
492
        } else if (!isConvertibleToNumber(left) || !isConvertibleToNumber(right)) {
1!
493
            return FAILED_BIN_PROMOTION;
×
494
        } else {
495
            return binaryNumericPromotion(left, right);
1✔
496
        }
497
    }
498

499
    private static Object projectCharOntoInt(Object v) {
500
        if (v instanceof Character) {
1✔
501
            return (int) (Character) v;
1✔
502
        }
503
        return v;
1✔
504
    }
505

506
    private static Object numericCoercion(Object v, JTypeMirror target) {
507
        v = projectCharOntoInt(v); // map chars to a Number (widen it to int, which may be narrowed in the switch)
1✔
508

509
        if (target.isNumeric() && v instanceof Number) {
1!
510
            switch (((JPrimitiveType) target).getKind()) {
1!
511
            case BOOLEAN:
512
                throw new AssertionError("unreachable");
×
513
            case CHAR:
514
                return (char) intValue(v);
1✔
515
            case BYTE:
516
                return (byte) intValue(v);
1✔
517
            case SHORT:
518
                return (short) intValue(v);
1✔
519
            case INT:
520
                return intValue(v);
1✔
521
            case LONG:
522
                return longValue(v);
1✔
523
            case FLOAT:
524
                return floatValue(v);
1✔
525
            case DOUBLE:
526
                return doubleValue(v);
1✔
527
            default:
528
                throw AssertionUtil.shouldNotReachHere("exhaustive enum: " + target);
×
529
            }
530
        }
531
        return null;
1✔
532
    }
533

534
    private static Object stringCoercion(Object v) {
535
        if (v instanceof String) {
1!
536
            return v;
×
537
        }
538
        return null;
1✔
539
    }
540

541
    private static boolean booleanValue(Object x) {
542
        return (Boolean) x;
1✔
543
    }
544

545
    private static int intValue(Object x) {
546
        return ((Number) x).intValue();
1✔
547
    }
548

549
    private static long longValue(Object x) {
550
        return ((Number) x).longValue();
1✔
551
    }
552

553
    private static float floatValue(Object x) {
554
        return ((Number) x).floatValue();
1✔
555
    }
556

557
    private static double doubleValue(Object x) {
558
        return ((Number) x).doubleValue();
1✔
559
    }
560

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