//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.github.sevntu.checkstyle.checks.coding; import java.util.LinkedList; import java.util.List; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; /** * <p>This check prevents new exception throwing inside try/catch * blocks without providing current exception cause. * New exception should propagate up to a higher-level handler with exact * cause to provide a full stack trace for the problem.</p> * <p> * Rationale: When handling exceptions using try/catch blocks junior developers * may lose the original/cause exception object and information associated * with it. * </p> * Examples: * <br> <br> * 1. Cause exception will be lost because current catch block * contains another exception throwing. * <pre> * public void foo() { * RuntimeException r; * catch (java.lang.Exception e) { * //your code * throw r; * } * }</pre> * 2. Cause exception will be lost because current catch block * doesn`t contains another exception throwing. * <pre> * catch (IllegalStateException e) { * //your code * throw new RuntimeException(); * } * catch (IllegalStateException e) { * //your code * throw new RuntimeException("Runtime Exception!"); * } * </pre> * @author <a href="mailto:Daniil.Yaroslavtsev@gmail.com"> Daniil * Yaroslavtsev</a> * @author <a href="mailto:IliaDubinin91@gmail.com">Ilja Dubinin</a> */ public class AvoidHidingCauseExceptionCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY = "avoid.hiding.cause.exception"; @Override public int[] getDefaultTokens() { return new int[] { TokenTypes.LITERAL_CATCH, }; } @Override public void visitToken(DetailAST detailAST) { final String originExcName = detailAST .findFirstToken(TokenTypes.PARAMETER_DEF).getLastChild() .getText(); final List<DetailAST> throwList = makeThrowList(detailAST); final List<String> wrapExcNames = new LinkedList<>(); wrapExcNames.add(originExcName); wrapExcNames.addAll(makeExceptionsList(detailAST, detailAST, originExcName)); for (DetailAST throwAST : throwList) { final List<DetailAST> throwParamNamesList = new LinkedList<>(); buildThrowParamNamesList(throwAST, throwParamNamesList); if (!isContainsCaughtExc(throwParamNamesList, wrapExcNames)) { log(throwAST, MSG_KEY, originExcName); } } } /** * Returns true when aThrowParamNamesList contains caught exception. * @param throwParamNamesList List of throw parameter names. * @param wrapExcNames List of caught exception names. * @return true when aThrowParamNamesList contains caught exception */ private static boolean isContainsCaughtExc(List<DetailAST> throwParamNamesList, List<String> wrapExcNames) { boolean result = false; for (DetailAST currentNode : throwParamNamesList) { if (currentNode.getParent().getType() != TokenTypes.DOT && wrapExcNames.contains(currentNode.getText())) { result = true; break; } } return result; } /** * Returns a List of<code>DetailAST</code> that contains the names of * parameters for current "throw" keyword. * @param startNode The start node for exception name searching. * @param paramNamesAST The list, that will be contain names of the * parameters * @return A null-safe list of tokens (<code>DetailAST</code>) contains the * thrown exception name if it was found or null otherwise. */ private List<DetailAST> buildThrowParamNamesList(DetailAST startNode, List<DetailAST> paramNamesAST) { for (DetailAST currentNode : getChildNodes(startNode)) { if (currentNode.getType() == TokenTypes.IDENT) { paramNamesAST.add(currentNode); } if (currentNode.getType() != TokenTypes.PARAMETER_DEF && currentNode.getType() != TokenTypes.LITERAL_TRY && currentNode.getNumberOfChildren() > 0) { buildThrowParamNamesList(currentNode, paramNamesAST); } } return paramNamesAST; } /** * Recursive method which searches for the <code>LITERAL_THROW</code> * DetailASTs all levels below on the current <code>aParentAST</code> node * without entering into nested try/catch blocks. * @param parentAST A start node for "throw" keyword <code>DetailASTs * </code> searching. * @return null-safe list of <code>LITERAL_THROW</code> literals */ private List<DetailAST> makeThrowList(DetailAST parentAST) { final List<DetailAST> throwList = new LinkedList<>(); for (DetailAST currentNode : getChildNodes(parentAST)) { if (currentNode.getType() == TokenTypes.LITERAL_THROW) { throwList.add(currentNode); } if (currentNode.getType() != TokenTypes.PARAMETER_DEF && currentNode.getType() != TokenTypes.LITERAL_THROW && currentNode.getType() != TokenTypes.LITERAL_TRY && currentNode.getNumberOfChildren() > 0) { throwList.addAll(makeThrowList(currentNode)); } } return throwList; } /** * Searches for all exceptions that wraps the original exception * object (only in current "catch" block). * @param currentCatchAST A LITERAL_CATCH node of the * current "catch" block. * @param parentAST Current parent node to start search. * @param currentExcName The name of exception handled by * current "catch" block. * @return List contains exceptions that wraps the original * exception object. */ private List<String> makeExceptionsList(DetailAST currentCatchAST, DetailAST parentAST, String currentExcName) { final List<String> wrapExcNames = new LinkedList<>(); for (DetailAST currentNode : getChildNodes(parentAST)) { if (currentNode.getType() == TokenTypes.IDENT && currentNode.getText().equals(currentExcName) && currentNode.getParent().getType() != TokenTypes.DOT) { DetailAST temp = currentNode; while (!temp.equals(currentCatchAST) && temp.getType() != TokenTypes.ASSIGN) { temp = temp.getParent(); } if (temp.getType() == TokenTypes.ASSIGN) { DetailAST convertedExc = null; if (temp.getParent().getType() == TokenTypes.VARIABLE_DEF) { convertedExc = temp.getParent().findFirstToken(TokenTypes.IDENT); } else { convertedExc = temp.findFirstToken(TokenTypes.IDENT); } if (convertedExc != null) { wrapExcNames.add(convertedExc.getText()); } } } if (currentNode.getType() != TokenTypes.PARAMETER_DEF && currentNode.getNumberOfChildren() > 0) { wrapExcNames.addAll(makeExceptionsList(currentCatchAST, currentNode, currentExcName)); } } return wrapExcNames; } /** * Gets all the children one level below on the current parent node. * @param node Current parent node. * @return List of children one level below on the current * parent node (aNode). */ private static List<DetailAST> getChildNodes(DetailAST node) { final List<DetailAST> result = new LinkedList<>(); DetailAST currNode = node.getFirstChild(); while (currNode != null) { result.add(currNode); currNode = currNode.getNextSibling(); } return result; } }