【问题标题】:Preventing Prepared Statement Leaks using Checkstyles使用检查样式防止准备好的语句泄漏
【发布时间】:2011-07-21 16:38:16
【问题描述】:

假设我有以下代码:

PreparedStatement ps = null;
ResultSet rs = null;
try {
  ps = conn.createStatement(myQueryString);
  rs = ps.executeQuery();
  // process the results...
} catch (java.sql.SQLException e) {
  log.error("an error!", e);
  throw new Exception("I'm sorry. Your query did not work.");
} finally {
  ps.close();   // if we forgot to do this we leak
  rs.close();   // if we forgot to do this we leak
}

我希望捕捉到我忘记使用 Checkstyles 关闭 PreparedStatementResultSet 的情况。这可能吗?如果可以,我该怎么做?

【问题讨论】:

    标签: java jdbc checkstyle prepared-statement


    【解决方案1】:

    PMD 和 Findbugs 都有针对 PreparedStatements(以及 ResultSets 和 Connections)的警告。我建议将它们用于此类警告,因为 CheckStyle 与代码样式有关,而不是查找诸如此类的数据流错误。

    【讨论】:

    • 但是 checkstyles 会为您的程序构建 AST,所以您不能使用自定义检查来做到这一点吗?
    • 另外值得注意的是,我是在自动化中运行它,而不是在我的 IDE 中。
    • 当然,如果您想花时间编写自定义检查,而不是仅仅重新使用已经存在的东西。我更喜欢使用 Findbugs/PMD/Checkstyle 相互重叠,因为每个都有一些其他人没有的检查 - 而不是只选择一个。 Findbugs 和 PMD 可以像 Checkstyle 一样运行和生成报告。
    • 能否提供 PMD 和 FindBugs 中的规则参考?
    • 我会推荐使用 Findbugs。要检测这种类型的数据流错误,需要有类型信息。 Checkstyle 检查无法访问此信息,而 Findbugs 检测器则可以。任何使用 Checkstyle 进行此检查的尝试都是“hack”。
    【解决方案2】:

    我们创建了一个自定义 Checkstyle Check 来防止这些语句泄漏。代码在下面。 checkstyle 的美妙之处在于您可以使用公开 Java AST 的API 自定义您的检查。 我们创建了数十种自定义检查。一旦掌握了要点,创建新支票就很容易了。 我们还创建了一个 Subversion 预提交挂钩,它运行检查并防止违规代码进入存储库。开发人员会收到一条明确的消息(请参阅下面的“日志”调用),指示问题和线路。

    import java.util.List;
    
    import com.puppycrawl.tools.checkstyle.api.DetailAST;
    import com.puppycrawl.tools.checkstyle.api.TokenTypes;
    
    /**
     * Code blocks that invoke Connection.prepareStatement(...), Connection.prepareCall(...) or
     * Connection.createStatement(...) must have a finally block
     * in which there is a call to ContextObject.closeStatement(Statement).
     */
    public class CheckCloseStatement extends CheckTcu {
    
        @Override
        public int[] getDefaultTokens() {
            return new int[] {TokenTypes.ASSIGN};
        }
    
        @Override
        public void visitToken(DetailAST aAST) {
            DetailAST literalTry;
            DetailAST literalFinally;
            DetailAST paramCloseStmt;
            List<DetailAST> idents;
            List<DetailAST> identsInFinally;
            String stmtVarName;
    
            idents = findAllAstsOfType(aAST, TokenTypes.IDENT);
            for (DetailAST ident : idents) {
    
                if ((ident.getText().equals("prepareStatement") || ident.getText().equals("createStatement") ||
                        ident.getText().equals("prepareCall")) && ident.getParent().getType() == TokenTypes.DOT) {
                    // a Statement is created in this assignment statement
    
                    boolean violationFound = true;
    
                    // look for the surrounding try statement
                    literalTry = ident;
                    do {
                        literalTry = literalTry.getParent();
                    } while (literalTry != null && literalTry.getType() != TokenTypes.LITERAL_TRY);
    
                    if (literalTry != null) {
                        // good, the Statement creating assignment is within a try block
                        // now look for the corresponding finally block
                        literalFinally = literalTry.findFirstToken(TokenTypes.LITERAL_FINALLY);
    
                        if (literalFinally != null) {
                            // good, there is a finally block
                            identsInFinally = findAllAstsOfType(literalFinally, TokenTypes.IDENT);
                            for (DetailAST identInFinally : identsInFinally) {
                                if (identInFinally.getText().equals("closeStatement")) {
                                    // good, there's a call to my closeStatement method
                                    paramCloseStmt =
                                            findFirstAstOfType(identInFinally.getParent().getNextSibling(), TokenTypes.IDENT);
                                    stmtVarName = findFirstAstOfType(aAST, TokenTypes.IDENT).getText();
                                    if (stmtVarName.equals(paramCloseStmt.getText())) {
                                        // great, closeStatement closes the Statement variable originally assigned
                                        violationFound = false;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    // Exception: this rule does not apply to Xyz and its subclasses (which have
                    // the same name Xyz followed by a suffix)
                    if (violationFound) {
                        DetailAST classDef = aAST;
                        do {
                            classDef = classDef.getParent();
                        } while (classDef != null && classDef.getType() != TokenTypes.CLASS_DEF);
                        if (classDef != null) {
                            String className = classDef.findFirstToken(TokenTypes.IDENT).getText();
                            if (className.startsWith("Xyz")) {
                                violationFound = false;
                            }
                        }
                    }
                    if (violationFound) {
                        log(ident.getLineNo(),
                                "Code blocks that call Connection.prepareStatement(...) or Connection.prepareCall(...) " +
                                        "need a finally block where you should call ContextObject.closeStatement(Statement).");
                    }
                }
            }
        }
    }
    

    此自定义检查扩展了一个抽象类,其中包含如下所示的两个实用方法。

    import java.util.ArrayList;
    import java.util.List;
    
    import com.puppycrawl.tools.checkstyle.api.Check;
    import com.puppycrawl.tools.checkstyle.api.DetailAST;
    
    /**
     * Utility methods used in custom checks. 
     */
    public abstract class CheckTcu extends Check {
    
        /**
         * Recursively traverse an expression tree and return all ASTs matching a specific token type.
         * 
         * @return list of DetailAST objects found; returns empty List if none is found.
         */
        protected List<DetailAST> findAllAstsOfType(DetailAST parent, int type) {
            List<DetailAST> children = new ArrayList<DetailAST>();
    
            DetailAST child = parent.getFirstChild();
            while (child != null) {
                if (child.getType() == type) {
                    children.add(child);
                } else {
                    children.addAll(findAllAstsOfType(child, type));
                }
                child = child.getNextSibling();
            }
            return children;
        }
    
        /**
         * Recursively traverse an expression tree and return the first AST matching a specific token type.
         * 
         * @return first DetailAST found or null if no AST of the given type is found
         */
        protected DetailAST findFirstAstOfType(DetailAST parent, int type) {
            DetailAST firstAst = null;
    
            DetailAST child = parent.getFirstChild();
            while (child != null) {
                if (child.getType() == type) {
                    firstAst = child;
                    break;
                }
                DetailAST grandChild = findFirstAstOfType(child, type);
                if (grandChild != null) {
                    firstAst = grandChild;
                    break;
                }
                child = child.getNextSibling();
            }
            return firstAst;
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2015-07-19
      • 1970-01-01
      • 2017-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-12
      • 2012-06-28
      相关资源
      最近更新 更多