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