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

pmd / pmd / 442

24 Mar 2026 06:29PM UTC coverage: 79.043% (-0.004%) from 79.047%
442

push

github

web-flow
chore: bump maven from 3.9.12 to 3.9.14 (#6514)

18601 of 24429 branches covered (76.14%)

Branch coverage included in aggregate %.

40589 of 50454 relevant lines covered (80.45%)

0.81 hits per line

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

93.89
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.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.errorprone;
6

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

10
import java.util.ArrayList;
11
import java.util.HashMap;
12
import java.util.HashSet;
13
import java.util.List;
14
import java.util.Map;
15
import java.util.Set;
16

17
import org.checkerframework.checker.nullness.qual.Nullable;
18

19
import net.sourceforge.pmd.lang.ast.Node;
20
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
21
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
22
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression;
23
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
24
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
25
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
26
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
27
import net.sourceforge.pmd.lang.java.ast.ASTExecutableDeclaration;
28
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
29
import net.sourceforge.pmd.lang.java.ast.ASTExpressionStatement;
30
import net.sourceforge.pmd.lang.java.ast.ASTFinallyClause;
31
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
32
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
33
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
34
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
35
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
36
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
37
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
38
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
39
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
40
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
41
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
42
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLike;
43
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
44
import net.sourceforge.pmd.lang.java.ast.ASTType;
45
import net.sourceforge.pmd.lang.java.ast.ASTTypeExpression;
46
import net.sourceforge.pmd.lang.java.ast.ASTTypePattern;
47
import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
48
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
49
import net.sourceforge.pmd.lang.java.ast.ASTVariableId;
50
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
51
import net.sourceforge.pmd.lang.java.ast.JavaNode;
52
import net.sourceforge.pmd.lang.java.ast.TypeNode;
53
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
54
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
55
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
56
import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil;
57
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
58
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
59
import net.sourceforge.pmd.lang.java.types.InvocationMatcher;
60
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
61
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
62
import net.sourceforge.pmd.properties.PropertyDescriptor;
63
import net.sourceforge.pmd.reporting.RuleContext;
64

65
/**
66
 * Makes sure you close your database connections. It does this by looking for
67
 * code patterned like this:
68
 *
69
 * <pre>
70
 *  Connection c = X;
71
 *  try {
72
 *   // do stuff, and maybe catch something
73
 *  } finally {
74
 *   c.close();
75
 *  }
76
 * </pre>
77
 *
78
 *  @author original author unknown
79
 *  @author Contribution from Pierre Mathien
80
 */
81
public class CloseResourceRule extends AbstractJavaRule {
82

83
    private static final String WRAPPING_TRY_WITH_RES_VAR_MESSAGE = "it is recommended to wrap resource ''{0}'' in try-with-resource declaration directly";
84
    private static final String REASSIGN_BEFORE_CLOSED_MESSAGE = "''{0}'' is reassigned, but the original instance is not closed";
85
    private static final String CLOSE_IN_FINALLY_BLOCK_MESSAGE = "''{0}'' is not closed within a finally block, thus might not be closed at all in case of exceptions";
86

87
    private static final PropertyDescriptor<List<String>> CLOSE_TARGETS_DESCRIPTOR =
1✔
88
            stringListProperty("closeTargets")
1✔
89
                           .desc("Methods which may close this resource")
1✔
90
                           .emptyDefaultValue()
1✔
91
                           .build();
1✔
92

93
    private static final PropertyDescriptor<List<String>> TYPES_DESCRIPTOR =
1✔
94
            stringListProperty("types")
1✔
95
                    .desc("Affected types")
1✔
96
                    .defaultValues("java.lang.AutoCloseable", "java.sql.Connection", "java.sql.Statement", "java.sql.ResultSet")
1✔
97
                    .build();
1✔
98

99
    private static final PropertyDescriptor<Boolean> USE_CLOSE_AS_DEFAULT_TARGET =
1✔
100
            booleanProperty("closeAsDefaultTarget")
1✔
101
                    .desc("Consider 'close' as a target by default").defaultValue(true).build();
1✔
102

103
    private static final PropertyDescriptor<List<String>> ALLOWED_RESOURCE_TYPES =
1✔
104
            stringListProperty("allowedResourceTypes")
1✔
105
            .desc("Exact class names that do not need to be closed")
1✔
106
            .defaultValues("java.io.ByteArrayOutputStream", "java.io.ByteArrayInputStream", "java.io.StringWriter",
1✔
107
                    "java.io.CharArrayWriter", "java.util.stream.Stream", "java.util.stream.IntStream", "java.util.stream.LongStream",
108
                    "java.util.stream.DoubleStream")
109
            .build();
1✔
110

111
    private static final PropertyDescriptor<List<String>> ALLOWED_RESOURCE_METHOD_PATTERNS =
1✔
112
            stringListProperty("allowedResourceMethodPatterns")
1✔
113
            .desc("Method invocation patterns that return resources managed externally (InvocationMatcher syntax)")
1✔
114
            .defaultValues(
1✔
115
                // javax.servlet (Java EE / Jakarta EE 8 and earlier)
116
                "javax.servlet.ServletRequest#getReader()",
117
                "javax.servlet.ServletResponse#getWriter()",
118
                "javax.servlet.ServletResponse#getOutputStream()",
119
                "javax.servlet.http.HttpServletRequest#getReader()",
120
                "javax.servlet.http.HttpServletResponse#getWriter()",
121
                "javax.servlet.http.HttpServletResponse#getOutputStream()",
122
                // jakarta.servlet (Jakarta EE 9+)
123
                "jakarta.servlet.ServletRequest#getReader()",
124
                "jakarta.servlet.ServletResponse#getWriter()",
125
                "jakarta.servlet.ServletResponse#getOutputStream()",
126
                "jakarta.servlet.http.HttpServletRequest#getReader()",
127
                "jakarta.servlet.http.HttpServletResponse#getWriter()",
128
                "jakarta.servlet.http.HttpServletResponse#getOutputStream()"
129
            )
130
            .build();
1✔
131

132
    private static final PropertyDescriptor<Boolean> DETECT_CLOSE_NOT_IN_FINALLY =
1✔
133
            booleanProperty("closeNotInFinally")
1✔
134
                .desc("Detect if 'close' (or other closeTargets) is called outside of a finally-block").defaultValue(false).build();
1✔
135

136
    private static final InvocationMatcher OBJECTS_NON_NULL = InvocationMatcher.parse("java.util.Objects#nonNull(_)");
1✔
137
    private static final InvocationMatcher FILESYSTEMS_GET_DEFAULT = InvocationMatcher.parse("java.nio.file.FileSystems#getDefault()");
1✔
138

139
    private final Set<String> types = new HashSet<>();
1✔
140
    private final Set<String> simpleTypes = new HashSet<>();
1✔
141
    private final Set<String> closeTargets = new HashSet<>();
1✔
142
    private final List<InvocationMatcher> managedResourceMethodPatterns = new ArrayList<>();
1✔
143

144
    // keeps track of already reported violations to avoid duplicated violations for the same variable
145
    private final Set<String> reportedVarNames = new HashSet<>();
1✔
146

147
    public CloseResourceRule() {
1✔
148
        definePropertyDescriptor(CLOSE_TARGETS_DESCRIPTOR);
1✔
149
        definePropertyDescriptor(TYPES_DESCRIPTOR);
1✔
150
        definePropertyDescriptor(USE_CLOSE_AS_DEFAULT_TARGET);
1✔
151
        definePropertyDescriptor(ALLOWED_RESOURCE_TYPES);
1✔
152
        definePropertyDescriptor(ALLOWED_RESOURCE_METHOD_PATTERNS);
1✔
153
        definePropertyDescriptor(DETECT_CLOSE_NOT_IN_FINALLY);
1✔
154
    }
1✔
155

156
    @Override
157
    public void start(RuleContext ctx) {
158
        closeTargets.clear();
1✔
159
        simpleTypes.clear();
1✔
160
        types.clear();
1✔
161

162
        if (getProperty(CLOSE_TARGETS_DESCRIPTOR) != null) {
1!
163
            closeTargets.addAll(getProperty(CLOSE_TARGETS_DESCRIPTOR));
1✔
164
        }
165
        if (getProperty(USE_CLOSE_AS_DEFAULT_TARGET)) {
1✔
166
            closeTargets.add("close");
1✔
167
        }
168
        if (getProperty(TYPES_DESCRIPTOR) != null) {
1!
169
            types.addAll(getProperty(TYPES_DESCRIPTOR));
1✔
170
            for (String type : getProperty(TYPES_DESCRIPTOR)) {
1✔
171
                simpleTypes.add(toSimpleType(type));
1✔
172
            }
1✔
173
        }
174

175
        managedResourceMethodPatterns.clear();
1✔
176
        List<String> patterns = getProperty(ALLOWED_RESOURCE_METHOD_PATTERNS);
1✔
177
        if (patterns != null) {
1!
178
            for (String pattern : patterns) {
1✔
179
                String trimmed = pattern.trim();
1✔
180
                if (!trimmed.isEmpty()) {
1!
181
                    managedResourceMethodPatterns.add(InvocationMatcher.parse(trimmed));
1✔
182
                }
183
            }
1✔
184
        }
185
    }
1✔
186

187
    private static String toSimpleType(String fullyQualifiedClassName) {
188
        int lastIndexOf = fullyQualifiedClassName.lastIndexOf('.');
1✔
189
        if (lastIndexOf > -1) {
1✔
190
            return fullyQualifiedClassName.substring(lastIndexOf + 1);
1✔
191
        } else {
192
            return fullyQualifiedClassName;
1✔
193
        }
194
    }
195

196
    @Override
197
    public Object visit(ASTConstructorDeclaration node, Object data) {
198
        checkForResources(node, data);
1✔
199
        return super.visit(node, data);
1✔
200
    }
201

202
    @Override
203
    public Object visit(ASTMethodDeclaration node, Object data) {
204
        checkForResources(node, data);
1✔
205
        return super.visit(node, data);
1✔
206
    }
207

208
    private void checkForResources(ASTExecutableDeclaration methodOrConstructor, Object data) {
209
        reportedVarNames.clear();
1✔
210
        Map<ASTVariableId, TypeNode> resVars = getResourceVariables(methodOrConstructor);
1✔
211
        for (Map.Entry<ASTVariableId, TypeNode> resVarEntry : resVars.entrySet()) {
1✔
212
            ASTVariableId resVar = resVarEntry.getKey();
1✔
213
            TypeNode runtimeType = resVarEntry.getValue();
1✔
214
            TypeNode resVarType = wrappedResourceTypeOrReturn(resVar, runtimeType);
1✔
215

216
            if (isWrappingResourceSpecifiedInTry(resVar)) {
1✔
217
                reportedVarNames.add(resVar.getName());
1✔
218
                asCtx(data).addViolationWithMessage(resVar, WRAPPING_TRY_WITH_RES_VAR_MESSAGE,
1✔
219
                                                    resVar.getName());
1✔
220
            } else if (shouldVarOfTypeBeClosedInMethod(resVar, resVarType, methodOrConstructor)) {
1✔
221
                reportedVarNames.add(resVar.getName());
1✔
222
                addCloseResourceViolation(resVar, runtimeType, data);
1✔
223
            } else if (isNotAllowedResourceType(resVarType)) {
1✔
224
                ASTExpressionStatement reassigningStatement = getFirstReassigningStatementBeforeBeingClosed(resVar, methodOrConstructor);
1✔
225
                if (reassigningStatement != null) {
1✔
226
                    reportedVarNames.add(resVar.getName());
1✔
227
                    asCtx(data).addViolationWithMessage(reassigningStatement, REASSIGN_BEFORE_CLOSED_MESSAGE,
1✔
228
                                                        resVar.getName());
1✔
229
                }
230
            }
231
        }
1✔
232
    }
1✔
233

234
    private Map<ASTVariableId, TypeNode> getResourceVariables(ASTExecutableDeclaration method) {
235
        Map<ASTVariableId, TypeNode> resVars = new HashMap<>();
1✔
236

237
        if (method.getBody() == null) {
1✔
238
            return resVars;
1✔
239
        }
240

241
        List<ASTVariableId> vars = method.getBody().descendants(ASTVariableId.class)
1✔
242
            .filterNot(ASTVariableId::isFormalParameter)
1✔
243
            .filterNot(ASTVariableId::isExceptionBlockParameter)
1✔
244
            .filter(this::isVariableNotSpecifiedInTryWithResource)
1✔
245
            .filter(var -> isResourceTypeOrSubtype(var) || isNodeInstanceOfResourceType(getTypeOfVariable(var)))
1✔
246
            .filterNot(var -> var.isAnnotationPresent("lombok.Cleanup"))
1✔
247
            .filterNot(this::isDefaultFileSystem)
1✔
248
            .filterNot(this::isInitializedFromManagedResourceMethod)
1✔
249
            .toList();
1✔
250

251
        for (ASTVariableId var : vars) {
1✔
252
            TypeNode varType = getTypeOfVariable(var);
1✔
253
            resVars.put(var, varType);
1✔
254
        }
1✔
255
        return resVars;
1✔
256
    }
257

258
    private TypeNode getTypeOfVariable(ASTVariableId var) {
259
        TypeNode runtimeType = getRuntimeTypeOfVariable(var);
1✔
260
        return runtimeType != null ? runtimeType : var.getTypeNode();
1✔
261
    }
262

263
    private TypeNode getRuntimeTypeOfVariable(ASTVariableId var) {
264
        ASTExpression initExpr = var.getInitializer();
1✔
265
        return var.isTypeInferred() || isRuntimeType(initExpr) ? initExpr : null;
1✔
266
    }
267

268
    private boolean isRuntimeType(ASTExpression expr) {
269
        if (expr == null || isMethodCall(expr) || expr instanceof ASTNullLiteral) {
1✔
270
            return false;
1✔
271
        }
272

273
        @Nullable
274
        JTypeDeclSymbol symbol = expr.getTypeMirror().getSymbol();
1✔
275
        return symbol != null && !symbol.isUnresolved();
1!
276
    }
277

278
    private TypeNode wrappedResourceTypeOrReturn(ASTVariableId var, TypeNode defaultVal) {
279
        TypeNode wrappedResType = getWrappedResourceType(var);
1✔
280
        return wrappedResType != null ? wrappedResType : defaultVal;
1✔
281
    }
282

283
    private TypeNode getWrappedResourceType(ASTVariableId var) {
284
        ASTExpression initExpr = initializerExpressionOf(var);
1✔
285
        if (initExpr != null) {
1✔
286
            ASTConstructorCall resAlloc = getLastResourceAllocation(initExpr);
1✔
287
            if (resAlloc != null) {
1✔
288
                ASTExpression firstArgRes = getFirstArgumentVariableIfResource(resAlloc);
1✔
289
                return firstArgRes != null ? firstArgRes : resAlloc;
1✔
290
            }
291
        }
292
        return null;
1✔
293
    }
294

295
    private ASTExpression initializerExpressionOf(ASTVariableId var) {
296
        return var.getInitializer();
1✔
297
    }
298

299
    private ASTConstructorCall getLastResourceAllocation(ASTExpression expr) {
300
        List<ASTConstructorCall> allocations = expr.descendantsOrSelf().filterIs(ASTConstructorCall.class).toList();
1✔
301
        int lastAllocIndex = allocations.size() - 1;
1✔
302
        for (int allocIndex = lastAllocIndex; allocIndex >= 0; allocIndex--) {
1✔
303
            ASTConstructorCall allocation = allocations.get(allocIndex);
1✔
304
            if (isResourceTypeOrSubtype(allocation)) {
1✔
305
                return allocation;
1✔
306
            }
307
        }
308
        return null;
1✔
309
    }
310

311
    private ASTExpression getFirstArgumentVariableIfResource(ASTConstructorCall allocation) {
312
        ASTArgumentList argsList = allocation.getArguments();
1✔
313
        if (argsList != null && argsList.size() > 0) {
1!
314
            ASTExpression firstArg = argsList.get(0);
1✔
315
            return isNotMethodCall(firstArg) && isResourceTypeOrSubtype(firstArg)
1✔
316
                    ? firstArg
1✔
317
                    : null;
1✔
318
        }
319
        return null;
1✔
320
    }
321

322
    private boolean isNotMethodCall(ASTExpression expr) {
323
        return !isMethodCall(expr);
1✔
324
    }
325

326
    private boolean isMethodCall(ASTExpression expression) {
327
        return expression instanceof ASTMethodCall;
1✔
328
    }
329

330
    private boolean isWrappingResourceSpecifiedInTry(ASTVariableId var) {
331
        ASTVariableAccess wrappedVarName = getWrappedVariableName(var);
1✔
332
        if (wrappedVarName != null) {
1✔
333
            JVariableSymbol referencedSym = wrappedVarName.getReferencedSym();
1✔
334
            if (referencedSym != null) {
1!
335
                ASTVariableId referencedVar = referencedSym.tryGetNode();
1✔
336
                if (referencedVar != null) {
1!
337
                    List<ASTTryStatement> tryContainers = referencedVar.ancestors(ASTTryStatement.class).toList();
1✔
338
                    for (ASTTryStatement tryContainer : tryContainers) {
1✔
339
                        if (isTryWithResourceSpecifyingVariable(tryContainer, referencedVar)) {
1✔
340
                            return true;
1✔
341
                        }
342
                    }
1✔
343
                }
344
            }
345
        }
346
        return false;
1✔
347
    }
348

349
    private boolean shouldVarOfTypeBeClosedInMethod(ASTVariableId var, TypeNode type,
350
                                                    ASTExecutableDeclaration method) {
351
        return isNotAllowedResourceType(type) && isNotWrappingResourceMethodParameter(var, method)
1✔
352
                && isResourceVariableUnclosed(var);
1✔
353
    }
354

355
    private boolean isNotAllowedResourceType(TypeNode varType) {
356
        return !isAllowedResourceType(varType);
1✔
357
    }
358

359
    private boolean isAllowedResourceType(TypeNode refType) {
360
        List<String> allowedResourceTypes = getProperty(ALLOWED_RESOURCE_TYPES);
1✔
361
        if (allowedResourceTypes != null) {
1!
362
            for (String type : allowedResourceTypes) {
1✔
363
                // the check here must be a exact type match, since subclasses may override close()
364
                // and actually require closing
365
                if (TypeTestUtil.isExactlyA(type, refType)) {
1✔
366
                    return true;
1✔
367
                }
368
            }
1✔
369
        }
370
        return false;
1✔
371
    }
372

373
    private boolean isNotWrappingResourceMethodParameter(ASTVariableId var,
374
                                                         ASTExecutableDeclaration method) {
375
        return !isWrappingResourceMethodParameter(var, method);
1✔
376
    }
377

378
    /**
379
     * Checks whether the variable is a resource and initialized from a method parameter.
380
     * @param var the resource variable that is being initialized
381
     * @param method the method or constructor in which the variable is declared
382
     * @return <code>true</code> if the variable is a resource and initialized from a method parameter. <code>false</code>
383
     *         otherwise.
384
     */
385
    private boolean isWrappingResourceMethodParameter(ASTVariableId var, ASTExecutableDeclaration method) {
386
        ASTVariableAccess wrappedVarName = getWrappedVariableName(var);
1✔
387
        ASTFormalParameters methodParams = method.getFormalParameters();
1✔
388
        if (wrappedVarName != null) {
1✔
389
            // get the parent node where it's used (no casts)
390
            Node parentUse = wrappedVarName.getParent();
1✔
391
            if (parentUse instanceof ASTCastExpression) {
1✔
392
                parentUse = parentUse.getParent();
1✔
393
            }
394
            return isReferencingMethodParameter(wrappedVarName, methodParams,
1!
395
                parentUse instanceof ASTVariableDeclarator || parentUse instanceof ASTAssignmentExpression);
396
        } else if (var.getParent() instanceof ASTTypePattern && var.getIndexInParent() == 2) {
1!
397
            JavaNode check = null;
1✔
398
            if (var.getParent().getParent().getParent() instanceof ASTInfixExpression) {
1✔
399
                check = var.getParent().getParent().getParent().getChild(0);
1✔
400
            }
401
            if (var.getParent().getParent() instanceof ASTSwitchLabel) {
1✔
402
                ASTSwitchLike sw = var.ancestors(ASTSwitchLike.class).firstOrThrow();
1✔
403
                check = sw.getTestedExpression();
1✔
404
            }
405
            if (check instanceof ASTVariableAccess) {
1!
406
                return isReferencingMethodParameter((ASTVariableAccess) check, methodParams, false);
1✔
407
            }
408
        }
409
        return false;
1✔
410
    }
411

412
    private boolean isReferencingMethodParameter(ASTVariableAccess wrappedVarName,
413
            ASTFormalParameters methodParams, boolean isAssignment) {
414
        for (ASTFormalParameter param: methodParams) {
1✔
415
            if ((isAssignment || isResourceTypeOrSubtype(param))
1✔
416
                    && JavaAstUtils.isReferenceToVar(wrappedVarName, param.getVarId().getSymbol())) {
1!
417
                return true;
1✔
418
            }
419
        }
1✔
420
        return false;
1✔
421
    }
422

423
    private ASTVariableAccess getWrappedVariableName(ASTVariableId var) {
424
        ASTExpression initializer = var.getInitializer();
1✔
425
        if (initializer != null) {
1✔
426
            return var.getInitializer().descendantsOrSelf().filterIs(ASTVariableAccess.class)
1✔
427
                    .filter(usage -> !(usage.getParent() instanceof ASTMethodCall)).first();
1✔
428
        }
429
        return null;
1✔
430
    }
431

432
    private boolean isResourceTypeOrSubtype(TypeNode refType) {
433
        @Nullable
434
        JTypeDeclSymbol symbol = refType.getTypeMirror().getSymbol();
1✔
435
        return symbol != null && !symbol.isUnresolved()
1✔
436
                ? isNodeInstanceOfResourceType(refType)
1✔
437
                : nodeHasReferenceToResourceType(refType);
1✔
438
    }
439

440
    private boolean isNodeInstanceOfResourceType(TypeNode refType) {
441
        for (String resType : types) {
1✔
442
            if (TypeTestUtil.isA(resType, refType)) {
1✔
443
                return true;
1✔
444
            }
445
        }
1✔
446
        return false;
1✔
447
    }
448

449
    private boolean nodeHasReferenceToResourceType(TypeNode refType) {
450
        @Nullable
451
        JTypeDeclSymbol symbol = refType.getTypeMirror().getSymbol();
1✔
452
        if (symbol != null) {
1✔
453
            String simpleTypeName = symbol.getSimpleName();
1✔
454
            return isResourceTypeName(simpleTypeName);
1✔
455
        }
456
        return false;
1✔
457
    }
458

459
    private boolean isResourceTypeName(String typeName) {
460
        String simpleTypeName = toSimpleType(typeName);
1✔
461
        return types.contains(typeName) || simpleTypes.contains(simpleTypeName);
1!
462
    }
463

464
    private boolean isResourceVariableUnclosed(ASTVariableId var) {
465
        return !isResourceVariableClosed(var);
1✔
466
    }
467

468
    private boolean isResourceVariableClosed(ASTVariableId var) {
469
        Node methodOfVar = getMethodOfNode(var);
1✔
470
        return hasTryStatementClosingResourceVariable(methodOfVar, var)
1✔
471
                || isReturnedByMethod(var, methodOfVar);
1✔
472
    }
473

474
    private Node getMethodOfNode(Node node) {
475
        Node parent = node.getParent();
1✔
476
        while (isNotMethod(parent)) {
1✔
477
            parent = parent.getParent();
1✔
478
        }
479
        return parent;
1✔
480
    }
481

482
    private boolean isNotMethod(Node node) {
483
        return !(node instanceof ASTBlock || node instanceof ASTConstructorDeclaration);
1!
484
    }
485

486
    private boolean hasTryStatementClosingResourceVariable(Node node, ASTVariableId var) {
487
        List<ASTTryStatement> tryStatements = node.descendants(ASTTryStatement.class).crossFindBoundaries().toList();
1✔
488
        for (ASTTryStatement tryStatement : tryStatements) {
1✔
489
            if (tryStatementClosesResourceVariable(tryStatement, var)) {
1✔
490
                return true;
1✔
491
            }
492
        }
1✔
493
        return false;
1✔
494
    }
495

496
    private boolean tryStatementClosesResourceVariable(ASTTryStatement tryStatement, ASTVariableId var) {
497
        if (tryStatement.getBeginLine() >= var.getBeginLine() && noneCriticalStatementsBetween(var, tryStatement)) {
1!
498
            if (isTryWithResourceSpecifyingVariable(tryStatement, var)) {
1✔
499
                return true;
1✔
500
            }
501
            if (hasFinallyClause(tryStatement)) {
1✔
502
                ASTBlock finallyBody = tryStatement.getFinallyClause().getBody();
1✔
503
                return blockClosesResourceVariable(finallyBody, var);
1✔
504
            }
505
        }
506
        return false;
1✔
507
    }
508

509
    private boolean noneCriticalStatementsBetween(ASTVariableId var, ASTTryStatement tryStatement) {
510
        return !anyCriticalStatementBetween(var, tryStatement);
1✔
511
    }
512

513
    private boolean anyCriticalStatementBetween(ASTVariableId var, ASTTryStatement tryStatement) {
514
        ASTStatement varStatement = var.ancestors(ASTStatement.class).first();
1✔
515
        if (isNotNullInitialized(var) && areStatementsOfSameBlock(varStatement, tryStatement)) {
1✔
516
            for (ASTStatement bsBetween : getBlockStatementsBetween(varStatement, tryStatement)) {
1✔
517
                if (isCriticalStatement(bsBetween)) {
1✔
518
                    return true;
1✔
519
                }
520
            }
1✔
521
        }
522
        return false;
1✔
523
    }
524

525
    private boolean isNotNullInitialized(ASTVariableId var) {
526
        return !hasNullInitializer(var);
1✔
527
    }
528

529
    private boolean hasNullInitializer(ASTVariableId var) {
530
        return var.getInitializer() instanceof ASTNullLiteral;
1✔
531
    }
532

533
    private boolean areStatementsOfSameBlock(ASTStatement bs0, ASTStatement bs1) {
534
        return bs0.getParent() == bs1.getParent();
1✔
535
    }
536

537
    private List<ASTStatement> getBlockStatementsBetween(ASTStatement top, ASTStatement bottom) {
538
        List<ASTStatement> blockStatements = top.getParent().children(ASTStatement.class).toList();
1✔
539
        int topIndex = blockStatements.indexOf(top);
1✔
540
        int bottomIndex = blockStatements.indexOf(bottom);
1✔
541
        return blockStatements.subList(topIndex + 1, bottomIndex);
1✔
542
    }
543

544
    private boolean isCriticalStatement(ASTStatement blockStatement) {
545
        boolean isVarDeclaration = blockStatement.descendantsOrSelf().filterIs(ASTLocalVariableDeclaration.class).nonEmpty();
1✔
546
        boolean isAssignmentOperator = blockStatement.descendantsOrSelf().filterIs(ASTAssignmentExpression.class).nonEmpty();
1✔
547
        return !isVarDeclaration && !isAssignmentOperator;
1✔
548
    }
549

550
    private boolean isTryWithResourceSpecifyingVariable(ASTTryStatement tryStatement, ASTVariableId varId) {
551
        return tryStatement.isTryWithResources() && isVariableSpecifiedInTryWithResource(varId, tryStatement);
1!
552
    }
553

554
    private boolean isVariableNotSpecifiedInTryWithResource(ASTVariableId varId) {
555
        @Nullable
556
        ASTTryStatement tryStatement = varId.ancestors(ASTTryStatement.class)
1✔
557
            .filter(ASTTryStatement::isTryWithResources)
1✔
558
            .first();
1✔
559
        return tryStatement == null || !isVariableSpecifiedInTryWithResource(varId, tryStatement);
1✔
560
    }
561

562
    private boolean isDefaultFileSystem(ASTVariableId varId) {
563
        @Nullable
564
        ASTExpression initializer = varId.getInitializer();
1✔
565
        return FILESYSTEMS_GET_DEFAULT.matchesCall(initializer);
1✔
566
    }
567

568
    private boolean isInitializedFromManagedResourceMethod(ASTVariableId varId) {
569
        @Nullable
570
        ASTExpression initializer = varId.getInitializer();
1✔
571
        if (initializer == null) {
1✔
572
            return false;
1✔
573
        }
574

575
        // Check if the initializer is directly a managed resource method call
576
        if (matchesManagedResourceMethod(initializer)) {
1✔
577
            return true;
1✔
578
        }
579

580
        // Check if the initializer is a constructor wrapping a managed resource method call
581
        // e.g., new ObjectOutputStream(response.getOutputStream())
582
        if (initializer instanceof ASTConstructorCall) {
1✔
583
            ASTConstructorCall constructorCall = (ASTConstructorCall) initializer;
1✔
584
            ASTArgumentList args = constructorCall.getArguments();
1✔
585
            if (args != null && args.size() > 0) {
1!
586
                ASTExpression firstArg = args.get(0);
1✔
587
                if (matchesManagedResourceMethod(firstArg)) {
1✔
588
                    return true;
1✔
589
                }
590
            }
591
        }
592
        return false;
1✔
593
    }
594

595
    private boolean matchesManagedResourceMethod(ASTExpression expr) {
596
        for (InvocationMatcher pattern : managedResourceMethodPatterns) {
1✔
597
            if (pattern.matchesCall(expr)) {
1✔
598
                return true;
1✔
599
            }
600
        }
1✔
601
        return false;
1✔
602
    }
603

604
    private boolean isVariableSpecifiedInTryWithResource(ASTVariableId varId, ASTTryStatement tryWithResource) {
605
        // skip own resources - these are definitively closed
606
        if (tryWithResource.getResources().descendants(ASTVariableId.class).toList().contains(varId)) {
1✔
607
            return true;
1✔
608
        }
609

610
        List<ASTVariableAccess> usedVars = getResourcesSpecifiedInTryWith(tryWithResource);
1✔
611
        for (ASTVariableAccess res : usedVars) {
1✔
612
            if (JavaAstUtils.isReferenceToVar(res, varId.getSymbol())) {
1✔
613
                return true;
1✔
614
            }
615
        }
1✔
616
        return false;
1✔
617
    }
618

619
    private List<ASTVariableAccess> getResourcesSpecifiedInTryWith(ASTTryStatement tryWithResource) {
620
        return tryWithResource.getResources().descendantsOrSelf().filterIs(ASTVariableAccess.class).toList();
1✔
621
    }
622

623
    private boolean hasFinallyClause(ASTTryStatement tryStatement) {
624
        return tryStatement.getFinallyClause() != null;
1✔
625
    }
626

627
    private boolean blockClosesResourceVariable(ASTBlock block, ASTVariableId variableToClose) {
628
        return hasNotConditionalCloseCallOnVariable(block, variableToClose)
1✔
629
                || hasMethodCallClosingResourceVariable(block, variableToClose);
1✔
630
    }
631

632
    private boolean hasNotConditionalCloseCallOnVariable(ASTBlock block, ASTVariableId variableToClose) {
633
        List<ASTMethodCall> methodCallsOnVariable = block.descendants(ASTMethodCall.class)
1✔
634
            .filter(call -> isMethodCallOnVariable(call, variableToClose))
1✔
635
            .toList();
1✔
636

637
        for (ASTMethodCall call : methodCallsOnVariable) {
1✔
638
            if (isCloseTargetMethodCall(call) && isNotConditional(block, call, variableToClose)) {
1✔
639
                return true;
1✔
640
            }
641
        }
1✔
642
        return false;
1✔
643
    }
644
    
645
    private boolean isMethodCallOnVariable(ASTExpression expr, ASTVariableId variable) {
646
        if (expr instanceof ASTMethodCall) {
1✔
647
            ASTMethodCall methodCall = (ASTMethodCall) expr;
1✔
648
            return JavaAstUtils.isReferenceToVar(methodCall.getQualifier(), variable.getSymbol());
1✔
649
        }
650
        return false;
1✔
651
    }
652

653
    /**
654
     * Checks, whether the given node is inside an if condition, and if so,
655
     * whether this is a null check for the given varName.
656
     *
657
     * @param enclosingBlock
658
     *            where to search for if statements
659
     * @param node
660
     *            the node, where the call for the close is done
661
     * @param var
662
     *            the variable, that is maybe null-checked
663
     * @return <code>true</code> if no if condition is involved or if the if
664
     *         condition is a null-check.
665
     */
666
    private boolean isNotConditional(ASTBlock enclosingBlock, Node node, ASTVariableId var) {
667
        ASTIfStatement ifStatement = findIfStatement(enclosingBlock, node);
1✔
668
        if (ifStatement != null) {
1✔
669
            // find expressions like: varName != null or null != varName
670
            if (ifStatement.getCondition() instanceof ASTInfixExpression) {
1✔
671
                ASTInfixExpression equalityExpr = (ASTInfixExpression) ifStatement.getCondition();
1✔
672
                if (BinaryOp.NE == equalityExpr.getOperator()) {
1!
673
                    ASTExpression left = equalityExpr.getLeftOperand();
1✔
674
                    ASTExpression right = equalityExpr.getRightOperand();
1✔
675

676
                    if (JavaAstUtils.isReferenceToVar(left, var.getSymbol()) && isNullLiteral(right)
1!
677
                            || JavaAstUtils.isReferenceToVar(right, var.getSymbol()) && isNullLiteral(left)) {
×
678
                        return true;
1✔
679
                    }
680
                }
681
            }
682

683
            // find method call Objects.nonNull(varName)
684
            return isObjectsNonNull(ifStatement.getCondition(), var);
1✔
685
        }
686
        return true;
1✔
687
    }
688

689
    private boolean isObjectsNonNull(ASTExpression expression, ASTVariableId var) {
690
        if (OBJECTS_NON_NULL.matchesCall(expression)) {
1✔
691
            ASTMethodCall methodCall = (ASTMethodCall) expression;
1✔
692
            return JavaAstUtils.isReferenceToVar(methodCall.getArguments().get(0), var.getSymbol());
1✔
693
        }
694

695
        return false;
1✔
696
    }
697

698
    private boolean isNullLiteral(JavaNode node) {
699
        return node instanceof ASTNullLiteral;
1✔
700
    }
701

702
    private ASTIfStatement findIfStatement(ASTBlock enclosingBlock, Node node) {
703
        ASTIfStatement ifStatement = node.ancestors(ASTIfStatement.class).first();
1✔
704
        List<ASTIfStatement> allIfStatements = enclosingBlock.descendants(ASTIfStatement.class).toList();
1✔
705
        if (ifStatement != null && allIfStatements.contains(ifStatement)) {
1!
706
            return ifStatement;
1✔
707
        }
708
        return null;
1✔
709
    }
710

711
    private boolean hasMethodCallClosingResourceVariable(ASTBlock block, ASTVariableId variableToClose) {
712
        List<ASTMethodCall> methodCalls = block.descendants(ASTMethodCall.class).crossFindBoundaries().toList();
1✔
713
        for (ASTMethodCall call : methodCalls) {
1✔
714
            if (isMethodCallClosingResourceVariable(call, variableToClose)) {
1✔
715
                return true;
1✔
716
            }
717
        }
1✔
718
        return false;
1✔
719
    }
720

721
    private boolean isMethodCallClosingResourceVariable(ASTExpression expr, ASTVariableId variableToClose) {
722
        if (!(expr instanceof ASTMethodCall)) {
1✔
723
            return false;
1✔
724
        }
725
        ASTMethodCall call = (ASTMethodCall) expr;
1✔
726
        return (isCloseTargetMethodCall(call) || hasChainedCloseTargetMethodCall(call))
1!
727
                && variableIsPassedToMethod(variableToClose, call);
1✔
728
    }
729

730
    private boolean isCloseTargetMethodCall(ASTMethodCall methodCall) {
731
        String fullName = methodCall.getMethodName();
1✔
732
        if (methodCall.getQualifier() instanceof ASTTypeExpression) {
1✔
733
            fullName = methodCall.getQualifier().getText() + "." + fullName;
1✔
734
        }
735
        return closeTargets.contains(fullName);
1✔
736
    }
737

738
    private boolean hasChainedCloseTargetMethodCall(ASTMethodCall start) {
739
        ASTExpression walker = start;
1✔
740
        while (walker instanceof ASTMethodCall) {
1✔
741
            ASTMethodCall methodCall = (ASTMethodCall) walker;
1✔
742
            if (isCloseTargetMethodCall(methodCall)) {
1!
743
                return true;
×
744
            }
745
            walker = methodCall.getQualifier();
1✔
746
        }
1✔
747
        return false;
1✔
748
    }
749

750
    private boolean variableIsPassedToMethod(ASTVariableId varName, ASTMethodCall methodCall) {
751
        List<ASTNamedReferenceExpr> usedRefs = methodCall.getArguments().descendants(ASTNamedReferenceExpr.class).toList();
1✔
752
        for (ASTNamedReferenceExpr ref : usedRefs) {
1✔
753
            if (varName.getSymbol().equals(ref.getReferencedSym())) {
1✔
754
                return true;
1✔
755
            }
756
        }
1✔
757
        return false;
1✔
758
    }
759

760
    private boolean isReturnedByMethod(ASTVariableId variable, Node method) {
761
        return method
1✔
762
                .descendants(ASTReturnStatement.class).crossFindBoundaries()
1✔
763
                .descendants(ASTVariableAccess.class)
1✔
764
                .filter(access -> !(access.getParent() instanceof ASTMethodCall))
1✔
765
                .filter(access -> JavaAstUtils.isReferenceToVar(access, variable.getSymbol()))
1✔
766
                .nonEmpty();
1✔
767
    }
768

769
    private void addCloseResourceViolation(ASTVariableId id, TypeNode type, Object data) {
770
        String resTypeName = getResourceTypeName(id, type);
1✔
771
        asCtx(data).addViolation(id, resTypeName);
1✔
772
    }
1✔
773

774
    private String getResourceTypeName(ASTVariableId varId, TypeNode type) {
775
        if (type == null) {
1✔
776
            final JTypeMirror typeMirror = varId.getTypeMirror();
1✔
777
            return typeMirror.getSymbol() != null ? typeMirror.getSymbol().getSimpleName() : typeMirror.toString();
1!
778
        }
779

780
        if (type instanceof ASTType) {
1✔
781
            return PrettyPrintingUtil.prettyPrintType((ASTType) type);
1✔
782
        }
783
        @Nullable
784
        JTypeDeclSymbol symbol = type.getTypeMirror().getSymbol();
1✔
785
        if (symbol != null) {
1!
786
            return symbol.getSimpleName();
1✔
787
        }
788
        @Nullable
789
        ASTLocalVariableDeclaration localVarDecl = varId.ancestors(ASTLocalVariableDeclaration.class).first();
×
790
        if (localVarDecl != null && localVarDecl.getTypeNode() != null) {
×
791
            return PrettyPrintingUtil.prettyPrintType(localVarDecl.getTypeNode());
×
792
        }
793
        return varId.getName();
×
794
    }
795

796
    @Override
797
    public Object visit(ASTMethodCall node, Object data) {
798
        if (!getProperty(DETECT_CLOSE_NOT_IN_FINALLY)) {
1✔
799
            return super.visit(node, data);
1✔
800
        }
801

802
        if (isCloseTargetMethodCall(node) && node.getQualifier() instanceof ASTVariableAccess) {
1!
803
            ASTVariableAccess closedVar = (ASTVariableAccess) node.getQualifier();
1✔
804
            if (isNotInFinallyBlock(closedVar) && !reportedVarNames.contains(closedVar.getName())) {
1✔
805
                asCtx(data).addViolationWithMessage(closedVar, CLOSE_IN_FINALLY_BLOCK_MESSAGE,
1✔
806
                                                    closedVar.getName());
1✔
807
            }
808
        }
809

810
        return super.visit(node, data);
1✔
811
    }
812

813
    private boolean isNotInFinallyBlock(ASTVariableAccess closedVar) {
814
        return closedVar.ancestors(ASTFinallyClause.class).isEmpty();
1✔
815
    }
816

817
    private ASTExpressionStatement getFirstReassigningStatementBeforeBeingClosed(ASTVariableId variable, ASTExecutableDeclaration methodOrConstructor) {
818
        List<ASTExpressionStatement> statements = methodOrConstructor.descendants(ASTExpressionStatement.class).toList();
1✔
819
        boolean variableClosed = false;
1✔
820
        boolean isInitialized = !hasNullInitializer(variable);
1✔
821
        ASTExpression initializingExpression = initializerExpressionOf(variable);
1✔
822
        for (ASTExpressionStatement statement : statements) {
1✔
823
            if (isClosingVariableStatement(statement, variable)) {
1✔
824
                variableClosed = true;
1✔
825
            }
826

827
            if (isAssignmentForVariable(statement, variable)) {
1✔
828
                ASTAssignmentExpression assignment = (ASTAssignmentExpression) statement.getFirstChild();
1✔
829
                if (isInitialized && !variableClosed) {
1✔
830
                    if (initializingExpression != null && !inSameIfBlock(statement, initializingExpression)
1!
831
                            && notInNullCheckIf(statement, variable)
1✔
832
                            && isNotSelfAssignment(assignment)) {
1✔
833
                        return statement;
1✔
834
                    }
835
                }
836

837
                if (variableClosed) {
1✔
838
                    variableClosed = false;
1✔
839
                } 
840
                if (!isInitialized) {
1✔
841
                    isInitialized = true;
1✔
842
                    initializingExpression = statement.getExpr();
1✔
843
                }
844
            }
845
        }
1✔
846
        return null;
1✔
847
    }
848

849
    private boolean isNotSelfAssignment(ASTAssignmentExpression assignment) {
850
        return assignment.getRightOperand().descendantsOrSelf().filterIs(ASTVariableAccess.class).filter(access -> {
1✔
851
            return JavaAstUtils.isReferenceToSameVar(access, assignment.getLeftOperand());
1✔
852
        }).isEmpty();
1✔
853
    }
854

855
    private boolean notInNullCheckIf(ASTExpressionStatement statement, ASTVariableId variable) {
856
        Node grandparent = statement.ancestors().get(1);
1✔
857
        if (grandparent instanceof ASTIfStatement) {
1✔
858
            ASTIfStatement ifStatement = (ASTIfStatement) grandparent;
1✔
859
            if (JavaRuleUtil.isNullCheck(ifStatement.getCondition(), variable.getSymbol())) {
1!
860
                return false;
1✔
861
            }
862
        }
863
        return true;
1✔
864
    }
865

866
    private boolean inSameIfBlock(ASTExpressionStatement statement1, ASTExpression statement2) {
867
        List<ASTIfStatement> parents1 = statement1.ancestors(ASTIfStatement.class).toList();
1✔
868
        List<ASTIfStatement> parents2 = statement2.ancestors(ASTIfStatement.class).toList();
1✔
869
        parents1.retainAll(parents2);
1✔
870
        return !parents1.isEmpty();
1!
871
    }
872

873
    private boolean isClosingVariableStatement(ASTExpressionStatement statement, ASTVariableId variable) {
874
        return isMethodCallClosingResourceVariable(statement.getExpr(), variable)
1✔
875
                || isMethodCallOnVariable(statement.getExpr(), variable);
1✔
876
    }
877

878
    private boolean isAssignmentForVariable(ASTExpressionStatement statement, ASTVariableId variable) {
879
        if (statement == null || variable == null || !(statement.getExpr() instanceof ASTAssignmentExpression)) {
1!
880
            return false;
1✔
881
        }
882

883
        ASTAssignmentExpression assignment = (ASTAssignmentExpression) statement.getExpr();
1✔
884
        return JavaAstUtils.isReferenceToVar(assignment.getLeftOperand(), variable.getSymbol());
1✔
885
    }
886
}
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