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 * &lt;module name=&quot;InnerAssignment"/&gt;
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&lt;String&gt; myList = new ArrayList&lt;String&gt;();
089 *     myList.add(nameOne = "tom"); // violation
090 *     for (int k = 0; k &lt; 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 &lt; 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}