001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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 antlr.collections.AST; 025 026import com.puppycrawl.tools.checkstyle.api.Check; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029 030/** 031 * <p> 032 * Checks for assignments in subexpressions, such as in 033 * <code>String s = Integer.toString(i = 2);</code>. 034 * </p> 035 * <p> 036 * Rationale: With the exception of <code>for</code> iterators, all assignments 037 * should occur in their own toplevel statement to increase readability. 038 * With inner assignments like the above it is difficult to see all places 039 * where a variable is set. 040 * </p> 041 * 042 * @author lkuehne 043 */ 044public class InnerAssignmentCheck 045 extends Check 046{ 047 /** 048 * list of allowed AST types from an assignement AST node 049 * towards the root. 050 */ 051 private static final int[][] ALLOWED_ASSIGMENT_CONTEXT = { 052 {TokenTypes.EXPR, TokenTypes.SLIST}, 053 {TokenTypes.VARIABLE_DEF}, 054 {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_INIT}, 055 {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_ITERATOR}, 056 {TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR}, 057 { 058 TokenTypes.RESOURCE, 059 TokenTypes.RESOURCES, 060 TokenTypes.RESOURCE_SPECIFICATION, 061 }, 062 }; 063 064 /** 065 * list of allowed AST types from an assignement AST node 066 * towards the root. 067 */ 068 private static final int[][] CONTROL_CONTEXT = { 069 {TokenTypes.EXPR, TokenTypes.LITERAL_DO}, 070 {TokenTypes.EXPR, TokenTypes.LITERAL_FOR}, 071 {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE}, 072 {TokenTypes.EXPR, TokenTypes.LITERAL_IF}, 073 {TokenTypes.EXPR, TokenTypes.LITERAL_ELSE}, 074 }; 075 076 /** 077 * list of allowed AST types from a comparison node (above an assignement) 078 * towards the root. 079 */ 080 private static final int[][] ALLOWED_ASSIGMENT_IN_COMPARISON_CONTEXT = { 081 {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE, }, 082 }; 083 084 /** 085 * The token types that identify comparison operators. 086 */ 087 private static final int[] COMPARISON_TYPES = { 088 TokenTypes.EQUAL, 089 TokenTypes.GE, 090 TokenTypes.GT, 091 TokenTypes.LE, 092 TokenTypes.LT, 093 TokenTypes.NOT_EQUAL, 094 }; 095 096 static { 097 Arrays.sort(COMPARISON_TYPES); 098 } 099 100 @Override 101 public int[] getDefaultTokens() 102 { 103 return new int[] { 104 TokenTypes.ASSIGN, // '=' 105 TokenTypes.DIV_ASSIGN, // "/=" 106 TokenTypes.PLUS_ASSIGN, // "+=" 107 TokenTypes.MINUS_ASSIGN, //"-=" 108 TokenTypes.STAR_ASSIGN, // "*=" 109 TokenTypes.MOD_ASSIGN, // "%=" 110 TokenTypes.SR_ASSIGN, // ">>=" 111 TokenTypes.BSR_ASSIGN, // ">>>=" 112 TokenTypes.SL_ASSIGN, // "<<=" 113 TokenTypes.BXOR_ASSIGN, // "^=" 114 TokenTypes.BOR_ASSIGN, // "|=" 115 TokenTypes.BAND_ASSIGN, // "&=" 116 }; 117 } 118 119 @Override 120 public void visitToken(DetailAST aAST) 121 { 122 if (isInContext(aAST, ALLOWED_ASSIGMENT_CONTEXT)) { 123 return; 124 } 125 126 if (isInNoBraceControlStatement(aAST)) { 127 return; 128 } 129 130 if (isInWhileIdiom(aAST)) { 131 return; 132 } 133 134 log(aAST.getLineNo(), aAST.getColumnNo(), "assignment.inner.avoid"); 135 } 136 137 /** 138 * Determines if aAST is in the body of a flow control statement without 139 * braces. An example of such a statement would be 140 * <p> 141 * <pre> 142 * if (y < 0) 143 * x = y; 144 * </pre> 145 * </p> 146 * <p> 147 * This leads to the following AST structure: 148 * </p> 149 * <p> 150 * <pre> 151 * LITERAL_IF 152 * LPAREN 153 * EXPR // test 154 * RPAREN 155 * EXPR // body 156 * SEMI 157 * </pre> 158 * </p> 159 * <p> 160 * We need to ensure that aAST is in the body and not in the test. 161 * </p> 162 * 163 * @param aAST an assignment operator AST 164 * @return whether aAST is in the body of a flow control statement 165 */ 166 private static boolean isInNoBraceControlStatement(DetailAST aAST) 167 { 168 if (!isInContext(aAST, CONTROL_CONTEXT)) { 169 return false; 170 } 171 final DetailAST expr = aAST.getParent(); 172 final AST exprNext = expr.getNextSibling(); 173 return (exprNext != null) && (exprNext.getType() == TokenTypes.SEMI); 174 } 175 176 /** 177 * Tests whether the given AST is used in the "assignment in while test" 178 * idiom. 179 * <p> 180 * <pre> 181 * while ((b = is.read()) != -1) { 182 * // work with b 183 * } 184 * </pre> 185 * </p> 186 * 187 * @param aAST assignment AST 188 * @return whether the context of the assignemt AST indicates the idiom 189 */ 190 private boolean isInWhileIdiom(DetailAST aAST) 191 { 192 if (!isComparison(aAST.getParent())) { 193 return false; 194 } 195 return isInContext( 196 aAST.getParent(), ALLOWED_ASSIGMENT_IN_COMPARISON_CONTEXT); 197 } 198 199 /** 200 * Checks if an AST is a comparison operator. 201 * @param aAST the AST to check 202 * @return true iff aAST is a comparison operator. 203 */ 204 private static boolean isComparison(DetailAST aAST) 205 { 206 final int astType = aAST.getType(); 207 return (Arrays.binarySearch(COMPARISON_TYPES, astType) >= 0); 208 } 209 210 /** 211 * Tests whether the provided AST is in 212 * one of the given contexts. 213 * 214 * @param aAST the AST from which to start walking towards root 215 * @param aContextSet the contexts to test against. 216 * 217 * @return whether the parents nodes of aAST match 218 * one of the allowed type paths 219 */ 220 private static boolean isInContext(DetailAST aAST, int[][] aContextSet) 221 { 222 for (int[] element : aContextSet) { 223 DetailAST current = aAST; 224 final int len = element.length; 225 for (int j = 0; j < len; j++) { 226 current = current.getParent(); 227 final int expectedType = element[j]; 228 if ((current == null) || (current.getType() != expectedType)) { 229 break; 230 } 231 if (j == len - 1) { 232 return true; 233 } 234 } 235 } 236 return false; 237 } 238}