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> mCurrentVariables = FastStack.newInstance();
035    /** Stack of block parameters. */
036    private final FastStack<FastStack<String>> mVariableStack =
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 aRootAST)
074    {
075        // clear data
076        mCurrentVariables.clear();
077        mVariableStack.clear();
078    }
079
080    @Override
081    public void visitToken(DetailAST aAST)
082    {
083        switch (aAST.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(aAST);
108            break;
109        default:
110            throw new IllegalStateException(aAST.toString());
111        }
112    }
113
114
115    @Override
116    public void leaveToken(DetailAST aAST)
117    {
118        switch (aAST.getType()) {
119        case TokenTypes.FOR_ITERATOR:
120            leaveForIter(aAST.getParent());
121            break;
122        case TokenTypes.FOR_EACH_CLAUSE:
123            leaveForEach(aAST);
124            break;
125        case TokenTypes.LITERAL_FOR:
126            leaveForDef(aAST);
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(aAST.toString());
151        }
152    }
153
154    /**
155     * Enters an inner class, which requires a new variable set.
156     */
157    private void enterBlock()
158    {
159        mVariableStack.push(mCurrentVariables);
160        mCurrentVariables = FastStack.newInstance();
161
162    }
163    /**
164     * Leave an inner class, so restore variable set.
165     */
166    private void exitBlock()
167    {
168        mCurrentVariables = mVariableStack.pop();
169    }
170
171    /**
172     * Check if ident is parameter.
173     * @param aAST ident to check.
174     */
175    private void checkIdent(DetailAST aAST)
176    {
177        if ((mCurrentVariables != null) && !mCurrentVariables.isEmpty()) {
178            final DetailAST identAST = aAST.getFirstChild();
179
180            if ((identAST != null)
181                && (identAST.getType() == TokenTypes.IDENT)
182                && mCurrentVariables.contains(identAST.getText()))
183            {
184                log(aAST.getLineNo(), aAST.getColumnNo(),
185                    "modified.control.variable", identAST.getText());
186            }
187        }
188    }
189
190    /**
191     * Push current variables to the stack.
192     * @param aAST a for definition.
193     */
194    private void leaveForIter(DetailAST aAST)
195    {
196        final DetailAST forInitAST = aAST.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                mCurrentVariables.push(param.getText());
207            }
208        }
209    }
210
211    /**
212     * Push current variables to the stack.
213     * @param aForEach a for-each clause
214     */
215    private void leaveForEach(DetailAST aForEach)
216    {
217        final DetailAST paramDef =
218            aForEach.findFirstToken(TokenTypes.VARIABLE_DEF);
219        final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
220        mCurrentVariables.push(paramName.getText());
221    }
222
223    /**
224     * Pops the variables from the stack.
225     * @param aAST a for definition.
226     */
227    private void leaveForDef(DetailAST aAST)
228    {
229        final DetailAST forInitAST = aAST.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                    mCurrentVariables.pop();
239                }
240            }
241        }
242        else {
243            // this is for-each loop, just pop veriables
244            mCurrentVariables.pop();
245        }
246    }
247}