001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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.ArrayList; 023import java.util.List; 024import java.util.Set; 025 026import javax.annotation.Nullable; 027 028import com.puppycrawl.tools.checkstyle.StatelessCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032 033/** 034 * <div> 035 * Checks for assignment of pattern variables. 036 * </div> 037 * 038 * <p> 039 * Pattern variable assignment is considered bad programming practice. The pattern variable 040 * is meant to be a direct reference to the object being matched. Reassigning it can break this 041 * connection and mislead readers. 042 * </p> 043 * 044 * @since 10.26.0 045 */ 046@StatelessCheck 047public class PatternVariableAssignmentCheck extends AbstractCheck { 048 049 /** 050 * A key is pointing to the warning message in "messages.properties" file. 051 */ 052 public static final String MSG_KEY = "pattern.variable.assignment"; 053 054 /** 055 * The set of all valid types of ASSIGN token for this check. 056 */ 057 private static final Set<Integer> ASSIGN_TOKEN_TYPES = Set.of( 058 TokenTypes.ASSIGN, TokenTypes.PLUS_ASSIGN, TokenTypes.MINUS_ASSIGN, TokenTypes.STAR_ASSIGN, 059 TokenTypes.DIV_ASSIGN, TokenTypes.MOD_ASSIGN, TokenTypes.SR_ASSIGN, TokenTypes.BSR_ASSIGN, 060 TokenTypes.SL_ASSIGN, TokenTypes.BAND_ASSIGN, TokenTypes.BXOR_ASSIGN, 061 TokenTypes.BOR_ASSIGN); 062 063 @Override 064 public int[] getRequiredTokens() { 065 return new int[] {TokenTypes.LITERAL_INSTANCEOF}; 066 } 067 068 @Override 069 public int[] getDefaultTokens() { 070 return getRequiredTokens(); 071 } 072 073 @Override 074 public int[] getAcceptableTokens() { 075 return getRequiredTokens(); 076 } 077 078 @Override 079 public void visitToken(DetailAST ast) { 080 081 final List<DetailAST> patternVariableIdents = getPatternVariableIdents(ast); 082 final List<DetailAST> reassignedVariableIdents = getReassignedVariableIdents(ast); 083 084 for (DetailAST patternVariableIdent : patternVariableIdents) { 085 for (DetailAST assignTokenIdent : reassignedVariableIdents) { 086 if (patternVariableIdent.getText().equals(assignTokenIdent.getText())) { 087 088 log(assignTokenIdent, MSG_KEY, assignTokenIdent.getText()); 089 break; 090 } 091 092 } 093 } 094 } 095 096 /** 097 * Gets the list of all pattern variable idents in instanceof expression. 098 * 099 * @param ast ast tree of instanceof to get the list from. 100 * @return list of pattern variables. 101 */ 102 private static List<DetailAST> getPatternVariableIdents(DetailAST ast) { 103 104 final DetailAST outermostPatternVariable = 105 ast.findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF); 106 107 final DetailAST recordPatternDef; 108 if (ast.getType() == TokenTypes.LITERAL_INSTANCEOF) { 109 recordPatternDef = ast.findFirstToken(TokenTypes.RECORD_PATTERN_DEF); 110 } 111 else { 112 recordPatternDef = ast; 113 } 114 115 final List<DetailAST> patternVariableIdentsArray = new ArrayList<>(); 116 117 if (outermostPatternVariable != null) { 118 patternVariableIdentsArray.add( 119 outermostPatternVariable.findFirstToken(TokenTypes.IDENT)); 120 } 121 else if (recordPatternDef != null) { 122 final DetailAST recordPatternComponents = recordPatternDef 123 .findFirstToken(TokenTypes.RECORD_PATTERN_COMPONENTS); 124 125 if (recordPatternComponents != null) { 126 for (DetailAST innerPatternVariable = recordPatternComponents.getFirstChild(); 127 innerPatternVariable != null; 128 innerPatternVariable = innerPatternVariable.getNextSibling()) { 129 130 if (innerPatternVariable.getType() == TokenTypes.PATTERN_VARIABLE_DEF) { 131 patternVariableIdentsArray.add( 132 innerPatternVariable.findFirstToken(TokenTypes.IDENT)); 133 } 134 else { 135 patternVariableIdentsArray.addAll( 136 getPatternVariableIdents(innerPatternVariable)); 137 } 138 139 } 140 } 141 142 } 143 return patternVariableIdentsArray; 144 } 145 146 /** 147 * Gets the array list made out of AST branches of reassigned variable idents. 148 * 149 * @param ast ast tree of checked instanceof statement. 150 * @return the list of AST branches of reassigned variable idents. 151 */ 152 private static List<DetailAST> getReassignedVariableIdents(DetailAST ast) { 153 154 final DetailAST branchLeadingToReassignedVar = getBranchLeadingToReassignedVars(ast); 155 final List<DetailAST> reassignedVariableIdents = new ArrayList<>(); 156 157 for (DetailAST expressionBranch = branchLeadingToReassignedVar; 158 expressionBranch != null; 159 expressionBranch = traverseUntilNeededBranchType(expressionBranch, 160 branchLeadingToReassignedVar, TokenTypes.EXPR)) { 161 162 final DetailAST assignToken = getMatchedAssignToken(expressionBranch); 163 164 if (assignToken != null) { 165 final DetailAST neededAssignIdent = getNeededAssignIdent(assignToken); 166 if (neededAssignIdent.getPreviousSibling() == null) { 167 reassignedVariableIdents.add(getNeededAssignIdent(assignToken)); 168 } 169 } 170 } 171 172 return reassignedVariableIdents; 173 174 } 175 176 /** 177 * Gets the closest consistent AST branch that leads to reassigned variable's ident. 178 * 179 * @param ast ast tree of checked instanceof statement. 180 * @return the closest consistent AST branch that leads to reassigned variable's ident. 181 */ 182 @Nullable 183 private static DetailAST getBranchLeadingToReassignedVars(DetailAST ast) { 184 DetailAST leadingToReassignedVarBranch = null; 185 186 for (DetailAST conditionalStatement = ast; 187 conditionalStatement != null && leadingToReassignedVarBranch == null; 188 conditionalStatement = conditionalStatement.getParent()) { 189 190 if (conditionalStatement.getType() == TokenTypes.LITERAL_IF 191 || conditionalStatement.getType() == TokenTypes.LITERAL_ELSE) { 192 193 leadingToReassignedVarBranch = 194 conditionalStatement.findFirstToken(TokenTypes.SLIST); 195 196 } 197 else if (conditionalStatement.getType() == TokenTypes.QUESTION) { 198 leadingToReassignedVarBranch = conditionalStatement; 199 } 200 } 201 202 return leadingToReassignedVarBranch; 203 204 } 205 206 /** 207 * Traverses along the AST tree to locate the first branch of certain token type. 208 * 209 * @param startingBranch AST branch to start the traverse from, but not check. 210 * @param bound AST Branch that the traverse cannot further extend to. 211 * @param neededTokenType Token type whose first encountered branch is to look for. 212 * @return the AST tree of first encountered branch of needed token type. 213 */ 214 @Nullable 215 private static DetailAST traverseUntilNeededBranchType(DetailAST startingBranch, 216 DetailAST bound, int neededTokenType) { 217 218 DetailAST match = null; 219 220 DetailAST iteratedBranch = shiftToNextTraversedBranch(startingBranch, bound); 221 222 while (iteratedBranch != null) { 223 if (iteratedBranch.getType() == neededTokenType) { 224 match = iteratedBranch; 225 break; 226 } 227 228 iteratedBranch = shiftToNextTraversedBranch(iteratedBranch, bound); 229 } 230 231 return match; 232 } 233 234 /** 235 * Shifts once to the next possible branch within traverse trajectory. 236 * 237 * @param ast AST branch to shift from. 238 * @param boundAst AST Branch that the traverse cannot further extend to. 239 * @return the AST tree of next possible branch within traverse trajectory. 240 */ 241 @Nullable 242 private static DetailAST shiftToNextTraversedBranch(DetailAST ast, DetailAST boundAst) { 243 DetailAST newAst = ast; 244 245 if (ast.getFirstChild() != null) { 246 newAst = ast.getFirstChild(); 247 } 248 else { 249 while (newAst.getNextSibling() == null && !newAst.equals(boundAst)) { 250 newAst = newAst.getParent(); 251 } 252 if (newAst.equals(boundAst)) { 253 newAst = null; 254 } 255 else { 256 newAst = newAst.getNextSibling(); 257 } 258 } 259 260 return newAst; 261 } 262 263 /** 264 * Gets the type of ASSIGN tokens that particularly matches with what follows the preceding 265 * branch. 266 * 267 * @param preAssignBranch branch that precedes the branch of ASSIGN token types. 268 * @return type of ASSIGN token. 269 */ 270 @Nullable 271 private static DetailAST getMatchedAssignToken(DetailAST preAssignBranch) { 272 DetailAST matchedAssignToken = null; 273 274 for (int assignType : ASSIGN_TOKEN_TYPES) { 275 matchedAssignToken = preAssignBranch.findFirstToken(assignType); 276 if (matchedAssignToken != null) { 277 break; 278 } 279 } 280 281 return matchedAssignToken; 282 } 283 284 /** 285 * Gets the needed AST Ident of reassigned variable for check to compare. 286 * 287 * @param assignToken The AST branch of reassigned variable's ASSIGN token. 288 * @return needed AST Ident. 289 */ 290 private static DetailAST getNeededAssignIdent(DetailAST assignToken) { 291 DetailAST assignIdent = assignToken; 292 293 while (traverseUntilNeededBranchType( 294 assignIdent, assignToken.getFirstChild(), TokenTypes.IDENT) != null) { 295 296 assignIdent = 297 traverseUntilNeededBranchType(assignIdent, assignToken, TokenTypes.IDENT); 298 } 299 300 return assignIdent; 301 } 302}