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