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

pmd / pmd / 722

14 Aug 2026 11:10AM UTC coverage: 79.301% (+0.008%) from 79.293%
722

push

github

web-flow
[java] Fix #6611: UnnecessaryVarargsArrayCreation ignores overload ambiguity (#6949)

* [java] UnnecessaryVarargsArrayCreation: suppress when removing array creates overload ambiguity

When an explicit array creation (e.g. new String[]{...}) is passed to a
varargs parameter, the rule suggested removing it. But when the callee
has another overload that is also applicable to the expanded varargs
argument list, removing the array would make the call ambiguous (or
select a different overload), so the explicit array is required for
overload resolution and the suggested fix does not compile.

Before reporting, check the sibling overloads of the selected executable:
if any of them is applicable (fixed- or variable-arity) to the argument
list obtained by expanding the array initializer into varargs elements,
suppress the violation. Constructors are enumerated from the selected
executable's own class (exhaustive, since constructors are not inherited);
methods are streamed from the declaring type via
JTypeMirror.streamMethods, which includes overloads inherited from
supertypes.

The applicability check is a conservative approximation of JLS
15.12.2.2-4 based on subtyping, treating generic parameter types by
their upper bound.

Fixes #6611

Signed-off-by: 付典 <fudianchn@gmail.com>

* import order

---------

Signed-off-by: 付典 <fudianchn@gmail.com>
Co-authored-by: Sören Glimm <git@uncleowen.de>

19503 of 25562 branches covered (76.3%)

Branch coverage included in aggregate %.

48 of 59 new or added lines in 1 file covered. (81.36%)

27 existing lines in 10 files now uncovered.

42260 of 52322 relevant lines covered (80.77%)

0.82 hits per line

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

94.62
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementRule.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.bestpractices;
6

7
import static net.sourceforge.pmd.properties.PropertyFactory.stringListProperty;
8

9
import java.util.ArrayList;
10
import java.util.HashMap;
11
import java.util.List;
12
import java.util.Locale;
13
import java.util.Map;
14

15
import org.checkerframework.checker.nullness.qual.Nullable;
16

17
import net.sourceforge.pmd.lang.java.ast.ASTArrayAccess;
18
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
19
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
20
import net.sourceforge.pmd.lang.java.ast.ASTExpressionStatement;
21
import net.sourceforge.pmd.lang.java.ast.ASTFieldAccess;
22
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
23
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
24
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
25
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
26
import net.sourceforge.pmd.lang.java.ast.ASTMethodReference;
27
import net.sourceforge.pmd.lang.java.ast.ASTThisExpression;
28
import net.sourceforge.pmd.lang.java.ast.ASTTypeExpression;
29
import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
30
import net.sourceforge.pmd.lang.java.ast.QualifiableExpression;
31
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
32
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
33
import net.sourceforge.pmd.properties.PropertyDescriptor;
34
import net.sourceforge.pmd.reporting.RuleContext;
35

36
/**
37
 * Check that log.debug, log.trace, log.error, etc... statements are guarded by
38
 * some test expression on log.isDebugEnabled() or log.isTraceEnabled().
39
 *
40
 * @author Romain Pelisse - &lt;belaran@gmail.com&gt;
41
 * @author Heiko Rupp - &lt;hwr@pilhuhn.de&gt;
42
 * @author Tammo van Lessen - provided original XPath expression
43
 *
44
 */
