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 reassignedVariableIdents.add(getNeededAssignIdent(assignToken)); 166 } 167 168 } 169 170 return reassignedVariableIdents; 171 172 } 173 174 /** 175 * Gets the closest consistent AST branch that leads to reassigned variable's ident. 176 * 177 * @param ast ast tree of checked instanceof statement. 178 * @return the closest consistent AST branch that leads to reassigned variable's ident. 179 */ 180 @Nullable 181 private static DetailAST getBranchLeadingToReassignedVars(DetailAST ast) { 182 DetailAST leadingToReassignedVarBranch = null; 183 184 for (DetailAST conditionalStatement = ast; 185 conditionalStatement != null && leadingToReassignedVarBranch == null; 186 conditionalStatement = conditionalStatement.getParent()) { 187 188 if (conditionalStatement.getType() == TokenTypes.LITERAL_IF 189 || conditionalStatement.getType() == TokenTypes.LITERAL_ELSE) { 190 191 leadingToReassignedVarBranch = 192 conditionalStatement.findFirstToken(TokenTypes.SLIST); 193 194 } 195 else if (conditionalStatement.getType() == TokenTypes.QUESTION) { 196 leadingToReassignedVarBranch = conditionalStatement; 197 } 198 } 199 200 return leadingToReassignedVarBranch; 201 202 } 203 204 /** 205 * Traverses along the AST tree to locate the first branch of certain token type. 206 * 207 * @param startingBranch AST branch to start the traverse from, but not check. 208 * @param bound AST Branch that the traverse cannot further extend to. 209 * @param neededTokenType Token type whose first encountered branch is to look for. 210 * @return the AST tree of first encountered branch of needed token type. 211 */ 212 @Nullable 213 private static DetailAST traverseUntilNeededBranchType(DetailAST startingBranch, 214 DetailAST bound, int neededTokenType) { 215 216 DetailAST match = null; 217 218 DetailAST iteratedBranch = shiftToNextTraversedBranch(startingBranch, bound); 219 220 while (iteratedBranch != null) { 221 if (iteratedBranch.getType() == neededTokenType) { 222 match = iteratedBranch; 223 break; 224 } 225 226 iteratedBranch = shiftToNextTraversedBranch(iteratedBranch, bound); 227 } 228 229 return match; 230 } 231 232 /** 233 * Shifts once to the next possible branch within traverse trajectory. 234 * 235 * @param ast AST branch to shift from. 236 * @param boundAst AST Branch that the traverse cannot further extend to. 237 * @return the AST tree of next possible branch within traverse trajectory. 238 */ 239 @Nullable 240 private static DetailAST shiftToNextTraversedBranch(DetailAST ast, DetailAST boundAst) { 241 DetailAST newAst = ast; 242 243 if (ast.getFirstChild() != null) { 244 newAst = ast.getFirstChild(); 245 } 246 else { 247 while (newAst.getNextSibling() == null && !newAst.equals(boundAst)) { 248 newAst = newAst.getParent(); 249 } 250 if (newAst.equals(boundAst)) { 251 newAst = null; 252 } 253 else { 254 newAst = newAst.getNextSibling(); 255 } 256 } 257 258 return newAst; 259 } 260 261 /** 262 * Gets the type of ASSIGN tokens that particularly matches with what follows the preceding 263 * branch. 264 * 265 * @param preAssignBranch branch that precedes the branch of ASSIGN token types. 266 * @return type of ASSIGN token. 267 */ 268 @Nullable 269 private static DetailAST getMatchedAssignToken(DetailAST preAssignBranch) { 270 DetailAST matchedAssignToken = null; 271 272 for (int assignType : ASSIGN_TOKEN_TYPES) { 273 matchedAssignToken = preAssignBranch.findFirstToken(assignType); 274 if (matchedAssignToken != null) { 275 break; 276 } 277 } 278 279 return matchedAssignToken; 280 } 281 282 /** 283 * Gets the needed AST Ident of reassigned variable for check to compare. 284 * 285 * @param assignToken The AST branch of reassigned variable's ASSIGN token. 286 * @return needed AST Ident. 287 */ 288 private static DetailAST getNeededAssignIdent(DetailAST assignToken) { 289 DetailAST assignIdent = assignToken; 290 291 while (traverseUntilNeededBranchType( 292 assignIdent, assignToken.getFirstChild(), TokenTypes.IDENT) != null) { 293 294 assignIdent = 295 traverseUntilNeededBranchType(assignIdent, assignToken, TokenTypes.IDENT); 296 } 297 298 return assignIdent; 299 } 300}