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 java.util.Objects; 023import java.util.regex.Pattern; 024import java.util.stream.Stream; 025 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 031 032/** 033 * <div> 034 * Checks for fall-through in {@code switch} statements. 035 * Finds locations where a {@code case} <b>contains</b> Java code but lacks a 036 * {@code break}, {@code return}, {@code yield}, {@code throw} or {@code continue} statement. 037 * </div> 038 * 039 * <p> 040 * The check honors special comments to suppress the warning. 041 * By default, the texts 042 * "fallthru", "fall thru", "fall-thru", 043 * "fallthrough", "fall through", "fall-through" 044 * "fallsthrough", "falls through", "falls-through" (case-sensitive). 045 * The comment containing these words must be all on one line, 046 * and must be on the last non-empty line before the {@code case} triggering 047 * the warning or on the same line before the {@code case}(ugly, but possible). 048 * Any other comment may follow on the same line. 049 * </p> 050 * 051 * <p> 052 * Note: 053 * The check assumes that there is no unreachable code in the {@code case}. 054 * </p> 055 * 056 * <p> 057 * A {@code case} whose code ends in an infinite loop is not flagged, e.g. 058 * {@code while (true) {}}, {@code for (;;) {}}, {@code for (;true;) {}} 059 * or {@code do {} while (true);}. 060 * </p> 061 * 062 * @since 3.4 063 */ 064@StatelessCheck 065public class FallThroughCheck extends AbstractCheck { 066 067 /** 068 * A key is pointing to the warning message text in "messages.properties" 069 * file. 070 */ 071 public static final String MSG_FALL_THROUGH = "fall.through"; 072 073 /** 074 * A key is pointing to the warning message text in "messages.properties" 075 * file. 076 */ 077 public static final String MSG_FALL_THROUGH_LAST = "fall.through.last"; 078 079 /** Control whether the last case group must be checked. */ 080 private boolean checkLastCaseGroup; 081 082 /** 083 * Define the RegExp to match the relief comment that suppresses 084 * the warning about a fall through. 085 */ 086 private Pattern reliefPattern = Pattern.compile("falls?[ -]?thr(u|ough)"); 087 088 /** 089 * Creates a new {@code FallThroughCheck} instance. 090 */ 091 public FallThroughCheck() { 092 // no code by default 093 } 094 095 @Override 096 public int[] getDefaultTokens() { 097 return getRequiredTokens(); 098 } 099 100 @Override 101 public int[] getRequiredTokens() { 102 return new int[] {TokenTypes.CASE_GROUP}; 103 } 104 105 @Override 106 public int[] getAcceptableTokens() { 107 return getRequiredTokens(); 108 } 109 110 @Override 111 public boolean isCommentNodesRequired() { 112 return true; 113 } 114 115 /** 116 * Setter to define the RegExp to match the relief comment that suppresses 117 * the warning about a fall through. 118 * 119 * @param pattern 120 * The regular expression pattern. 121 * @since 4.0 122 */ 123 public void setReliefPattern(Pattern pattern) { 124 reliefPattern = pattern; 125 } 126 127 /** 128 * Setter to control whether the last case group must be checked. 129 * 130 * @param value new value of the property. 131 * @since 4.0 132 */ 133 public void setCheckLastCaseGroup(boolean value) { 134 checkLastCaseGroup = value; 135 } 136 137 @Override 138 public void visitToken(DetailAST ast) { 139 final DetailAST nextGroup = ast.getNextSibling(); 140 final boolean isLastGroup = nextGroup.getType() != TokenTypes.CASE_GROUP; 141 if (!isLastGroup || checkLastCaseGroup) { 142 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 143 144 if (slist != null && !CheckUtil.isTerminated(slist) && !hasFallThroughComment(ast)) { 145 if (isLastGroup) { 146 log(ast, MSG_FALL_THROUGH_LAST); 147 } 148 else { 149 log(nextGroup, MSG_FALL_THROUGH); 150 } 151 } 152 } 153 } 154 155 /** 156 * Determines if the fall through case between {@code currentCase} and 157 * {@code nextCase} is relieved by an appropriate comment. 158 * 159 * <p>Handles</p> 160 * <pre> 161 * case 1: 162 * /* FALLTHRU */ case 2: 163 * 164 * switch(i) { 165 * default: 166 * /* FALLTHRU */} 167 * 168 * case 1: 169 * // FALLTHRU 170 * case 2: 171 * 172 * switch(i) { 173 * default: 174 * // FALLTHRU 175 * </pre> 176 * 177 * @param currentCase AST of the case that falls through to the next case. 178 * @return True if a relief comment was found 179 */ 180 private boolean hasFallThroughComment(DetailAST currentCase) { 181 final DetailAST nextSibling = currentCase.getNextSibling(); 182 final DetailAST ast; 183 if (nextSibling.getType() == TokenTypes.CASE_GROUP) { 184 ast = nextSibling.getFirstChild(); 185 } 186 else { 187 ast = currentCase; 188 } 189 return hasReliefComment(ast); 190 } 191 192 /** 193 * Check if there is any fall through comment. 194 * 195 * @param ast ast to check 196 * @return true if relief comment found 197 */ 198 private boolean hasReliefComment(DetailAST ast) { 199 final DetailAST nonCommentAst = CheckUtil.getNextNonCommentAst(ast); 200 boolean result = false; 201 if (nonCommentAst != null) { 202 final int prevLineNumber = nonCommentAst.getPreviousSibling().getLineNo(); 203 result = Stream.iterate(nonCommentAst.getPreviousSibling(), 204 Objects::nonNull, 205 DetailAST::getPreviousSibling) 206 .takeWhile(sibling -> sibling.getLineNo() == prevLineNumber) 207 .map(DetailAST::getFirstChild) 208 .filter(Objects::nonNull) 209 .anyMatch(firstChild -> reliefPattern.matcher(firstChild.getText()).find()); 210 } 211 return result; 212 } 213 214}