使用检查样式防止准备好的语句泄漏
Posted
技术标签:
【中文标题】使用检查样式防止准备好的语句泄漏【英文标题】: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 关闭 PreparedStatement
或 ResultSet
的情况。这可能吗?如果可以,我该怎么做?
【问题讨论】:
【参考方案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;
【讨论】:
以上是关于使用检查样式防止准备好的语句泄漏的主要内容,如果未能解决你的问题,请参考以下文章
在 MySQL 中使用准备好的语句可以防止 SQL 注入攻击吗?