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

pmd / pmd / 590

12 Jun 2026 09:33AM UTC coverage: 79.109% (+0.001%) from 79.108%
590

push

github

web-flow
[apex] Fix #2955: ApexSOQLInjection: False positive when concatenating strings (#6765)

19070 of 25035 branches covered (76.17%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

41418 of 51427 relevant lines covered (80.54%)

0.81 hits per line

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

93.64
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesRule.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.rule.codestyle;
6

7
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.ALWAYS;
8
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.BALANCING;
9
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.CLARIFYING;
10
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.NEVER;
11
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.definitely;
12
import static net.sourceforge.pmd.lang.java.rule.codestyle.UselessParenthesesRule.Necessity.necessaryIf;
13

14
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression;
15
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
16
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
17
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
18
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
19
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
20
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
21
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
22
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression;
23
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
24
import net.sourceforge.pmd.lang.java.ast.JavaNode;
25
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
26
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
27
import net.sourceforge.pmd.properties.PropertyDescriptor;
28
import net.sourceforge.pmd.properties.PropertyFactory;
29
import net.sourceforge.pmd.util.AssertionUtil;
30

31

32
public final class UselessParenthesesRule extends AbstractJavaRulechainRule {
33
    // todo rename to UnnecessaryParentheses
34

35
    private static final PropertyDescriptor<Boolean> IGNORE_CLARIFYING =
1✔
36
        PropertyFactory.booleanProperty("ignoreClarifying")
1✔
37
                       .defaultValue(true)
1✔
38
                       .desc("Ignore parentheses that separate expressions of difference precedence,"
1✔
39
                                 + " like in `(a % 2 == 0) ? x : -x`")
40
                       .build();
1✔
41

42
    private static final PropertyDescriptor<Boolean> IGNORE_BALANCING =
1✔
43
        PropertyFactory.booleanProperty("ignoreBalancing")
1✔
44
                       .defaultValue(true)
1✔
45
                       .desc("Ignore unnecessary parentheses that appear balanced around an equality "
1✔
46
                                 + "operator, because the other operand requires parentheses."
47
                                 + "For example, in `(a == null) == (b == null)`, only the second pair "
48
                                 + "of parentheses is necessary, but the expression is clearer that way.")
49
                       .build();
1✔
50
    private static final int MAX_SNIPPET_LENGTH = 50;
51

52
    public UselessParenthesesRule() {
53
        super(ASTExpression.class);
1✔
54
        definePropertyDescriptor(IGNORE_CLARIFYING);
1✔
55
        definePropertyDescriptor(IGNORE_BALANCING);
1✔
56
    }
1✔
57

58
    private boolean reportClarifying() {
59
        return !getProperty(IGNORE_CLARIFYING);
1✔
60
    }
61

62
    private boolean reportBalancing() {
63
        return !getProperty(IGNORE_BALANCING);
1!
64
    }
65

66

67
    @Override
68
    public Object visitJavaNode(JavaNode node, Object data) {
69
        if (node instanceof ASTExpression) {
1!
70
            checkExpr((ASTExpression) node, data);
1✔
71
        } else {
72
            throw new IllegalArgumentException("Expected an expression, got " + node);
×
73
        }
74
        return null;
1✔
75
    }
76

77
    private void checkExpr(ASTExpression e, Object data) {
78
        if (!e.isParenthesized()) {
1✔
79
            return;
1✔
80
        }
81

82
        Necessity necessity = needsParentheses(e, e.getParent());
1✔
83

84
        if (necessity == NEVER
1✔
85
            || reportClarifying() && necessity == CLARIFYING
1✔
86
            || reportBalancing() && necessity == BALANCING) {
1!
87
            String template = e.getParenthesisDepth() > 1 ? "Duplicate parentheses around `{0}`."
1✔
88
                : "Useless parentheses around `{0}`.";
1✔
89
            CharSequence snippet = PrettyPrintingUtil.prettyPrint(e);
1✔
90
            String dots = "...";
1✔
91
            if (snippet.length() > MAX_SNIPPET_LENGTH) {
1✔
92
                snippet = snippet.subSequence(0, MAX_SNIPPET_LENGTH - dots.length()) + dots;
1✔
93
            }
94
            asCtx(data).addViolationWithMessage(e, template,
1✔
95
                snippet);
96

97
        }
98
    }
1✔
99

100
    public static Necessity needsParentheses(ASTExpression inner, JavaNode outer) {
101
        // Note: as of jdk 15, PatternExpression cannot be parenthesized
102
        // TypeExpression may never be parenthesized either
103
        assert inner.isParenthesized() : inner + " is not parenthesized";
1!
104

105

106
        if (inner.getParenthesisDepth() > 1
1✔
107
            || !(outer instanceof ASTExpression)) {
108
            // ((a + b))        unnecessary
109
            // new int[(2)]     unnecessary
110
            // return (1 + 2);
111
            return NEVER;
1✔
112
        }
113

114
        if (inner instanceof ASTPrimaryExpression) {
1✔
115
            return NEVER;
1✔
116
        }
117
        if (inner instanceof ASTSwitchExpression) {
1✔
118
            return outer instanceof ASTPrimaryExpression && inner.getIndexInParent() == 0
1✔
119
                    ? ALWAYS : NEVER;
1✔
120
        }
121

122
        if (outer instanceof ASTLambdaExpression) {
1✔
123
            // () -> (a + b)        unnecessary
124
            // () -> (() -> b)      unnecessary
125
            // () -> (a ? b : c);   unnecessary unless the parent of the lambda is itself a ternary
126
            if (inner instanceof ASTLambdaExpression) {
1✔
127
                return NEVER;
1✔
128
            }
129
            return definitely(inner instanceof ASTConditionalExpression && outer.getParent() instanceof ASTConditionalExpression);
1✔
130
        }
131

132
        if (inner instanceof ASTAssignmentExpression) {
1✔
133
            //  a * (b = c)          necessary
134
            //  a ? (b = c) : d      necessary
135
            //  a = (b = c)          associative
136
            //  (a = b) = c          (impossible)
137
            return outer instanceof ASTAssignmentExpression ? NEVER : ALWAYS;
1✔
138
        }
139

140
        if (inner instanceof ASTConditionalExpression) {
1✔
141

142
            // a ? (b ? c : d) : e    necessary
143
            // a ? b : (c ? d : e)    associative
144
            // (a ? b : c) ? d : e    necessary
145
            if (outer instanceof ASTConditionalExpression) {
1✔
146
                return inner.getIndexInParent() == 2 ? NEVER  // last child
1✔
147
                                                     : ALWAYS;
1✔
148
            } else {
149
                return necessaryIf(!(outer instanceof ASTAssignmentExpression));
1!
150
            }
151
        }
152

153
        if (inner instanceof ASTLambdaExpression) {
1✔
154
            // a ? (() -> b) + c : d     invalid, but necessary
155
            // a ? (() -> b) : d         clarifying
156
            return outer instanceof ASTConditionalExpression ? CLARIFYING
1✔
157
                                                             : definitely(!(outer instanceof ASTAssignmentExpression));
1✔
158
        }
159

160
        if (inner instanceof ASTInfixExpression) {
1✔
161
            if (outer instanceof ASTInfixExpression) {
1✔
162
                BinaryOp inop = ((ASTInfixExpression) inner).getOperator();
1✔
163
                BinaryOp outop = ((ASTInfixExpression) outer).getOperator();
1✔
164
                int comp = outop.comparePrecedence(inop);
1✔
165

166
                // (a * b) + c       unnecessary
167
                // a * (b + c)       necessary
168
                // a + (b + c)       unnecessary
169
                // (a + b) + c       unnecessary
170
                // (a - b) + c       clarifying
171

172
                if (comp > 0) {
1✔
173
                    return ALWAYS; // outer has greater precedence
1✔
174
                } else if (comp < 0) {
1✔
175
                    return CLARIFYING; // outer has lower precedence, but the operators are different
1✔
176
                }
177

178
                // the rest deals with ties in precedence
179

180
                if (inner.getIndexInParent() == 1) {
1✔
181
                    // parentheses are on the right
182
                    // eg a - (b + c)
183

184
                    if (associatesRightWith(outop, inop, (ASTInfixExpression) inner, (ASTInfixExpression) outer)) {
1✔
185
                        // a & (b & c)
186
                        // a | (b | c)
187
                        // a ^ (b ^ c)
188
                        // a && (b && c)
189
                        // a || (b || c)
190
                        return NEVER;
1✔
191
                    } else {
192
                        return ALWAYS;
1✔
193
                    }
194
                } else {
195
                    // parentheses are on the left
196
                    // eg (a + b) + c
197
                    if (outop.hasSamePrecedenceAs(BinaryOp.EQ) // EQ or NE
1✔
198
                        && ((ASTInfixExpression) outer).getRightOperand().isParenthesized()) {
1✔
199
                        // (a == null) == (b == null)
200
                        return BALANCING;
1✔
201
                    } else if (outop == BinaryOp.ADD
1✔
202
                        && inop.hasSamePrecedenceAs(BinaryOp.ADD) && inner.getTypeMirror().isNumeric()
1!
203
                        && !((ASTInfixExpression) outer).getTypeMirror().isNumeric()) {
1✔
204
                        return CLARIFYING;
1✔
205
                    }
206

207
                    return NEVER;
1✔
208
                }
209
            } else { // outer !is ASTInfixExpression
210
                if (outer instanceof ASTConditionalExpression && inner.getIndexInParent() == 0) {
1!
211
                    // (a == b) ? .. : ..
212
                    return CLARIFYING;
1✔
213
                }
214

215
                if (outer instanceof ASTAssignmentExpression) {
1✔
216
                    return NEVER;
1✔
217
                }
218

219
                return necessaryIf(isUnary(outer) || outer instanceof ASTPrimaryExpression);
1!
220
            }
221
        }
222

223
        if (isUnary(inner)) {
1!
224
            return isUnary(outer) ? NEVER : necessaryIf(outer instanceof ASTPrimaryExpression);
1✔
225
        }
226

227

UNCOV
228
        throw AssertionUtil.shouldNotReachHere("Unhandled case inside " + outer);
×
229
    }
230

231
    private static boolean isUnary(JavaNode expr) {
232
        return expr instanceof ASTUnaryExpression || expr instanceof ASTCastExpression;
1✔
233
    }
234

235
    // Returns true if it is safe to remove parentheses in the expression `A outop (B inop C)`
236
    // outop and inop have the same precedence class
237
    private static boolean associatesRightWith(BinaryOp outop, BinaryOp inop, ASTInfixExpression inner, ASTInfixExpression outer) {
238

239
        /*
240
         Notes about associativity:
241
         - Integer multiplication/addition is associative when the operands are all of the same type.
242
         - Floating-point multiplication/addition is not associative.
243
         - The boolean equality operators are associative, but input type != output type in general
244
         - Bitwise and logical operators are associative
245
         - The conditional-OR/AND operator is fully associative with respect to both side effects and result value.
246
         */
247

248
        switch (inop) {
1✔
249
        case AND:
250
        case OR:
251
        case XOR:
252
        case CONDITIONAL_AND:
253
        case CONDITIONAL_OR:
254
            return true;
1✔
255
        case MUL:
256
            // a * (b * c)      -- yes
257
            // a * (b / c)      -- no, could change semantics
258
            // a * (b % c)      -- no
259

260
            // a / (b * c)      -- no
261
            // a / (b / c)      -- no
262
            // a / (b % c)      -- no
263

264
            // a % (b * c)      -- no
265
            // a % (b / c)      -- no
266
            // a % (b % c)      -- no
267

268
            return inop == outop; // == MUL
1✔
269
        case SUB:
270
        case ADD:
271
            // a + (b + c)      -- yes, unless outop is concatenation and inop is addition, or operands are floats
272
            // a + (b - c)      -- yes
273

274
            // a - (b + c)      -- no
275
            // a - (b - c)      -- no
276
            return outop == BinaryOp.ADD
1✔
277
                && outer.getTypeMirror().isPrimitive() == inner.getTypeMirror().isPrimitive()
1✔
278
                && !inner.getTypeMirror().isFloatingPoint();
1✔
279
        default:
280
            return false;
1✔
281
        }
282
    }
283

284
    public enum Necessity {
1✔
285
        ALWAYS,
1✔
286
        NEVER,
1✔
287
        CLARIFYING,
1✔
288
        BALANCING;
1✔
289

290
        static Necessity definitely(boolean b) {
291
            return b ? ALWAYS : NEVER;
1✔
292
        }
293

294
        static Necessity necessaryIf(boolean b) {
295
            return b ? ALWAYS : CLARIFYING;
1✔
296
        }
297
    }
298
}
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