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

pmd / pmd / 319

21 Dec 2025 06:30PM UTC coverage: 78.964% (-0.002%) from 78.966%
319

push

github

adangel
[java] Fix grammar of switch label (#6299)

18515 of 24326 branches covered (76.11%)

Branch coverage included in aggregate %.

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

3 existing lines in 1 file now uncovered.

40313 of 50174 relevant lines covered (80.35%)

0.81 hits per line

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

94.59
/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 - <belaran@gmail.com>
41
 * @author Heiko Rupp - <hwr@pilhuhn.de>
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
        ASTExpression expr = node.getExpr();
1✔
102
        if (!(expr instanceof ASTMethodCall)) {
1✔
103
            return null;
1✔
104
        }
105

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

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

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

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

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

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

162
        return getJutilLogLevelInFirstArg(methodCall);
1✔
163
    }
164

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

172
        return 0;
1✔
173
    }
174

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

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

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

197
        if (!isPermittedType) {
1✔
198
            return false;
1✔
199
        }
200

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

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

209
            return qualifier == null || qualifier instanceof ASTTypeExpression || isDirectAccess(qualifier);
1!
210
        }
211

212
        return true;
1✔
213
    }
214

215
    private void extractProperties() {
216
        if (guardStmtByLogLevel.isEmpty()) {
1✔
217

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

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

236
            buildGuardStatementMap(logLevels, guardMethods);
1✔
237
        }
238
    }
1✔
239

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

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

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

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

271
        }
1✔
272
        return false;
1✔
273
    }
274
}
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