package jmemorize.checks; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.puppycrawl.tools.checkstyle.api.*; /* * All catch clauses must contain at least one of * - throw * - method call whose regexp matches "logger.severe"() * - method call whose regexp matches "logThrowable"() * - assert false; for exceptions that should not happen * - the last line is a fallthrough comment * the second case also includes Main.getLogger.severe( ... ) * if you really need a non logged exception, then what? * * This does not attempt to deal with nested catches, instead * treating a nested catch as an error. * * The sections of this code that deal with the fallthrough comment are * based on the checkstyle FallThrough check. */ public class ExceptionLoggedCheck extends Check { private int catchDepth; // state info about the first level catch private boolean hasThrow; private boolean hasLogCall; private boolean hasAssert; private int catchLineNo; // catch start line private int lastRCurly; // catch end line /** Relief pattern to allow fall throught to the next case branch. */ private String mReliefPattern = "fallthru|falls? ?through"; /** Relief regexp. */ private Pattern mRegExp; public int[] getDefaultTokens() { return new int[]{TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_THROW, TokenTypes.LITERAL_ASSERT, TokenTypes.METHOD_CALL, TokenTypes.RCURLY, }; } private void clearCatchState() { hasThrow = false; hasLogCall = false; hasAssert = false; } public void beginTree(DetailAST aRootAST) { catchDepth = 0; mRegExp = Utils.getPattern(mReliefPattern); } public void finishTree(DetailAST arg0) { assert catchDepth == 0; } public void visitToken(DetailAST ast) { if (ast.getType() == TokenTypes.LITERAL_CATCH) { // found the catch, init the state data and check for the fallthrough catchDepth++; assert catchDepth > 0; if (catchDepth == 1) { clearCatchState(); catchLineNo = ast.getLineNo(); } else { log(ast.getLineNo(), "Dubious coding: Nested Catch clauses"); } } else if (ast.getType() == TokenTypes.RCURLY && catchDepth == 1) { lastRCurly = ast.getLineNo(); } else if (ast.getType() == TokenTypes.LITERAL_THROW && catchDepth == 1) { hasThrow = true; } else if (ast.getType() == TokenTypes.LITERAL_ASSERT && catchDepth == 1) { DetailAST childAst = ast.findFirstToken(TokenTypes.EXPR); if (childAst != null || childAst.getNumberOfChildren() == 1) { DetailAST grandchildAst = childAst.findFirstToken(TokenTypes.LITERAL_FALSE); if (grandchildAst != null) { hasAssert = true; } } } else if (ast.getType() == TokenTypes.METHOD_CALL && catchDepth == 1) { // look for a call whose name ends in logger.severe String callText = FullIdent.createFullIdentBelow(ast).getText(); //log(ast.getLineNo(), "catch method call " + callText); if ( callText.toLowerCase().endsWith("logger.severe") || callText.endsWith("logThrowable") ) { hasLogCall = true; } } } /** * This function borrowed directly from checkstyle:FallThrough * Does a regular expression match on the given line and checks that a * possible match is within a comment. * @param aPattern The regular expression pattern to use. * @param aLine The line of test to do the match on. * @param aLineNo The line number in the file. * @return True if a match was found inside a comment. */ private boolean commentMatch(Pattern aPattern, String aLine, int aLineNo) { final Matcher matcher = aPattern.matcher(aLine); final boolean hit = matcher.find(); if (hit) { final int startMatch = matcher.start(); // -1 because it returns the char position beyond the match final int endMatch = matcher.end() - 1; return getFileContents().hasIntersectionWithComment(aLineNo, startMatch, aLineNo, endMatch); } return false; } public void leaveToken(DetailAST ast) { if (ast.getType() == TokenTypes.LITERAL_CATCH) { //log(catchLineNo, "End of Catch clause found at lines " + catchLineNo + // "-" + lastRCurly); catchDepth--; assert catchDepth >= 0; if (catchDepth == 0 && !hasThrow && !hasLogCall && !hasAssert) { // check for a fall through comment. Inspired by and based on the FallThrough check String[] lines = getLines(); String line = lines[lastRCurly - 2]; //log(ast.getLineNo(), "catch clause last line:" + line); if (!commentMatch(mRegExp, line, lastRCurly -1)) { log(catchLineNo, "Catch clause with no throw, log, assert false, or fallthrough."); } } } } }