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.BitSet; 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; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 030 031/** 032 * <div> 033 * Checks for assignments in subexpressions, such as in 034 * {@code String s = Integer.toString(i = 2);}. 035 * </div> 036 * 037 * <p> 038 * Rationale: Except for the loop idioms, 039 * all assignments should occur in their own top-level statement to increase readability. 040 * With inner assignments like the one given above, it is difficult to see all places 041 * where a variable is set. 042 * </p> 043 * 044 * <p> 045 * Note: Check allows usage of the popular assignments in loops: 046 * </p> 047 * {@snippet : 048 * String line; 049 * while ((line = bufferedReader.readLine()) != null) { // OK 050 * // process the line 051 * } 052 * 053 * for (;(line = bufferedReader.readLine()) != null;) { // OK 054 * // process the line 055 * } 056 * 057 * do { 058 * // process the line 059 * } 060 * while ((line = bufferedReader.readLine()) != null); // OK 061 * } 062 * 063 * <p> 064 * Assignment inside a condition is not a problem here, as the assignment is surrounded 065 * by an extra pair of parentheses. The comparison is {@code != null} and there is no chance that 066 * intention was to write {@code line == reader.readLine()}. 067 * </p> 068 * 069 * @since 3.0 070 */ 071@StatelessCheck 072public class InnerAssignmentCheck extends AbstractCheck { 073 074 /** 075 * A key is pointing to the warning message text in "messages.properties" file. 076 */ 077 public static final String MSG_KEY = "assignment.inner.avoid"; 078 079 /** 080 * Allowed AST types from an assignment AST node towards the root. 081 */ 082 private static final int[][] ALLOWED_ASSIGNMENT_CONTEXT = { 083 {TokenTypes.EXPR, TokenTypes.SLIST}, 084 {TokenTypes.VARIABLE_DEF}, 085 {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_INIT}, 086 {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_ITERATOR}, 087 {TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR}, { 088 TokenTypes.RESOURCE, 089 TokenTypes.RESOURCES, 090 TokenTypes.RESOURCE_SPECIFICATION, 091 }, 092 {TokenTypes.EXPR, TokenTypes.LAMBDA}, 093 {TokenTypes.EXPR, TokenTypes.SWITCH_RULE, TokenTypes.LITERAL_SWITCH, TokenTypes.SLIST}, 094 }; 095 096 /** 097 * Allowed AST types from an assignment AST node towards the root. 098 */ 099 private static final int[][] CONTROL_CONTEXT = { 100 {TokenTypes.EXPR, TokenTypes.LITERAL_DO}, 101 {TokenTypes.EXPR, TokenTypes.LITERAL_FOR}, 102 {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE}, 103 {TokenTypes.EXPR, TokenTypes.LITERAL_IF}, 104 {TokenTypes.EXPR, TokenTypes.LITERAL_ELSE}, 105 }; 106 107 /** 108 * Allowed AST types from a comparison node (above an assignment) towards the root. 109 */ 110 private static final int[][] ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT = { 111 {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE}, 112 {TokenTypes.EXPR, TokenTypes.FOR_CONDITION}, 113 {TokenTypes.EXPR, TokenTypes.LITERAL_DO}, 114 }; 115 116 /** 117 * The token types that identify comparison operators. 118 */ 119 private static final BitSet COMPARISON_TYPES = TokenUtil.asBitSet( 120 TokenTypes.EQUAL, 121 TokenTypes.GE, 122 TokenTypes.GT, 123 TokenTypes.LE, 124 TokenTypes.LT, 125 TokenTypes.NOT_EQUAL 126 ); 127 128 /** 129 * The token types that are ignored while checking "loop-idiom". 130 */ 131 private static final BitSet LOOP_IDIOM_IGNORED_PARENTS = TokenUtil.asBitSet( 132 TokenTypes.LAND, 133 TokenTypes.LOR, 134 TokenTypes.LNOT, 135 TokenTypes.BOR, 136 TokenTypes.BAND 137 ); 138 139 /** 140 * Creates a new {@code InnerAssignmentCheck} instance. 141 */ 142 public InnerAssignmentCheck() { 143 // no code by default 144 } 145 146 @Override 147 public int[] getDefaultTokens() { 148 return getRequiredTokens(); 149 } 150 151 @Override 152 public int[] getAcceptableTokens() { 153 return getRequiredTokens(); 154 } 155 156 @Override 157 public int[] getRequiredTokens() { 158 return new int[] { 159 TokenTypes.ASSIGN, // '=' 160 TokenTypes.DIV_ASSIGN, // "/=" 161 TokenTypes.PLUS_ASSIGN, // "+=" 162 TokenTypes.MINUS_ASSIGN, // "-=" 163 TokenTypes.STAR_ASSIGN, // "*=" 164 TokenTypes.MOD_ASSIGN, // "%=" 165 TokenTypes.SR_ASSIGN, // ">>=" 166 TokenTypes.BSR_ASSIGN, // ">>>=" 167 TokenTypes.SL_ASSIGN, // "<<=" 168 TokenTypes.BXOR_ASSIGN, // "^=" 169 TokenTypes.BOR_ASSIGN, // "|=" 170 TokenTypes.BAND_ASSIGN, // "&=" 171 }; 172 } 173 174 @Override 175 public void visitToken(DetailAST ast) { 176 if (!isInContext(ast, ALLOWED_ASSIGNMENT_CONTEXT, CommonUtil.EMPTY_BIT_SET) 177 && !isInNoBraceControlStatement(ast) 178 && !isInLoopIdiom(ast)) { 179 log(ast, MSG_KEY); 180 } 181 } 182 183 /** 184 * Determines if ast is in the body of a flow control statement without 185 * braces. An example of such a statement would be 186 * {@snippet : 187 * if (y > 0) 188 * x = y; 189 * } 190 * 191 * <p> 192 * This leads to the following AST structure: 193 * </p> 194 * {@snippet lang="text" : 195 * LITERAL_IF 196 * LPAREN 197 * EXPR // test 198 * RPAREN 199 * EXPR // body 200 * SEMI 201 * } 202 * 203 * <p> 204 * We need to ensure that ast is in the body and not in the test. 205 * </p> 206 * 207 * @param ast an assignment operator AST 208 * @return whether ast is in the body of a flow control statement 209 */ 210 private static boolean isInNoBraceControlStatement(DetailAST ast) { 211 boolean result = false; 212 if (isInContext(ast, CONTROL_CONTEXT, CommonUtil.EMPTY_BIT_SET)) { 213 final DetailAST expr = ast.getParent(); 214 final DetailAST exprNext = expr.getNextSibling(); 215 result = exprNext.getType() == TokenTypes.SEMI; 216 } 217 return result; 218 } 219 220 /** 221 * Tests whether the given AST is used in the "assignment in loop" idiom. 222 * {@snippet : 223 * String line; 224 * while ((line = bufferedReader.readLine()) != null) { 225 * // process the line 226 * } 227 * for (;(line = bufferedReader.readLine()) != null;) { 228 * // process the line 229 * } 230 * do { 231 * // process the line 232 * } 233 * while ((line = bufferedReader.readLine()) != null); 234 * } 235 * Assignment inside a condition is not a problem here, as the assignment is surrounded by an 236 * extra pair of parentheses. The comparison is {@code != null} and there is no chance that 237 * intention was to write {@code line == reader.readLine()}. 238 * 239 * @param ast assignment AST 240 * @return whether the context of the assignment AST indicates the idiom 241 */ 242 private static boolean isInLoopIdiom(DetailAST ast) { 243 return isComparison(ast.getParent()) 244 && isInContext(ast.getParent(), 245 ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT, 246 LOOP_IDIOM_IGNORED_PARENTS); 247 } 248 249 /** 250 * Checks if an AST is a comparison operator. 251 * 252 * @param ast the AST to check 253 * @return true iff ast is a comparison operator. 254 */ 255 private static boolean isComparison(DetailAST ast) { 256 final int astType = ast.getType(); 257 return COMPARISON_TYPES.get(astType); 258 } 259 260 /** 261 * Tests whether the provided AST is in one of the given contexts. 262 * 263 * @param ast the AST from which to start walking towards root 264 * @param contextSet the contexts to test against. 265 * @param skipTokens parent token types to ignore 266 * 267 * @return whether the parents nodes of ast match one of the allowed type paths. 268 */ 269 private static boolean isInContext(DetailAST ast, int[][] contextSet, BitSet skipTokens) { 270 boolean found = false; 271 for (int[] element : contextSet) { 272 DetailAST current = ast; 273 for (int anElement : element) { 274 current = getParent(current, skipTokens); 275 if (current.getType() == anElement) { 276 found = true; 277 } 278 else { 279 found = false; 280 break; 281 } 282 } 283 284 if (found) { 285 break; 286 } 287 } 288 return found; 289 } 290 291 /** 292 * Get ast parent, ignoring token types from {@code skipTokens}. 293 * 294 * @param ast token to get parent 295 * @param skipTokens token types to skip 296 * @return first not ignored parent of ast 297 */ 298 private static DetailAST getParent(DetailAST ast, BitSet skipTokens) { 299 DetailAST result = ast.getParent(); 300 while (skipTokens.get(result.getType())) { 301 result = result.getParent(); 302 } 303 return result; 304 } 305 306}