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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.coding;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FastStack;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025/**
026 * Check for ensuring that for loop control variables are not modified
027 * inside the for block.
028 *
029 * @author Daniel Grenner
030 */
031public final class ModifiedControlVariableCheck extends Check
032{
033    /** Current set of parameters. */
034    private FastStack<String> currentVariables = FastStack.newInstance();
035    /** Stack of block parameters. */
036    private final FastStack<FastStack<String>> variableStack =
037        FastStack.newInstance();
038
039    @Override
040    public int[] getDefaultTokens()
041    {
042        return new int[] {
043            TokenTypes.OBJBLOCK,
044            TokenTypes.LITERAL_FOR,
045            TokenTypes.FOR_ITERATOR,
046            TokenTypes.FOR_EACH_CLAUSE,
047            TokenTypes.ASSIGN,
048            TokenTypes.PLUS_ASSIGN,
049            TokenTypes.MINUS_ASSIGN,
050            TokenTypes.STAR_ASSIGN,
051            TokenTypes.DIV_ASSIGN,
052            TokenTypes.MOD_ASSIGN,
053            TokenTypes.SR_ASSIGN,
054            TokenTypes.BSR_ASSIGN,
055            TokenTypes.SL_ASSIGN,
056            TokenTypes.BAND_ASSIGN,
057            TokenTypes.BXOR_ASSIGN,
058            TokenTypes.BOR_ASSIGN,
059            TokenTypes.INC,
060            TokenTypes.POST_INC,
061            TokenTypes.DEC,
062            TokenTypes.POST_DEC,
063        };
064    }
065
066    @Override
067    public int[] getRequiredTokens()
068    {
069        return getDefaultTokens();
070    }
071
072    @Override
073    public void beginTree(DetailAST rootAST)
074    {
075        // clear data
076        currentVariables.clear();
077        variableStack.clear();
078    }
079
080    @Override
081    public void visitToken(DetailAST ast)
082    {
083        switch (ast.getType()) {
084            case TokenTypes.OBJBLOCK:
085                enterBlock();
086                break;
087            case TokenTypes.LITERAL_FOR:
088            case TokenTypes.FOR_ITERATOR:
089            case TokenTypes.FOR_EACH_CLAUSE:
090                break;
091            case TokenTypes.ASSIGN:
092            case TokenTypes.PLUS_ASSIGN:
093            case TokenTypes.MINUS_ASSIGN:
094            case TokenTypes.STAR_ASSIGN:
095            case TokenTypes.DIV_ASSIGN:
096            case TokenTypes.MOD_ASSIGN:
097            case TokenTypes.SR_ASSIGN:
098            case TokenTypes.BSR_ASSIGN:
099            case TokenTypes.SL_ASSIGN:
100            case TokenTypes.BAND_ASSIGN:
101            case TokenTypes.BXOR_ASSIGN:
102            case TokenTypes.BOR_ASSIGN:
103            case TokenTypes.INC:
104            case TokenTypes.POST_INC:
105            case TokenTypes.DEC:
106            case TokenTypes.POST_DEC:
107                checkIdent(ast);
108                break;
109            default:
110                throw new IllegalStateException(ast.toString());
111        }
112    }
113
114
115    @Override
116    public void leaveToken(DetailAST ast)
117    {
118        switch (ast.getType()) {
119            case TokenTypes.FOR_ITERATOR:
120                leaveForIter(ast.getParent());
121                break;
122            case TokenTypes.FOR_EACH_CLAUSE:
123                leaveForEach(ast);
124                break;
125            case TokenTypes.LITERAL_FOR:
126                leaveForDef(ast);
127                break;
128            case TokenTypes.OBJBLOCK:
129                exitBlock();
130                break;
131            case TokenTypes.ASSIGN:
132            case TokenTypes.PLUS_ASSIGN:
133            case TokenTypes.MINUS_ASSIGN:
134            case TokenTypes.STAR_ASSIGN:
135            case TokenTypes.DIV_ASSIGN:
136            case TokenTypes.MOD_ASSIGN:
137            case TokenTypes.SR_ASSIGN:
138            case TokenTypes.BSR_ASSIGN:
139            case TokenTypes.SL_ASSIGN:
140            case TokenTypes.BAND_ASSIGN:
141            case TokenTypes.BXOR_ASSIGN:
142            case TokenTypes.BOR_ASSIGN:
143            case TokenTypes.INC:
144            case TokenTypes.POST_INC:
145            case TokenTypes.DEC:
146            case TokenTypes.POST_DEC:
147                // Do nothing
148                break;
149            default:
150                throw new IllegalStateException(ast.toString());
151        }
152    }
153
154    /**
155     * Enters an inner class, which requires a new variable set.
156     */
157    private void enterBlock()
158    {
159        variableStack.push(currentVariables);
160        currentVariables = FastStack.newInstance();
161
162    }
163    /**
164     * Leave an inner class, so restore variable set.
165     */
166    private void exitBlock()
167    {
168        currentVariables = variableStack.pop();
169    }
170
171    /**
172     * Check if ident is parameter.
173     * @param ast ident to check.
174     */
175    private void checkIdent(DetailAST ast)
176    {
177        if ((currentVariables != null) && !currentVariables.isEmpty()) {
178            final DetailAST identAST = ast.getFirstChild();
179
180            if ((identAST != null)
181                && (identAST.getType() == TokenTypes.IDENT)
182                && currentVariables.contains(identAST.getText()))
183            {
184                log(ast.getLineNo(), ast.getColumnNo(),
185                    "modified.control.variable", identAST.getText());
186            }
187        }
188    }
189
190    /**
191     * Push current variables to the stack.
192     * @param ast a for definition.
193     */
194    private void leaveForIter(DetailAST ast)
195    {
196        final DetailAST forInitAST = ast.findFirstToken(TokenTypes.FOR_INIT);
197        DetailAST parameterDefAST =
198            forInitAST.findFirstToken(TokenTypes.VARIABLE_DEF);
199
200        for (; parameterDefAST != null;
201             parameterDefAST = parameterDefAST.getNextSibling())
202        {
203            if (parameterDefAST.getType() == TokenTypes.VARIABLE_DEF) {
204                final DetailAST param =
205                    parameterDefAST.findFirstToken(TokenTypes.IDENT);
206                currentVariables.push(param.getText());
207            }
208        }
209    }
210
211    /**
212     * Push current variables to the stack.
213     * @param forEach a for-each clause
214     */
215    private void leaveForEach(DetailAST forEach)
216    {
217        final DetailAST paramDef =
218            forEach.findFirstToken(TokenTypes.VARIABLE_DEF);
219        final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
220        currentVariables.push(paramName.getText());
221    }
222
223    /**
224     * Pops the variables from the stack.
225     * @param ast a for definition.
226     */
227    private void leaveForDef(DetailAST ast)
228    {
229        final DetailAST forInitAST = ast.findFirstToken(TokenTypes.FOR_INIT);
230        if (forInitAST != null) {
231            DetailAST parameterDefAST =
232                forInitAST.findFirstToken(TokenTypes.VARIABLE_DEF);
233
234            for (; parameterDefAST != null;
235                 parameterDefAST = parameterDefAST.getNextSibling())
236            {
237                if (parameterDefAST.getType() == TokenTypes.VARIABLE_DEF) {
238                    currentVariables.pop();
239                }
240            }
241        }
242        else {
243            // this is for-each loop, just pop veriables
244            currentVariables.pop();
245        }
246    }
247}