使用Checkstyles防止预处理语句泄漏



假设我有以下代码:

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

PMD和Findbugs都对preparedstatement(以及ResultSets和Connections)有警告。我建议对这种类型的警告使用它们,因为CheckStyle更多的是与代码风格有关,而不是寻找像这样的数据流错误。

我们创建了一个自定义Checkstyle检查来防止这些语句泄漏。代码在下面。checkstyle的美妙之处在于,您可以使用公开Java AST的API自定义检查。我们已经创建了几十种海关支票。一旦你掌握了要点,创建新的支票就很容易了。我们还创建了一个Subversion预提交钩子,它运行检查并防止违规代码进入存储库。开发人员将获得一条明确的消息(参见下面的"log"调用),指出问题和行。

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

最新更新