45
public class GuardLogStatementRule extends AbstractJavaRulechainRule {
46
    /*
47
     * guard methods and log levels:
48
     *
49
     * log4j + apache commons logging (jakarta):
50
     * trace -> isTraceEnabled
51
     * debug -> isDebugEnabled
52
     * info  -> isInfoEnabled
53
     * warn  -> isWarnEnabled
54
     * error -> isErrorEnabled
55
     *
56
     *
57
     * java util:
58
     * log(Level.FINE) ->  isLoggable
59
     * finest ->  isLoggable
60
     * finer  ->  isLoggable
61
     * fine   ->  isLoggable
62
     * info   ->  isLoggable
63
     * warning -> isLoggable
64
     * severe  -> isLoggable
65
     */
66
    private static final PropertyDescriptor<List<String>> LOG_LEVELS =
1✔
67
            stringListProperty("logLevels")
1✔
68
                    .desc("LogLevels to guard")
1✔
69
                    .defaultValues("trace", "debug", "info", "warn", "error",
1✔
70
                                   "log", "finest", "finer", "fine", "info", "warning", "severe")
71
                    .build();
1✔
72

73
    private static final PropertyDescriptor<List<String>> GUARD_METHODS =
1✔
74
            stringListProperty("guardsMethods")
1✔
75
                    .desc("Method use to guard the log statement")
1✔
76
                    .defaultValues("isTraceEnabled", "isDebugEnabled", "isInfoEnabled", "isWarnEnabled", "isErrorEnabled", "isLoggable")
1✔
77
                    .build();
1✔
78

79
    private final Map<String, String> guardStmtByLogLevel = new HashMap<>(12);
1✔
80

81
    /*
82
     * java util methods, that need special handling, e.g. they require an argument, which
83
     * determines the log level
84
     */
85
    private static final String JAVA_UTIL_LOG_METHOD = "log";
86
    private static final String JAVA_UTIL_LOG_GUARD_METHOD = "isLoggable";
87

88
    public GuardLogStatementRule() {
89
        super(ASTExpressionStatement.class);
1✔
90
        definePropertyDescriptor(LOG_LEVELS);
1✔
91
        definePropertyDescriptor(GUARD_METHODS);
1✔
92
    }
1✔
93

94
    @Override
95
    public void start(RuleContext ctx) {
96
        extractProperties();
1✔
97
    }
1✔
98

99
    @Override
100
    public Object visit(ASTExpressionStatement node, Object data) {
101
        RuleContext ctx = (RuleContext) data;
1✔
102

103
        ASTExpression expr = node.getExpr();
1✔
104
        if (!(expr instanceof ASTMethodCall)) {
1✔
105
            return null;
1✔
106
        }
107

108
        ASTMethodCall methodCall = (ASTMethodCall) expr;
1✔
109
        String logLevel = getLogLevelName(methodCall);
1✔
110
        if (logLevel != null && guardStmtByLogLevel.containsKey(logLevel)) {
1✔
111
            if (needsGuard(methodCall) && !hasGuard(methodCall, logLevel)) {
1✔
112
                ctx.addViolation(node);
1✔
113
            }
114
        }
115
        return null;
1✔
116
    }
117

118
    @SuppressWarnings("PMD.SimplifyBooleanReturns")
119
    private boolean needsGuard(ASTMethodCall node) {
120
        if (node.getArguments().isEmpty()) {
1✔
121
            return false;
1✔
122
        }
123

124
        // get the message expression
125
        // it must either be a direct access (var / param access, lambda, method ref, etc.)
126
        // or a compile-time constant string to not require a guard
127
        int messageArg = getMessageArgIndex(node);
1✔
128
        ASTExpression messageExpr = node.getArguments().get(messageArg);
1✔
129
        if (!isDirectAccess(messageExpr) && !messageExpr.getConstFoldingResult().hasValue()) {
1✔
130
            return true;
1✔
131
        }
132

133
        // if any additional params are not a direct access or constant foldable, we need a guard
134
        return !areAdditionalParamsLowOverhead(node, messageArg + 1);
1✔
135
    }
136

137
    private boolean hasGuard(ASTMethodCall node, String logLevel) {
138
        for (ASTIfStatement ifStatement: node.ancestors(ASTIfStatement.class).take(2)) {
1✔
139
            if (ifStatement == null) {
1!
UNCOV
140
                return false;
×
141
            }
142
            if (containsGuardMethod(ifStatement, logLevel)) {
1✔
143
                return true;
1✔
144
            }
145
        }
1✔
146
        return false;
1✔
147
    }
148

149
    /**
150
     * Determines the log level, that is used. It is either the called method name
151
     * itself or - in case java util logging is used, then it is the first argument of
152
     * the method call (if it exists).
153
     *
154
     * @param methodCall the method call
155
     *
156
     * @return the log level or <code>null</code> if it could not be determined
157
     */
158
    private @Nullable String getLogLevelName(ASTMethodCall methodCall) {
159
        String methodName = methodCall.getMethodName();
1✔
160
        if (!JAVA_UTIL_LOG_METHOD.equals(methodName)) {
1✔
161
            return methodName; // probably logger.warn(...)
1✔
162
        }
163

164
        return getJutilLogLevelInFirstArg(methodCall);
1✔
165
    }
166

167
    private int getMessageArgIndex(ASTMethodCall methodCall) {
168
        String methodName = methodCall.getMethodName();
1✔
169
        if (JAVA_UTIL_LOG_METHOD.equals(methodName)) {
1✔
170
            // LOGGER.log(Level.FINE, "m")
171
            return 1;
1✔
172
        }
173

174
        return 0;
1✔
175
    }
176

177
    private @Nullable String getJutilLogLevelInFirstArg(ASTMethodCall methodCall) {
178
        ASTExpression firstArg = methodCall.getArguments().toStream().get(0);
1✔
179
        if (TypeTestUtil.isA("java.util.logging.Level", firstArg) && firstArg instanceof ASTNamedReferenceExpr) {
1!
180
            return ((ASTNamedReferenceExpr) firstArg).getName().toLowerCase(Locale.ROOT);
1✔
181
        }
182
        return null;
1✔
183
    }
184

185
    private boolean areAdditionalParamsLowOverhead(ASTMethodCall call, int messageArgIndex) {
186
        // return true if the statement has limited overhead even if unguarded,
187
        // so that we can ignore it
188
        return call.getArguments().toStream()
1✔
189
                   .drop(messageArgIndex) // remove the level argument if needed
1✔
190
                   .all(it -> isDirectAccess(it) || it.getConstFoldingResult().hasValue());
1✔
191
    }
192

193
    private static boolean isDirectAccess(ASTExpression it) {
194
        final boolean isPermittedType = it instanceof ASTLiteral || it instanceof ASTLambdaExpression
1✔
195
                || it instanceof ASTVariableAccess || it instanceof ASTThisExpression
196
                || it instanceof ASTMethodReference || it instanceof ASTFieldAccess
197
                || it instanceof ASTArrayAccess;
198

199
        if (!isPermittedType) {
1✔
200
            return false;
1✔
201
        }
202

203
        if (it instanceof QualifiableExpression) {
1✔
204
            final ASTExpression qualifier = ((QualifiableExpression) it).getQualifier();
1✔
205

206
            // for array access, we also care about the index expression
207
            if (it instanceof ASTArrayAccess && !isDirectAccess(((ASTArrayAccess) it).getIndexExpression())) {
1✔
208
                return false;
1✔
209
            }
210

211
            return qualifier == null || qualifier instanceof ASTTypeExpression || isDirectAccess(qualifier);
1!
212
        }
213

214
        return true;
1✔
215
    }
216

217
    private void extractProperties() {
218
        if (guardStmtByLogLevel.isEmpty()) {
1✔
219

220
            List<String> logLevels = new ArrayList<>(super.getProperty(LOG_LEVELS));
1✔
221
            List<String> guardMethods = new ArrayList<>(super.getProperty(GUARD_METHODS));
1✔
222

223
            if (guardMethods.isEmpty() && !logLevels.isEmpty()) {
1!
UNCOV
224
                throw new IllegalArgumentException("Can't specify logLevels without specifying guardMethods.");
×
225
            }
226
            if (logLevels.size() > guardMethods.size()) {
1✔
227
                // reuse the last guardMethod for the remaining log levels
228
                int needed = logLevels.size() - guardMethods.size();
1✔
229
                String lastGuard = guardMethods.get(guardMethods.size() - 1);
1✔
230
                for (int i = 0; i < needed; i++) {
1✔
231
                    guardMethods.add(lastGuard);
1✔
232
                }
233
            }
234
            if (logLevels.size() != guardMethods.size()) {
1!
UNCOV
235
                throw new IllegalArgumentException("For each logLevel a guardMethod must be specified.");
×
236
            }
237

238
            buildGuardStatementMap(logLevels, guardMethods);
1✔
239
        }
240
    }
1✔
241

242
    private void buildGuardStatementMap(List<String> logLevels, List<String> guardMethods) {
243
        for (int i = 0; i < logLevels.size(); i++) {
1✔
244
            String logLevel = logLevels.get(i);
1✔
245
            if (guardStmtByLogLevel.containsKey(logLevel)) {
1✔
246
                String combinedGuard = guardStmtByLogLevel.get(logLevel);
1✔
247
                combinedGuard += "|" + guardMethods.get(i);
1✔
248
                guardStmtByLogLevel.put(logLevel, combinedGuard);
1✔
249
            } else {
1✔
250
                guardStmtByLogLevel.put(logLevel, guardMethods.get(i));
1✔
251
            }
252
        }
253
    }
1✔
254

255
    private boolean containsGuardMethod(ASTIfStatement ifStatement, String logLevel) {
256
        for (ASTMethodCall maybeAGuardCall : ifStatement.getCondition().descendantsOrSelf().filterIs(ASTMethodCall.class)) {
1✔
257
            String guardMethodName = maybeAGuardCall.getMethodName();
1✔
258
            // the guard is adapted to the actual log statement
259

260
            if (!guardStmtByLogLevel.get(logLevel).contains(guardMethodName)) {
1✔
261
                continue;
1✔
262
            }
263

264
            if (JAVA_UTIL_LOG_GUARD_METHOD.equals(guardMethodName)) {
1✔
265
                // java.util.logging: guard method with argument. Verify the log level
266
                if (logLevel.equals(getJutilLogLevelInFirstArg(maybeAGuardCall))) {
1✔
267
                    return true;
1✔
268
                }
269
            } else {
270
                return true;
1✔
271
            }
272

273
        }
1✔
274
        return false;
1✔
275
    }
276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc