1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.blocks; 21 22 import java.util.regex.Pattern; 23 24 import com.puppycrawl.tools.checkstyle.StatelessCheck; 25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 26 import com.puppycrawl.tools.checkstyle.api.DetailAST; 27 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 28 29 /** 30 * <p> 31 * Checks for empty catch blocks. 32 * By default check allows empty catch block with any comment inside. 33 * </p> 34 * <p> 35 * There are two options to make validation more precise: <b>exceptionVariableName</b> and 36 * <b>commentFormat</b>. 37 * If both options are specified - they are applied by <b>any of them is matching</b>. 38 * </p> 39 * <ul> 40 * <li> 41 * Property {@code exceptionVariableName} - Specify the RegExp for the name of the variable 42 * associated with exception. If check meets variable name matching specified value - empty 43 * block is suppressed. 44 * Default value is {@code "^$" (empty)}. 45 * </li> 46 * <li> 47 * Property {@code commentFormat} - Specify the RegExp for the first comment inside empty 48 * catch block. If check meets comment inside empty catch block matching specified format 49 * - empty block is suppressed. If it is multi-line comment - only its first line is analyzed. 50 * Default value is {@code ".*"}. 51 * </li> 52 * </ul> 53 * <p> 54 * To configure the check to suppress empty catch block if exception's variable name is 55 * {@code expected} or {@code ignore} or there's any comment inside: 56 * </p> 57 * <pre> 58 * <module name="EmptyCatchBlock"> 59 * <property name="exceptionVariableName" value="expected|ignore"/> 60 * </module> 61 * </pre> 62 * <p> 63 * Such empty blocks would be both suppressed: 64 * </p> 65 * <pre> 66 * try { 67 * throw new RuntimeException(); 68 * } catch (RuntimeException expected) { 69 * } 70 * try { 71 * throw new RuntimeException(); 72 * } catch (RuntimeException ignore) { 73 * } 74 * </pre> 75 * <p> 76 * To configure the check to suppress empty catch block if single-line comment inside 77 * is "//This is expected": 78 * </p> 79 * <pre> 80 * <module name="EmptyCatchBlock"> 81 * <property name="commentFormat" value="This is expected"/> 82 * </module> 83 * </pre> 84 * <p> 85 * Such empty block would be suppressed: 86 * </p> 87 * <pre> 88 * try { 89 * throw new RuntimeException(); 90 * } catch (RuntimeException ex) { 91 * //This is expected 92 * } 93 * </pre> 94 * <p> 95 * To configure the check to suppress empty catch block if single-line comment inside 96 * is "//This is expected" or exception's 97 * variable name is "myException" (any option is matching): 98 * </p> 99 * <pre> 100 * <module name="EmptyCatchBlock"> 101 * <property name="commentFormat" value="This is expected"/> 102 * <property name="exceptionVariableName" value="myException"/> 103 * </module> 104 * </pre> 105 * <p> 106 * Such empty blocks would be suppressed: 107 * </p> 108 * <pre> 109 * try { 110 * throw new RuntimeException(); 111 * } catch (RuntimeException e) { 112 * //This is expected 113 * } 114 * ... 115 * try { 116 * throw new RuntimeException(); 117 * } catch (RuntimeException e) { 118 * // This is expected 119 * } 120 * ... 121 * try { 122 * throw new RuntimeException(); 123 * } catch (RuntimeException e) { 124 * // This is expected 125 * // some another comment 126 * } 127 * ... 128 * try { 129 * throw new RuntimeException(); 130 * } catch (RuntimeException e) { 131 * /* This is expected */ 132 * } 133 * ... 134 * try { 135 * throw new RuntimeException(); 136 * } catch (RuntimeException e) { 137 * /* 138 * * 139 * * This is expected 140 * * some another comment 141 * */ 142 * } 143 * ... 144 * try { 145 * throw new RuntimeException(); 146 * } catch (RuntimeException myException) { 147 * 148 * } 149 * </pre> 150 * 151 * @since 6.4 152 */ 153 @StatelessCheck 154 public class EmptyCatchBlockCheck extends AbstractCheck { 155 156 /** 157 * A key is pointing to the warning message text in "messages.properties" 158 * file. 159 */ 160 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty"; 161 162 /** 163 * Specify the RegExp for the name of the variable associated with exception. 164 * If check meets variable name matching specified value - empty block is suppressed. 165 */ 166 private Pattern exceptionVariableName = Pattern.compile("^$"); 167 168 /** 169 * Specify the RegExp for the first comment inside empty catch block. 170 * If check meets comment inside empty catch block matching specified format - empty 171 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 172 */ 173 private Pattern commentFormat = Pattern.compile(".*"); 174 175 /** 176 * Setter to specify the RegExp for the name of the variable associated with exception. 177 * If check meets variable name matching specified value - empty block is suppressed. 178 * @param exceptionVariablePattern 179 * pattern of exception's variable name. 180 */ 181 public void setExceptionVariableName(Pattern exceptionVariablePattern) { 182 exceptionVariableName = exceptionVariablePattern; 183 } 184 185 /** 186 * Setter to specify the RegExp for the first comment inside empty catch block. 187 * If check meets comment inside empty catch block matching specified format - empty 188 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 189 * @param commentPattern 190 * pattern of comment. 191 */ 192 public void setCommentFormat(Pattern commentPattern) { 193 commentFormat = commentPattern; 194 } 195 196 @Override 197 public int[] getDefaultTokens() { 198 return getRequiredTokens(); 199 } 200 201 @Override 202 public int[] getAcceptableTokens() { 203 return getRequiredTokens(); 204 } 205 206 @Override 207 public int[] getRequiredTokens() { 208 return new int[] { 209 TokenTypes.LITERAL_CATCH, 210 }; 211 } 212 213 @Override 214 public boolean isCommentNodesRequired() { 215 return true; 216 } 217 218 @Override 219 public void visitToken(DetailAST ast) { 220 visitCatchBlock(ast); 221 } 222 223 /** 224 * Visits catch ast node, if it is empty catch block - checks it according to 225 * Check's options. If exception's variable name or comment inside block are matching 226 * specified regexp - skips from consideration, else - puts violation. 227 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 228 */ 229 private void visitCatchBlock(DetailAST catchAst) { 230 if (isEmptyCatchBlock(catchAst)) { 231 final String commentContent = getCommentFirstLine(catchAst); 232 if (isVerifiable(catchAst, commentContent)) { 233 log(catchAst.getLineNo(), MSG_KEY_CATCH_BLOCK_EMPTY); 234 } 235 } 236 } 237 238 /** 239 * Gets the first line of comment in catch block. If comment is single-line - 240 * returns it fully, else if comment is multi-line - returns the first line. 241 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 242 * @return the first line of comment in catch block, "" if no comment was found. 243 */ 244 private static String getCommentFirstLine(DetailAST catchAst) { 245 final DetailAST slistToken = catchAst.getLastChild(); 246 final DetailAST firstElementInBlock = slistToken.getFirstChild(); 247 String commentContent = ""; 248 if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 249 commentContent = firstElementInBlock.getFirstChild().getText(); 250 } 251 else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 252 commentContent = firstElementInBlock.getFirstChild().getText(); 253 final String[] lines = commentContent.split(System.getProperty("line.separator")); 254 for (String line : lines) { 255 if (!line.isEmpty()) { 256 commentContent = line; 257 break; 258 } 259 } 260 } 261 return commentContent; 262 } 263 264 /** 265 * Checks if current empty catch block is verifiable according to Check's options 266 * (exception's variable name and comment format are both in consideration). 267 * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block. 268 * @param commentContent text of comment. 269 * @return true if empty catch block is verifiable by Check. 270 */ 271 private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) { 272 final String variableName = getExceptionVariableName(emptyCatchAst); 273 final boolean isMatchingVariableName = exceptionVariableName 274 .matcher(variableName).find(); 275 final boolean isMatchingCommentContent = !commentContent.isEmpty() 276 && commentFormat.matcher(commentContent).find(); 277 return !isMatchingVariableName && !isMatchingCommentContent; 278 } 279 280 /** 281 * Checks if catch block is empty or contains only comments. 282 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 283 * @return true if catch block is empty. 284 */ 285 private static boolean isEmptyCatchBlock(DetailAST catchAst) { 286 boolean result = true; 287 final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST); 288 DetailAST catchBlockStmt = slistToken.getFirstChild(); 289 while (catchBlockStmt.getType() != TokenTypes.RCURLY) { 290 if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT 291 && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) { 292 result = false; 293 break; 294 } 295 catchBlockStmt = catchBlockStmt.getNextSibling(); 296 } 297 return result; 298 } 299 300 /** 301 * Gets variable's name associated with exception. 302 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 303 * @return Variable's name associated with exception. 304 */ 305 private static String getExceptionVariableName(DetailAST catchAst) { 306 final DetailAST parameterDef = catchAst.findFirstToken(TokenTypes.PARAMETER_DEF); 307 final DetailAST variableName = parameterDef.findFirstToken(TokenTypes.IDENT); 308 return variableName.getText(); 309 } 310 311 }