001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.blocks; 021 022import java.util.regex.Pattern; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028 029/** 030 * <p> 031 * Checks for empty catch blocks. 032 * By default check allows empty catch block with any comment inside. 033 * </p> 034 * <p> 035 * There are two options to make validation more precise: <b>exceptionVariableName</b> and 036 * <b>commentFormat</b>. 037 * If both options are specified - they are applied by <b>any of them is matching</b>. 038 * </p> 039 * <ul> 040 * <li> 041 * Property {@code exceptionVariableName} - Specify the RegExp for the name of the variable 042 * associated with exception. If check meets variable name matching specified value - empty 043 * block is suppressed. 044 * Type is {@code java.util.regex.Pattern}. 045 * Default value is {@code "^$"}. 046 * </li> 047 * <li> 048 * Property {@code commentFormat} - Specify the RegExp for the first comment inside empty 049 * catch block. If check meets comment inside empty catch block matching specified format 050 * - empty block is suppressed. If it is multi-line comment - only its first line is analyzed. 051 * Type is {@code java.util.regex.Pattern}. 052 * Default value is {@code ".*"}. 053 * </li> 054 * </ul> 055 * <p> 056 * To configure the check: 057 * </p> 058 * <pre> 059 * <module name="EmptyCatchBlock"/> 060 * </pre> 061 * <p> 062 * Example: 063 * </p> 064 * <pre> 065 * try { 066 * throw new RuntimeException(); 067 * } catch (RuntimeException expected) { 068 * } // violation 069 * 070 * try { 071 * throw new RuntimeException(); 072 * } catch (RuntimeException ignore) { 073 * // no handling 074 * } // ok, catch block has comment 075 * 076 * try { 077 * throw new RuntimeException(); 078 * } catch (RuntimeException o) { 079 * } // violation 080 * 081 * try { 082 * throw new RuntimeException(); 083 * } catch (RuntimeException ex) { 084 * // This is expected 085 * } // ok 086 * </pre> 087 * <p> 088 * To configure the check to suppress empty catch block if exception's variable name is 089 * {@code expected} or {@code ignore} or there's any comment inside: 090 * </p> 091 * <pre> 092 * <module name="EmptyCatchBlock"> 093 * <property name="exceptionVariableName" value="expected|ignore"/> 094 * </module> 095 * </pre> 096 * <p> 097 * Such empty blocks would be both suppressed: 098 * </p> 099 * <pre> 100 * try { 101 * throw new RuntimeException(); 102 * } catch (RuntimeException expected) { 103 * } // ok 104 * 105 * try { 106 * throw new RuntimeException(); 107 * } catch (RuntimeException ignore) { 108 * // no handling 109 * } // ok 110 * 111 * try { 112 * throw new RuntimeException(); 113 * } catch (RuntimeException o) { 114 * } // violation 115 * 116 * try { 117 * throw new RuntimeException(); 118 * } catch (RuntimeException ex) { 119 * // This is expected 120 * } // ok 121 * </pre> 122 * <p> 123 * To configure the check to suppress empty catch block if single-line comment inside 124 * is "//This is expected": 125 * </p> 126 * <pre> 127 * <module name="EmptyCatchBlock"> 128 * <property name="commentFormat" value="This is expected"/> 129 * </module> 130 * </pre> 131 * <p> 132 * Such empty block would be suppressed: 133 * </p> 134 * <pre> 135 * try { 136 * throw new RuntimeException(); 137 * } catch (RuntimeException expected) { 138 * } // violation 139 * 140 * try { 141 * throw new RuntimeException(); 142 * } catch (RuntimeException ignore) { 143 * // no handling 144 * } // violation 145 * 146 * try { 147 * throw new RuntimeException(); 148 * } catch (RuntimeException o) { 149 * } // violation 150 * 151 * try { 152 * throw new RuntimeException(); 153 * } catch (RuntimeException ex) { 154 * // This is expected 155 * } // ok 156 * </pre> 157 * <p> 158 * To configure the check to suppress empty catch block if single-line comment inside 159 * is "//This is expected" or exception's 160 * variable name is "myException" (any option is matching): 161 * </p> 162 * <pre> 163 * <module name="EmptyCatchBlock"> 164 * <property name="commentFormat" value="This is expected"/> 165 * <property name="exceptionVariableName" value="myException"/> 166 * </module> 167 * </pre> 168 * <p> 169 * Such empty blocks would be suppressed: 170 * </p> 171 * <pre> 172 * try { 173 * throw new RuntimeException(); 174 * } catch (RuntimeException e) { 175 * //This is expected 176 * } 177 * ... 178 * try { 179 * throw new RuntimeException(); 180 * } catch (RuntimeException e) { 181 * // This is expected 182 * } 183 * ... 184 * try { 185 * throw new RuntimeException(); 186 * } catch (RuntimeException e) { 187 * // This is expected 188 * // some another comment 189 * } 190 * ... 191 * try { 192 * throw new RuntimeException(); 193 * } catch (RuntimeException e) { 194 * /* This is expected */ 195 * } 196 * ... 197 * try { 198 * throw new RuntimeException(); 199 * } catch (RuntimeException e) { 200 * /* 201 * * 202 * * This is expected 203 * * some another comment 204 * */ 205 * } 206 * ... 207 * try { 208 * throw new RuntimeException(); 209 * } catch (RuntimeException myException) { 210 * 211 * } 212 * </pre> 213 * <p> 214 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 215 * </p> 216 * <p> 217 * Violation Message Keys: 218 * </p> 219 * <ul> 220 * <li> 221 * {@code catch.block.empty} 222 * </li> 223 * </ul> 224 * 225 * @since 6.4 226 */ 227@StatelessCheck 228public class EmptyCatchBlockCheck extends AbstractCheck { 229 230 /** 231 * A key is pointing to the warning message text in "messages.properties" 232 * file. 233 */ 234 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty"; 235 236 /** 237 * A pattern to split on line ends. 238 */ 239 private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r"); 240 241 /** 242 * Specify the RegExp for the name of the variable associated with exception. 243 * If check meets variable name matching specified value - empty block is suppressed. 244 */ 245 private Pattern exceptionVariableName = Pattern.compile("^$"); 246 247 /** 248 * Specify the RegExp for the first comment inside empty catch block. 249 * If check meets comment inside empty catch block matching specified format - empty 250 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 251 */ 252 private Pattern commentFormat = Pattern.compile(".*"); 253 254 /** 255 * Setter to specify the RegExp for the name of the variable associated with exception. 256 * If check meets variable name matching specified value - empty block is suppressed. 257 * 258 * @param exceptionVariablePattern 259 * pattern of exception's variable name. 260 */ 261 public void setExceptionVariableName(Pattern exceptionVariablePattern) { 262 exceptionVariableName = exceptionVariablePattern; 263 } 264 265 /** 266 * Setter to specify the RegExp for the first comment inside empty catch block. 267 * If check meets comment inside empty catch block matching specified format - empty 268 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 269 * 270 * @param commentPattern 271 * pattern of comment. 272 */ 273 public void setCommentFormat(Pattern commentPattern) { 274 commentFormat = commentPattern; 275 } 276 277 @Override 278 public int[] getDefaultTokens() { 279 return getRequiredTokens(); 280 } 281 282 @Override 283 public int[] getAcceptableTokens() { 284 return getRequiredTokens(); 285 } 286 287 @Override 288 public int[] getRequiredTokens() { 289 return new int[] { 290 TokenTypes.LITERAL_CATCH, 291 }; 292 } 293 294 @Override 295 public boolean isCommentNodesRequired() { 296 return true; 297 } 298 299 @Override 300 public void visitToken(DetailAST ast) { 301 visitCatchBlock(ast); 302 } 303 304 /** 305 * Visits catch ast node, if it is empty catch block - checks it according to 306 * Check's options. If exception's variable name or comment inside block are matching 307 * specified regexp - skips from consideration, else - puts violation. 308 * 309 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 310 */ 311 private void visitCatchBlock(DetailAST catchAst) { 312 if (isEmptyCatchBlock(catchAst)) { 313 final String commentContent = getCommentFirstLine(catchAst); 314 if (isVerifiable(catchAst, commentContent)) { 315 log(catchAst.findFirstToken(TokenTypes.SLIST), MSG_KEY_CATCH_BLOCK_EMPTY); 316 } 317 } 318 } 319 320 /** 321 * Gets the first line of comment in catch block. If comment is single-line - 322 * returns it fully, else if comment is multi-line - returns the first line. 323 * 324 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 325 * @return the first line of comment in catch block, "" if no comment was found. 326 */ 327 private static String getCommentFirstLine(DetailAST catchAst) { 328 final DetailAST slistToken = catchAst.getLastChild(); 329 final DetailAST firstElementInBlock = slistToken.getFirstChild(); 330 String commentContent = ""; 331 if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 332 commentContent = firstElementInBlock.getFirstChild().getText(); 333 } 334 else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 335 commentContent = firstElementInBlock.getFirstChild().getText(); 336 final String[] lines = LINE_END_PATTERN.split(commentContent); 337 for (String line : lines) { 338 if (!line.isEmpty()) { 339 commentContent = line; 340 break; 341 } 342 } 343 } 344 return commentContent; 345 } 346 347 /** 348 * Checks if current empty catch block is verifiable according to Check's options 349 * (exception's variable name and comment format are both in consideration). 350 * 351 * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block. 352 * @param commentContent text of comment. 353 * @return true if empty catch block is verifiable by Check. 354 */ 355 private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) { 356 final String variableName = getExceptionVariableName(emptyCatchAst); 357 final boolean isMatchingVariableName = exceptionVariableName 358 .matcher(variableName).find(); 359 final boolean isMatchingCommentContent = !commentContent.isEmpty() 360 && commentFormat.matcher(commentContent).find(); 361 return !isMatchingVariableName && !isMatchingCommentContent; 362 } 363 364 /** 365 * Checks if catch block is empty or contains only comments. 366 * 367 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 368 * @return true if catch block is empty. 369 */ 370 private static boolean isEmptyCatchBlock(DetailAST catchAst) { 371 boolean result = true; 372 final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST); 373 DetailAST catchBlockStmt = slistToken.getFirstChild(); 374 while (catchBlockStmt.getType() != TokenTypes.RCURLY) { 375 if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT 376 && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) { 377 result = false; 378 break; 379 } 380 catchBlockStmt = catchBlockStmt.getNextSibling(); 381 } 382 return result; 383 } 384 385 /** 386 * Gets variable's name associated with exception. 387 * 388 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 389 * @return Variable's name associated with exception. 390 */ 391 private static String getExceptionVariableName(DetailAST catchAst) { 392 final DetailAST parameterDef = catchAst.findFirstToken(TokenTypes.PARAMETER_DEF); 393 final DetailAST variableName = parameterDef.findFirstToken(TokenTypes.IDENT); 394 return variableName.getText(); 395 } 396 397}