使用 Checkstyle 防止预准备语句泄漏

发布于 2024-11-26 03:44:21 字数 606 浏览 2 评论 0原文

假设我有以下代码:

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 的场景。这可能吗?如果可以,我该怎么做?

Lets say I have the following code :

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
}

and I wished to catch the scenario where I forget to close the PreparedStatement or the ResultSet using Checkstyles. Is this possible, and if so, how would I go about it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

箜明 2024-12-03 03:44:21

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

PMD and Findbugs both have warnings for PreparedStatements (and ResultSets and Connections). I'd suggest using them for this type of warning, since CheckStyle has more to do with code style than finding data-flow bugs such as this.

落日海湾 2024-12-03 03:44:21

我们创建了一个自定义的 Checkstyle 检查来防止这些语句泄漏。代码在下面。
checkstyle 的优点在于,您可以使用 API 公开 Java AST。
我们已经创建了数十个自定义检查。一旦掌握了要点,创建新的检查就很容易了。
我们还创建了一个 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;
    }

}

We created a custom Checkstyle Check that prevents these Statement leaks. The code is down below.
The beauty of checkstyle is that you can customize your checks using an API that exposes the Java AST.
We have created tens of custom checks. Once you get the gist of it, creating new checks is easy.
We have also created a Subversion pre-commit hook that runs the check and prevents code with violations to get into the repository. The developer gets a clear message (see 'log' call below) indicating the problem and the line.

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).");
                }
            }
        }
    }
}

This custom check extends an abstract class that contains two utility methods shown below.

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;
    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文