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