| 1 | //////////////////////////////////////////////////////////////////////////////// | |
| 2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
| 3 | // Copyright (C) 2001-2018 the original author or authors. | |
| 4 | // | |
| 5 | // This library is free software; you can redistribute it and/or | |
| 6 | // modify it under the terms of the GNU Lesser General Public | |
| 7 | // License as published by the Free Software Foundation; either | |
| 8 | // version 2.1 of the License, or (at your option) any later version. | |
| 9 | // | |
| 10 | // This library is distributed in the hope that it will be useful, | |
| 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | |
| 13 | // Lesser General Public License for more details. | |
| 14 | // | |
| 15 | // You should have received a copy of the GNU Lesser General Public | |
| 16 | // License along with this library; if not, write to the Free Software | |
| 17 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | |
| 18 | //////////////////////////////////////////////////////////////////////////////// | |
| 19 | ||
| 20 | package com.puppycrawl.tools.checkstyle.checks.coding; | |
| 21 | ||
| 22 | import java.util.Arrays; | |
| 23 | ||
| 24 | import antlr.collections.AST; | |
| 25 | import com.puppycrawl.tools.checkstyle.StatelessCheck; | |
| 26 | import com.puppycrawl.tools.checkstyle.api.AbstractCheck; | |
| 27 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
| 28 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
| 29 | ||
| 30 | /** | |
| 31 |  * <p> | |
| 32 |  * Checks for assignments in subexpressions, such as in | |
| 33 |  * {@code String s = Integer.toString(i = 2);}. | |
| 34 |  * </p> | |
| 35 |  * <p> | |
| 36 |  * Rationale: With the exception of {@code for} iterators, all assignments | |
| 37 |  * should occur in their own top-level statement to increase readability. | |
| 38 |  * With inner assignments like the above it is difficult to see all places | |
| 39 |  * where a variable is set. | |
| 40 |  * </p> | |
| 41 |  * | |
| 42 |  * @author lkuehne | |
| 43 |  */ | |
| 44 | @StatelessCheck | |
| 45 | public class InnerAssignmentCheck | |
| 46 |         extends AbstractCheck { | |
| 47 | ||
| 48 |     /** | |
| 49 |      * A key is pointing to the warning message text in "messages.properties" | |
| 50 |      * file. | |
| 51 |      */ | |
| 52 |     public static final String MSG_KEY = "assignment.inner.avoid"; | |
| 53 | ||
| 54 |     /** | |
| 55 |      * List of allowed AST types from an assignment AST node | |
| 56 |      * towards the root. | |
| 57 |      */ | |
| 58 |     private static final int[][] ALLOWED_ASSIGNMENT_CONTEXT = { | |
| 59 |         {TokenTypes.EXPR, TokenTypes.SLIST}, | |
| 60 |         {TokenTypes.VARIABLE_DEF}, | |
| 61 |         {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_INIT}, | |
| 62 |         {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_ITERATOR}, | |
| 63 |         {TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR}, { | |
| 64 |             TokenTypes.RESOURCE, | |
| 65 |             TokenTypes.RESOURCES, | |
| 66 |             TokenTypes.RESOURCE_SPECIFICATION, | |
| 67 |         }, | |
| 68 |         {TokenTypes.EXPR, TokenTypes.LAMBDA}, | |
| 69 |     }; | |
| 70 | ||
| 71 |     /** | |
| 72 |      * List of allowed AST types from an assignment AST node | |
| 73 |      * towards the root. | |
| 74 |      */ | |
| 75 |     private static final int[][] CONTROL_CONTEXT = { | |
| 76 |         {TokenTypes.EXPR, TokenTypes.LITERAL_DO}, | |
| 77 |         {TokenTypes.EXPR, TokenTypes.LITERAL_FOR}, | |
| 78 |         {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE}, | |
| 79 |         {TokenTypes.EXPR, TokenTypes.LITERAL_IF}, | |
| 80 |         {TokenTypes.EXPR, TokenTypes.LITERAL_ELSE}, | |
| 81 |     }; | |
| 82 | ||
| 83 |     /** | |
| 84 |      * List of allowed AST types from a comparison node (above an assignment) | |
| 85 |      * towards the root. | |
| 86 |      */ | |
| 87 |     private static final int[][] ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT = { | |
| 88 |         {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE, }, | |
| 89 |     }; | |
| 90 | ||
| 91 |     /** | |
| 92 |      * The token types that identify comparison operators. | |
| 93 |      */ | |
| 94 |     private static final int[] COMPARISON_TYPES = { | |
| 95 |         TokenTypes.EQUAL, | |
| 96 |         TokenTypes.GE, | |
| 97 |         TokenTypes.GT, | |
| 98 |         TokenTypes.LE, | |
| 99 |         TokenTypes.LT, | |
| 100 |         TokenTypes.NOT_EQUAL, | |
| 101 |     }; | |
| 102 | ||
| 103 |     static { | |
| 104 |         Arrays.sort(COMPARISON_TYPES); | |
| 105 |     } | |
| 106 | ||
| 107 |     @Override | |
| 108 |     public int[] getDefaultTokens() { | |
| 109 | 1
1. getDefaultTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |         return getRequiredTokens(); | 
| 110 |     } | |
| 111 | ||
| 112 |     @Override | |
| 113 |     public int[] getAcceptableTokens() { | |
| 114 | 1
1. getAcceptableTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |         return getRequiredTokens(); | 
| 115 |     } | |
| 116 | ||
| 117 |     @Override | |
| 118 |     public int[] getRequiredTokens() { | |
| 119 | 1
1. getRequiredTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |         return new int[] { | 
| 120 |             TokenTypes.ASSIGN,            // '=' | |
| 121 |             TokenTypes.DIV_ASSIGN,        // "/=" | |
| 122 |             TokenTypes.PLUS_ASSIGN,       // "+=" | |
| 123 |             TokenTypes.MINUS_ASSIGN,      //"-=" | |
| 124 |             TokenTypes.STAR_ASSIGN,       // "*=" | |
| 125 |             TokenTypes.MOD_ASSIGN,        // "%=" | |
| 126 |             TokenTypes.SR_ASSIGN,         // ">>=" | |
| 127 |             TokenTypes.BSR_ASSIGN,        // ">>>=" | |
| 128 |             TokenTypes.SL_ASSIGN,         // "<<=" | |
| 129 |             TokenTypes.BXOR_ASSIGN,       // "^=" | |
| 130 |             TokenTypes.BOR_ASSIGN,        // "|=" | |
| 131 |             TokenTypes.BAND_ASSIGN,       // "&=" | |
| 132 |         }; | |
| 133 |     } | |
| 134 | ||
| 135 |     @Override | |
| 136 |     public void visitToken(DetailAST ast) { | |
| 137 | 1
1. visitToken : negated conditional → KILLED |         if (!isInContext(ast, ALLOWED_ASSIGNMENT_CONTEXT) | 
| 138 | 1
1. visitToken : negated conditional → KILLED |                 && !isInNoBraceControlStatement(ast) | 
| 139 | 1
1. visitToken : negated conditional → KILLED |                 && !isInWhileIdiom(ast)) { | 
| 140 | 1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::log → KILLED |             log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY); | 
| 141 |         } | |
| 142 |     } | |
| 143 | ||
| 144 |     /** | |
| 145 |      * Determines if ast is in the body of a flow control statement without | |
| 146 |      * braces. An example of such a statement would be | |
| 147 |      * <p> | |
| 148 |      * <pre> | |
| 149 |      * if (y < 0) | |
| 150 |      *     x = y; | |
| 151 |      * </pre> | |
| 152 |      * </p> | |
| 153 |      * <p> | |
| 154 |      * This leads to the following AST structure: | |
| 155 |      * </p> | |
| 156 |      * <p> | |
| 157 |      * <pre> | |
| 158 |      * LITERAL_IF | |
| 159 |      *     LPAREN | |
| 160 |      *     EXPR // test | |
| 161 |      *     RPAREN | |
| 162 |      *     EXPR // body | |
| 163 |      *     SEMI | |
| 164 |      * </pre> | |
| 165 |      * </p> | |
| 166 |      * <p> | |
| 167 |      * We need to ensure that ast is in the body and not in the test. | |
| 168 |      * </p> | |
| 169 |      * | |
| 170 |      * @param ast an assignment operator AST | |
| 171 |      * @return whether ast is in the body of a flow control statement | |
| 172 |      */ | |
| 173 |     private static boolean isInNoBraceControlStatement(DetailAST ast) { | |
| 174 |         boolean result = false; | |
| 175 | 1
1. isInNoBraceControlStatement : negated conditional → KILLED |         if (isInContext(ast, CONTROL_CONTEXT)) { | 
| 176 |             final DetailAST expr = ast.getParent(); | |
| 177 |             final AST exprNext = expr.getNextSibling(); | |
| 178 | 1
1. isInNoBraceControlStatement : negated conditional → KILLED |             result = exprNext.getType() == TokenTypes.SEMI; | 
| 179 |         } | |
| 180 | 1
1. isInNoBraceControlStatement : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |         return result; | 
| 181 |     } | |
| 182 | ||
| 183 |     /** | |
| 184 |      * Tests whether the given AST is used in the "assignment in while" idiom. | |
| 185 |      * <pre> | |
| 186 |      * String line; | |
| 187 |      * while ((line = bufferedReader.readLine()) != null) { | |
| 188 |      *    // process the line | |
| 189 |      * } | |
| 190 |      * </pre> | |
| 191 |      * Assignment inside a condition is not a problem here, as the assignment is surrounded by an | |
| 192 |      * extra pair of parentheses. The comparison is {@code != null} and there is no chance that | |
| 193 |      * intention was to write {@code line == reader.readLine()}. | |
| 194 |      * | |
| 195 |      * @param ast assignment AST | |
| 196 |      * @return whether the context of the assignment AST indicates the idiom | |
| 197 |      */ | |
| 198 |     private static boolean isInWhileIdiom(DetailAST ast) { | |
| 199 |         boolean result = false; | |
| 200 | 1
1. isInWhileIdiom : negated conditional → KILLED |         if (isComparison(ast.getParent())) { | 
| 201 |             result = isInContext( | |
| 202 |                     ast.getParent(), ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT); | |
| 203 |         } | |
| 204 | 1
1. isInWhileIdiom : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |         return result; | 
| 205 |     } | |
| 206 | ||
| 207 |     /** | |
| 208 |      * Checks if an AST is a comparison operator. | |
| 209 |      * @param ast the AST to check | |
| 210 |      * @return true iff ast is a comparison operator. | |
| 211 |      */ | |
| 212 |     private static boolean isComparison(DetailAST ast) { | |
| 213 |         final int astType = ast.getType(); | |
| 214 | 3
1. isComparison : changed conditional boundary → KILLED 2. isComparison : negated conditional → KILLED 3. isComparison : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |         return Arrays.binarySearch(COMPARISON_TYPES, astType) >= 0; | 
| 215 |     } | |
| 216 | ||
| 217 |     /** | |
| 218 |      * Tests whether the provided AST is in | |
| 219 |      * one of the given contexts. | |
| 220 |      * | |
| 221 |      * @param ast the AST from which to start walking towards root | |
| 222 |      * @param contextSet the contexts to test against. | |
| 223 |      * | |
| 224 |      * @return whether the parents nodes of ast match one of the allowed type paths. | |
| 225 |      */ | |
| 226 |     private static boolean isInContext(DetailAST ast, int[]... contextSet) { | |
| 227 |         boolean found = false; | |
| 228 | 1
1. isInContext : Changed increment from 1 to -1 → KILLED |         for (int[] element : contextSet) { | 
| 229 |             DetailAST current = ast; | |
| 230 |             for (int anElement : element) { | |
| 231 |                 current = current.getParent(); | |
| 232 | 1
1. isInContext : negated conditional → KILLED |                 if (current.getType() == anElement) { | 
| 233 |                     found = true; | |
| 234 |                 } | |
| 235 |                 else { | |
| 236 |                     found = false; | |
| 237 |                     break; | |
| 238 |                 } | |
| 239 |             } | |
| 240 | ||
| 241 | 1
1. isInContext : negated conditional → KILLED |             if (found) { | 
| 242 |                 break; | |
| 243 |             } | |
| 244 |         } | |
| 245 | 1
1. isInContext : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |         return found; | 
| 246 |     } | |
| 247 | ||
| 248 | } | |
| Mutations | ||
| 109 | 1.1 | |
| 114 | 1.1 | |
| 119 | 1.1 | |
| 137 | 1.1 | |
| 138 | 1.1 | |
| 139 | 1.1 | |
| 140 | 1.1 | |
| 175 | 1.1 | |
| 178 | 1.1 | |
| 180 | 1.1 | |
| 200 | 1.1 | |
| 204 | 1.1 | |
| 214 | 1.1 2.2 3.3 | |
| 228 | 1.1 | |
| 232 | 1.1 | |
| 241 | 1.1 | |
| 245 | 1.1 |