View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.pmd.rules;
6   
7   import java.util.Set;
8   import net.sourceforge.pmd.lang.ast.Node;
9   import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
10  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
11  import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
12  import net.sourceforge.pmd.lang.java.ast.ASTResource;
13  import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
14  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
15  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
16  import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
17  
18  /**
19   * Rule to flag {@code AutoCloseable} expressions that are created inline and
20   * then consumed by another expression instead of a resource boundary.
21   *
22   * @since 0.27.7
23   */
24  public final class CloseInlineResourceRule extends AbstractJavaRulechainRule {
25  
26      /**
27       * Exact resource types allowed by PMD's CloseResource rule.
28       */
29      private static final Set<String> ALLOWED = Set.of(
30          "java.io.ByteArrayOutputStream",
31          "java.io.ByteArrayInputStream",
32          "java.io.StringWriter",
33          "java.io.CharArrayWriter",
34          "java.util.stream.Stream",
35          "java.util.stream.IntStream",
36          "java.util.stream.LongStream",
37          "java.util.stream.DoubleStream"
38      );
39  
40      /**
41       * Default constructor.
42       */
43      public CloseInlineResourceRule() {
44          super(ASTConstructorCall.class, ASTMethodCall.class);
45      }
46  
47      @Override
48      public Object visit(final ASTConstructorCall call, final Object data) {
49          if (CloseInlineResourceRule.leaksInline(call)) {
50              this.asCtx(data).addViolation(call);
51          }
52          return data;
53      }
54  
55      @Override
56      public Object visit(final ASTMethodCall call, final Object data) {
57          if (CloseInlineResourceRule.leaksInline(call)
58              && CloseInlineResourceRule.hasCloseableArgument(call)) {
59              this.asCtx(data).addViolation(call);
60          }
61          return data;
62      }
63  
64      private static boolean leaksInline(final ASTExpression expr) {
65          boolean result = false;
66          if (CloseInlineResourceRule.closeable(expr)
67              && !CloseInlineResourceRule.allowed(expr)) {
68              result = CloseInlineResourceRule.unmanagedInline(expr);
69          }
70          return result;
71      }
72  
73      private static boolean closeable(final ASTExpression expr) {
74          return TypeTestUtil.isA(AutoCloseable.class, expr);
75      }
76  
77      private static boolean allowed(final ASTExpression expr) {
78          return CloseInlineResourceRule.ALLOWED.stream()
79              .anyMatch(type -> TypeTestUtil.isExactlyA(type, expr));
80      }
81  
82      private static boolean unmanagedInline(final ASTExpression expr) {
83          return CloseInlineResourceRule.inline(expr)
84              && !CloseInlineResourceRule.closedDirectly(expr)
85              && !CloseInlineResourceRule.returnedDirectly(expr);
86      }
87  
88      private static boolean hasCloseableArgument(final ASTMethodCall call) {
89          return call.getArguments().descendants(ASTExpression.class)
90              .any(CloseInlineResourceRule::closeable);
91      }
92  
93      private static boolean inline(final ASTExpression expr) {
94          return expr.ancestors(ASTResource.class).isEmpty()
95              && expr.ancestors(ASTVariableDeclarator.class).isEmpty();
96      }
97  
98      private static boolean closedDirectly(final ASTExpression expr) {
99          boolean found = false;
100         final Node parent = expr.getParent();
101         if (parent instanceof ASTMethodCall call) {
102             found = "close".equals(call.getMethodName())
103                 && expr.equals(call.getQualifier());
104         }
105         return found;
106     }
107 
108     private static boolean returnedDirectly(final ASTExpression expr) {
109         return expr.getParent() instanceof ASTReturnStatement;
110     }
111 }