Предотвращение утечки подготовленного заявления с помощью контрольных стилей - PullRequest
2 голосов
/ 21 июля 2011

Допустим, у меня есть следующий код:

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
}

, и я хотел поймать сценарий, в котором я забыл закрыть PreparedStatement или ResultSet с помощью Checkstyles.Возможно ли это, и если да, то как мне это сделать?

Ответы [ 2 ]

6 голосов
/ 21 июля 2011

PMD и Findbugs оба имеют предупреждения для PreparedStatements (и ResultSets и Connections).Я бы посоветовал использовать их для этого типа предупреждения, поскольку CheckStyle больше связан со стилем кода, чем с обнаружением таких ошибок потока данных, как этот.

1 голос
/ 13 сентября 2012

Мы создали специальную проверку 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;
    }

}
...