001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import com.puppycrawl.tools.checkstyle.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026 027/** 028 * <div> 029 * Checks that throw statements within catch blocks do not use 030 * the caught exception's getMessage() call when the thrown exception 031 * is the same type as the caught exception, as rethrowing the same 032 * exception type with its own message is redundant. 033 * </div> 034 * 035 * <p> 036 * Rationale: When throwing an exception of the same type as the caught 037 * exception and including the caught exception's message via getMessage(), 038 * the information is redundant. The original exception should be rethrown 039 * directly, or a different exception type should be used. 040 * </p> 041 * 042 * <p> 043 * Example of violations: 044 * </p> 045 * <div class="wrapper"><pre> 046 * catch (IOException ex) { 047 * throw new IOException("Error: " + ex.getMessage()); // violation 048 * } 049 * </pre></div> 050 * 051 * <p> 052 * Correct usage: 053 * </p> 054 * <div class="wrapper"><pre> 055 * catch (IOException ex) { 056 * throw new RuntimeException("Error: " + ex.getMessage(), ex); // OK, different type 057 * } 058 * catch (IOException ex) { 059 * throw new IOException("Error processing file", ex); // OK, no getMessage() 060 * } 061 * </pre></div> 062 * 063 * @since 13.3.0 064 */ 065@StatelessCheck 066public class NoGetMessageInThrowCheck extends AbstractCheck { 067 068 /** 069 * A key is pointing to the warning message text in "messages.properties" file. 070 */ 071 public static final String MSG_KEY = "no.getmessage.in.throw"; 072 073 @Override 074 public int[] getDefaultTokens() { 075 return getRequiredTokens(); 076 } 077 078 @Override 079 public int[] getAcceptableTokens() { 080 return getRequiredTokens(); 081 } 082 083 @Override 084 public int[] getRequiredTokens() { 085 return new int[] {TokenTypes.LITERAL_THROW}; 086 } 087 088 @Override 089 public void visitToken(DetailAST ast) { 090 final String thrownType = getThrownType(ast); 091 if (thrownType != null && hasViolatingGetMessageCall(ast, thrownType)) { 092 log(ast, MSG_KEY); 093 } 094 } 095 096 /** 097 * Gets the simple type name of the exception being thrown via a new expression. 098 * 099 * @param throwAst the LITERAL_THROW AST node 100 * @return the simple type name, or null if the throw does not use a new expression 101 */ 102 private static String getThrownType(DetailAST throwAst) { 103 final DetailAST expr = throwAst.getFirstChild(); 104 final DetailAST literalNew = expr.findFirstToken(TokenTypes.LITERAL_NEW); 105 String result = null; 106 if (literalNew != null) { 107 result = literalNew.findFirstToken(TokenTypes.IDENT).getText(); 108 } 109 return result; 110 } 111 112 /** 113 * Recursively checks if the AST subtree contains a getMessage() call 114 * on a caught exception variable whose type matches the thrown type. 115 * 116 * @param ast the AST node to search 117 * @param thrownType the simple type name of the thrown exception 118 * @return true if a violating getMessage() call is found 119 */ 120 private static boolean hasViolatingGetMessageCall(DetailAST ast, String thrownType) { 121 boolean found = false; 122 DetailAST currentNode = ast.getFirstChild(); 123 124 while (currentNode != null && !found) { 125 found = isMatchingGetMessage(currentNode, thrownType) 126 || hasViolatingGetMessageCall(currentNode, thrownType); 127 currentNode = currentNode.getNextSibling(); 128 } 129 130 return found; 131 } 132 133 /** 134 * Checks if a node represents a getMessage() call on a caught exception 135 * variable whose catch type matches the thrown type. 136 * 137 * @param node the AST node to check 138 * @param thrownType the simple type name of the thrown exception 139 * @return true if this is a matching getMessage() call 140 */ 141 private static boolean isMatchingGetMessage(DetailAST node, String thrownType) { 142 final DetailAST dot = node.findFirstToken(TokenTypes.DOT); 143 boolean result = false; 144 if (dot != null) { 145 final DetailAST methodIdent = dot.getLastChild(); 146 if ("getMessage".equals(methodIdent.getText())) { 147 final String varName = dot.getFirstChild().getText(); 148 final DetailAST catchBlock = findCatchForVariable(node, varName); 149 if (catchBlock != null) { 150 final DetailAST paramDef = 151 catchBlock.findFirstToken(TokenTypes.PARAMETER_DEF); 152 result = matchesCaughtType(paramDef, thrownType); 153 } 154 } 155 } 156 return result; 157 } 158 159 /** 160 * Walks up the AST from the given node to find an enclosing catch block 161 * that declares a parameter with the specified variable name. 162 * 163 * @param ast the starting AST node 164 * @param varName the variable name to look for 165 * @return the LITERAL_CATCH node that declares the variable, or null 166 */ 167 private static DetailAST findCatchForVariable(DetailAST ast, String varName) { 168 DetailAST result = null; 169 DetailAST current = ast; 170 while (current != null) { 171 current = current.getParent(); 172 if (current != null && current.getType() == TokenTypes.LITERAL_CATCH) { 173 final DetailAST paramDef = 174 current.findFirstToken(TokenTypes.PARAMETER_DEF); 175 final DetailAST ident = paramDef.findFirstToken(TokenTypes.IDENT); 176 if (varName.equals(ident.getText())) { 177 result = current; 178 break; 179 } 180 } 181 } 182 return result; 183 } 184 185 /** 186 * Checks if the caught exception type in a parameter definition matches 187 * the given type name. Handles both simple types and multi-catch types. 188 * 189 * @param paramDef the PARAMETER_DEF AST node 190 * @param typeName the type name to match against 191 * @return true if the type matches any of the caught types 192 */ 193 private static boolean matchesCaughtType(DetailAST paramDef, String typeName) { 194 final DetailAST typeAst = paramDef.findFirstToken(TokenTypes.TYPE); 195 return containsTypeName(typeAst, typeName); 196 } 197 198 /** 199 * Checks if the given TYPE AST's child matches the specified type name. 200 * 201 * @param ast the TYPE AST node 202 * @param typeName the type name to match 203 * @return true if the type matches 204 */ 205 private static boolean containsTypeName(DetailAST ast, String typeName) { 206 return typeName.equals(ast.getFirstChild().getText()); 207 } 208}