001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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.Locale;
023import java.util.Objects;
024import java.util.Set;
025import java.util.regex.Pattern;
026
027import com.google.common.collect.Sets;
028import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.Scope;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
033import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
034import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
035
036/**
037 * Checks that a local variable or a parameter does not shadow
038 * a field that is defined in the same class.
039 *
040 * <p>An example of how to configure the check is:
041 * <pre>
042 * &lt;module name="HiddenField"/&gt;
043 * </pre>
044 *
045 * <p>An example of how to configure the check so that it checks variables but not
046 * parameters is:
047 * <pre>
048 * &lt;module name="HiddenField"&gt;
049 *    &lt;property name="tokens" value="VARIABLE_DEF"/&gt;
050 * &lt;/module&gt;
051 * </pre>
052 *
053 * <p>An example of how to configure the check so that it ignores the parameter of
054 * a setter method is:
055 * <pre>
056 * &lt;module name="HiddenField"&gt;
057 *    &lt;property name="ignoreSetter" value="true"/&gt;
058 * &lt;/module&gt;
059 * </pre>
060 *
061 * <p>A method is recognized as a setter if it is in the following form
062 * <pre>
063 * ${returnType} set${Name}(${anyType} ${name}) { ... }
064 * </pre>
065 * where ${anyType} is any primitive type, class or interface name;
066 * ${name} is name of the variable that is being set and ${Name} its
067 * capitalized form that appears in the method name. By default it is expected
068 * that setter returns void, i.e. ${returnType} is 'void'. For example
069 * <pre>
070 * void setTime(long time) { ... }
071 * </pre>
072 * Any other return types will not let method match a setter pattern. However,
073 * by setting <em>setterCanReturnItsClass</em> property to <em>true</em>
074 * definition of a setter is expanded, so that setter return type can also be
075 * a class in which setter is declared. For example
076 * <pre>
077 * class PageBuilder {
078 *   PageBuilder setName(String name) { ... }
079 * }
080 * </pre>
081 * Such methods are known as chain-setters and a common when Builder-pattern
082 * is used. Property <em>setterCanReturnItsClass</em> has effect only if
083 * <em>ignoreSetter</em> is set to true.
084 *
085 * <p>An example of how to configure the check so that it ignores the parameter
086 * of either a setter that returns void or a chain-setter.
087 * <pre>
088 * &lt;module name="HiddenField"&gt;
089 *    &lt;property name="ignoreSetter" value="true"/&gt;
090 *    &lt;property name="setterCanReturnItsClass" value="true"/&gt;
091 * &lt;/module&gt;
092 * </pre>
093 *
094 * <p>An example of how to configure the check so that it ignores constructor
095 * parameters is:
096 * <pre>
097 * &lt;module name="HiddenField"&gt;
098 *    &lt;property name="ignoreConstructorParameter" value="true"/&gt;
099 * &lt;/module&gt;
100 * </pre>
101 *
102 * <p>An example of how to configure the check so that it ignores variables and parameters
103 * named 'test':
104 * <pre>
105 * &lt;module name="HiddenField"&gt;
106 *    &lt;property name="ignoreFormat" value="^test$"/&gt;
107 * &lt;/module&gt;
108 * </pre>
109 *
110 * <pre>
111 * {@code
112 * class SomeClass
113 * {
114 *     private List&lt;String&gt; test;
115 *
116 *     private void addTest(List&lt;String&gt; test) // no violation
117 *     {
118 *         this.test.addAll(test);
119 *     }
120 *
121 *     private void foo()
122 *     {
123 *         final List&lt;String&gt; test = new ArrayList&lt;&gt;(); // no violation
124 *         ...
125 *     }
126 * }
127 * }
128 * </pre>
129 *
130 * @author Dmitri Priimak
131 */
132public class HiddenFieldCheck
133    extends AbstractCheck {
134    /**
135     * A key is pointing to the warning message text in "messages.properties"
136     * file.
137     */
138    public static final String MSG_KEY = "hidden.field";
139
140    /** Stack of sets of field names,
141     * one for each class of a set of nested classes.
142     */
143    private FieldFrame frame;
144
145    /** Pattern for names of variables and parameters to ignore. */
146    private Pattern regexp;
147
148    /** Controls whether to check the parameter of a property setter method. */
149    private boolean ignoreSetter;
150
151    /**
152     * If ignoreSetter is set to true then this variable controls what
153     * the setter method can return By default setter must return void.
154     * However, is this variable is set to true then setter can also
155     * return class in which is declared.
156     */
157    private boolean setterCanReturnItsClass;
158
159    /** Controls whether to check the parameter of a constructor. */
160    private boolean ignoreConstructorParameter;
161
162    /** Controls whether to check the parameter of abstract methods. */
163    private boolean ignoreAbstractMethods;
164
165    @Override
166    public int[] getDefaultTokens() {
167        return getAcceptableTokens();
168    }
169
170    @Override
171    public int[] getAcceptableTokens() {
172        return new int[] {
173            TokenTypes.VARIABLE_DEF,
174            TokenTypes.PARAMETER_DEF,
175            TokenTypes.CLASS_DEF,
176            TokenTypes.ENUM_DEF,
177            TokenTypes.ENUM_CONSTANT_DEF,
178            TokenTypes.LAMBDA,
179        };
180    }
181
182    @Override
183    public int[] getRequiredTokens() {
184        return new int[] {
185            TokenTypes.CLASS_DEF,
186            TokenTypes.ENUM_DEF,
187            TokenTypes.ENUM_CONSTANT_DEF,
188        };
189    }
190
191    @Override
192    public void beginTree(DetailAST rootAST) {
193        frame = new FieldFrame(null, true, null);
194    }
195
196    @Override
197    public void visitToken(DetailAST ast) {
198        final int type = ast.getType();
199        switch (type) {
200            case TokenTypes.VARIABLE_DEF:
201            case TokenTypes.PARAMETER_DEF:
202                processVariable(ast);
203                break;
204            case TokenTypes.LAMBDA:
205                processLambda(ast);
206                break;
207            default:
208                visitOtherTokens(ast, type);
209        }
210    }
211
212    /**
213     * Process a lambda token.
214     * Checks whether a lambda parameter shadows a field.
215     * Note, that when parameter of lambda expression is untyped,
216     * ANTLR parses the parameter as an identifier.
217     * @param ast the lambda token.
218     */
219    private void processLambda(DetailAST ast) {
220        final DetailAST firstChild = ast.getFirstChild();
221        if (firstChild.getType() == TokenTypes.IDENT) {
222            final String untypedLambdaParameterName = firstChild.getText();
223            if (isStaticOrInstanceField(firstChild, untypedLambdaParameterName)) {
224                log(firstChild, MSG_KEY, untypedLambdaParameterName);
225            }
226        }
227        else {
228            // Type of lambda parameter is not omitted.
229            processVariable(ast);
230        }
231    }
232
233    /**
234     * Called to process tokens other than {@link TokenTypes#VARIABLE_DEF}
235     * and {@link TokenTypes#PARAMETER_DEF}.
236     *
237     * @param ast token to process
238     * @param type type of the token
239     */
240    private void visitOtherTokens(DetailAST ast, int type) {
241        //A more thorough check of enum constant class bodies is
242        //possible (checking for hidden fields against the enum
243        //class body in addition to enum constant class bodies)
244        //but not attempted as it seems out of the scope of this
245        //check.
246        final DetailAST typeMods = ast.findFirstToken(TokenTypes.MODIFIERS);
247        final boolean isStaticInnerType =
248                typeMods != null
249                        && typeMods.branchContains(TokenTypes.LITERAL_STATIC);
250        final String frameName;
251
252        if (type == TokenTypes.CLASS_DEF || type == TokenTypes.ENUM_DEF) {
253            frameName = ast.findFirstToken(TokenTypes.IDENT).getText();
254        }
255        else {
256            frameName = null;
257        }
258        final FieldFrame newFrame = new FieldFrame(frame, isStaticInnerType, frameName);
259
260        //add fields to container
261        final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
262        // enum constants may not have bodies
263        if (objBlock != null) {
264            DetailAST child = objBlock.getFirstChild();
265            while (child != null) {
266                if (child.getType() == TokenTypes.VARIABLE_DEF) {
267                    final String name =
268                        child.findFirstToken(TokenTypes.IDENT).getText();
269                    final DetailAST mods =
270                        child.findFirstToken(TokenTypes.MODIFIERS);
271                    if (mods.branchContains(TokenTypes.LITERAL_STATIC)) {
272                        newFrame.addStaticField(name);
273                    }
274                    else {
275                        newFrame.addInstanceField(name);
276                    }
277                }
278                child = child.getNextSibling();
279            }
280        }
281        // push container
282        frame = newFrame;
283    }
284
285    @Override
286    public void leaveToken(DetailAST ast) {
287        if (ast.getType() == TokenTypes.CLASS_DEF
288            || ast.getType() == TokenTypes.ENUM_DEF
289            || ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
290            //pop
291            frame = frame.getParent();
292        }
293    }
294
295    /**
296     * Process a variable token.
297     * Check whether a local variable or parameter shadows a field.
298     * Store a field for later comparison with local variables and parameters.
299     * @param ast the variable token.
300     */
301    private void processVariable(DetailAST ast) {
302        if (!ScopeUtils.isInInterfaceOrAnnotationBlock(ast)
303            && !CheckUtils.isReceiverParameter(ast)
304            && (ScopeUtils.isLocalVariableDef(ast)
305                || ast.getType() == TokenTypes.PARAMETER_DEF)) {
306            // local variable or parameter. Does it shadow a field?
307            final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT);
308            final String name = nameAST.getText();
309
310            if ((isStaticFieldHiddenFromAnonymousClass(ast, name)
311                        || isStaticOrInstanceField(ast, name))
312                    && !isMatchingRegexp(name)
313                    && !isIgnoredParam(ast, name)) {
314                log(nameAST, MSG_KEY, name);
315            }
316        }
317    }
318
319    /**
320     * Checks whether a static field is hidden from closure.
321     * @param nameAST local variable or parameter.
322     * @param name field name.
323     * @return true if static field is hidden from closure.
324     */
325    private boolean isStaticFieldHiddenFromAnonymousClass(DetailAST nameAST, String name) {
326        return isInStatic(nameAST)
327            && frame.containsStaticField(name);
328    }
329
330    /**
331     * Checks whether method or constructor parameter is ignored.
332     * @param ast the parameter token.
333     * @param name the parameter name.
334     * @return true if parameter is ignored.
335     */
336    private boolean isIgnoredParam(DetailAST ast, String name) {
337        return isIgnoredSetterParam(ast, name)
338            || isIgnoredConstructorParam(ast)
339            || isIgnoredParamOfAbstractMethod(ast);
340    }
341
342    /**
343     * Check for static or instance field.
344     * @param ast token
345     * @param name identifier of token
346     * @return true if static or instance field
347     */
348    private boolean isStaticOrInstanceField(DetailAST ast, String name) {
349        return frame.containsStaticField(name)
350                || !isInStatic(ast) && frame.containsInstanceField(name);
351    }
352
353    /**
354     * Check name by regExp.
355     * @param name string value to check
356     * @return true is regexp is matching
357     */
358    private boolean isMatchingRegexp(String name) {
359        return regexp != null && regexp.matcher(name).find();
360    }
361
362    /**
363     * Determines whether an AST node is in a static method or static
364     * initializer.
365     * @param ast the node to check.
366     * @return true if ast is in a static method or a static block;
367     */
368    private static boolean isInStatic(DetailAST ast) {
369        DetailAST parent = ast.getParent();
370        boolean inStatic = false;
371
372        while (parent != null && !inStatic) {
373            if (parent.getType() == TokenTypes.STATIC_INIT) {
374                inStatic = true;
375            }
376            else if (parent.getType() == TokenTypes.METHOD_DEF
377                        && !ScopeUtils.isInScope(parent, Scope.ANONINNER)
378                        || parent.getType() == TokenTypes.VARIABLE_DEF) {
379                final DetailAST mods =
380                    parent.findFirstToken(TokenTypes.MODIFIERS);
381                inStatic = mods.branchContains(TokenTypes.LITERAL_STATIC);
382                break;
383            }
384            else {
385                parent = parent.getParent();
386            }
387        }
388        return inStatic;
389    }
390
391    /**
392     * Decides whether to ignore an AST node that is the parameter of a
393     * setter method, where the property setter method for field 'xyz' has
394     * name 'setXyz', one parameter named 'xyz', and return type void
395     * (default behavior) or return type is name of the class in which
396     * such method is declared (allowed only if
397     * {@link #setSetterCanReturnItsClass(boolean)} is called with
398     * value <em>true</em>)
399     *
400     * @param ast the AST to check.
401     * @param name the name of ast.
402     * @return true if ast should be ignored because check property
403     *     ignoreSetter is true and ast is the parameter of a setter method.
404     */
405    private boolean isIgnoredSetterParam(DetailAST ast, String name) {
406        if (ignoreSetter && ast.getType() == TokenTypes.PARAMETER_DEF) {
407            final DetailAST parametersAST = ast.getParent();
408            final DetailAST methodAST = parametersAST.getParent();
409            if (parametersAST.getChildCount() == 1
410                && methodAST.getType() == TokenTypes.METHOD_DEF
411                && isSetterMethod(methodAST, name)) {
412                return true;
413            }
414        }
415        return false;
416    }
417
418    /**
419     * Determine if a specific method identified by methodAST and a single
420     * variable name aName is a setter. This recognition partially depends
421     * on mSetterCanReturnItsClass property.
422     *
423     * @param aMethodAST AST corresponding to a method call
424     * @param aName name of single parameter of this method.
425     * @return true of false indicating of method is a setter or not.
426     */
427    private boolean isSetterMethod(DetailAST aMethodAST, String aName) {
428        final String methodName =
429            aMethodAST.findFirstToken(TokenTypes.IDENT).getText();
430        boolean isSetterMethod = false;
431
432        if (("set" + capitalize(aName)).equals(methodName)) {
433            // method name did match set${Name}(${anyType} ${aName})
434            // where ${Name} is capitalized version of ${aName}
435            // therefore this method is potentially a setter
436            final DetailAST typeAST = aMethodAST.findFirstToken(TokenTypes.TYPE);
437            final String returnType = typeAST.getFirstChild().getText();
438            if (typeAST.branchContains(TokenTypes.LITERAL_VOID)
439                    || setterCanReturnItsClass && frame.isEmbeddedIn(returnType)) {
440                // this method has signature
441                //
442                //     void set${Name}(${anyType} ${name})
443                //
444                // and therefore considered to be a setter
445                //
446                // or
447                //
448                // return type is not void, but it is the same as the class
449                // where method is declared and and mSetterCanReturnItsClass
450                // is set to true
451                isSetterMethod = true;
452            }
453        }
454
455        return isSetterMethod;
456    }
457
458    /**
459     * Capitalizes a given property name the way we expect to see it in
460     * a setter name.
461     * @param name a property name
462     * @return capitalized property name
463     */
464    private static String capitalize(final String name) {
465        String setterName = name;
466        // we should not capitalize the first character if the second
467        // one is a capital one, since according to JavaBeans spec
468        // setXYzz() is a setter for XYzz property, not for xYzz one.
469        if (name.length() == 1 || !Character.isUpperCase(name.charAt(1))) {
470            setterName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
471        }
472        return setterName;
473    }
474
475    /**
476     * Decides whether to ignore an AST node that is the parameter of a
477     * constructor.
478     * @param ast the AST to check.
479     * @return true if ast should be ignored because check property
480     *     ignoreConstructorParameter is true and ast is a constructor parameter.
481     */
482    private boolean isIgnoredConstructorParam(DetailAST ast) {
483        boolean result = false;
484        if (ignoreConstructorParameter
485                && ast.getType() == TokenTypes.PARAMETER_DEF) {
486            final DetailAST parametersAST = ast.getParent();
487            final DetailAST constructorAST = parametersAST.getParent();
488            result = constructorAST.getType() == TokenTypes.CTOR_DEF;
489        }
490        return result;
491    }
492
493    /**
494     * Decides whether to ignore an AST node that is the parameter of an
495     * abstract method.
496     * @param ast the AST to check.
497     * @return true if ast should be ignored because check property
498     *     ignoreAbstractMethods is true and ast is a parameter of abstract methods.
499     */
500    private boolean isIgnoredParamOfAbstractMethod(DetailAST ast) {
501        boolean result = false;
502        if (ignoreAbstractMethods
503                && ast.getType() == TokenTypes.PARAMETER_DEF) {
504            final DetailAST method = ast.getParent().getParent();
505            if (method.getType() == TokenTypes.METHOD_DEF) {
506                final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS);
507                result = mods.branchContains(TokenTypes.ABSTRACT);
508            }
509        }
510        return result;
511    }
512
513    /**
514     * Set the ignore format to the specified regular expression.
515     * @param format a {@code String} value
516     */
517    public void setIgnoreFormat(String format) {
518        regexp = CommonUtils.createPattern(format);
519    }
520
521    /**
522     * Set whether to ignore the parameter of a property setter method.
523     * @param ignoreSetter decide whether to ignore the parameter of
524     *     a property setter method.
525     */
526    public void setIgnoreSetter(boolean ignoreSetter) {
527        this.ignoreSetter = ignoreSetter;
528    }
529
530    /**
531     * Controls if setter can return only void (default behavior) or it
532     * can also return class in which it is declared.
533     *
534     * @param aSetterCanReturnItsClass if true then setter can return
535     *        either void or class in which it is declared. If false then
536     *        in order to be recognized as setter method (otherwise
537     *        already recognized as a setter) must return void.  Later is
538     *        the default behavior.
539     */
540    public void setSetterCanReturnItsClass(
541        boolean aSetterCanReturnItsClass) {
542        setterCanReturnItsClass = aSetterCanReturnItsClass;
543    }
544
545    /**
546     * Set whether to ignore constructor parameters.
547     * @param ignoreConstructorParameter decide whether to ignore
548     *     constructor parameters.
549     */
550    public void setIgnoreConstructorParameter(
551        boolean ignoreConstructorParameter) {
552        this.ignoreConstructorParameter = ignoreConstructorParameter;
553    }
554
555    /**
556     * Set whether to ignore parameters of abstract methods.
557     * @param ignoreAbstractMethods decide whether to ignore
558     *     parameters of abstract methods.
559     */
560    public void setIgnoreAbstractMethods(
561        boolean ignoreAbstractMethods) {
562        this.ignoreAbstractMethods = ignoreAbstractMethods;
563    }
564
565    /**
566     * Holds the names of static and instance fields of a type.
567     * @author Rick Giles
568     */
569    private static class FieldFrame {
570        /** Name of the frame, such name of the class or enum declaration. */
571        private final String frameName;
572
573        /** Is this a static inner type. */
574        private final boolean staticType;
575
576        /** Parent frame. */
577        private final FieldFrame parent;
578
579        /** Set of instance field names. */
580        private final Set<String> instanceFields = Sets.newHashSet();
581
582        /** Set of static field names. */
583        private final Set<String> staticFields = Sets.newHashSet();
584
585        /**
586         * Creates new frame.
587         * @param parent parent frame.
588         * @param staticType is this a static inner type (class or enum).
589         * @param frameName name associated with the frame, which can be a
590         */
591        FieldFrame(FieldFrame parent, boolean staticType, String frameName) {
592            this.parent = parent;
593            this.staticType = staticType;
594            this.frameName = frameName;
595        }
596
597        /**
598         * Adds an instance field to this FieldFrame.
599         * @param field  the name of the instance field.
600         */
601        public void addInstanceField(String field) {
602            instanceFields.add(field);
603        }
604
605        /**
606         * Adds a static field to this FieldFrame.
607         * @param field  the name of the instance field.
608         */
609        public void addStaticField(String field) {
610            staticFields.add(field);
611        }
612
613        /**
614         * Determines whether this FieldFrame contains an instance field.
615         * @param field the field to check.
616         * @return true if this FieldFrame contains instance field field.
617         */
618        public boolean containsInstanceField(String field) {
619            return instanceFields.contains(field)
620                    || parent != null
621                    && !staticType
622                    && parent.containsInstanceField(field);
623
624        }
625
626        /**
627         * Determines whether this FieldFrame contains a static field.
628         * @param field the field to check.
629         * @return true if this FieldFrame contains static field field.
630         */
631        public boolean containsStaticField(String field) {
632            return staticFields.contains(field)
633                    || parent != null
634                    && parent.containsStaticField(field);
635        }
636
637        /**
638         * Getter for parent frame.
639         * @return parent frame.
640         */
641        public FieldFrame getParent() {
642            return parent;
643        }
644
645        /**
646         * Check if current frame is embedded in class or enum with
647         * specific name.
648         *
649         * @param classOrEnumName name of class or enum that we are looking
650         *     for in the chain of field frames.
651         *
652         * @return true if current frame is embedded in class or enum
653         *     with name classOrNameName
654         */
655        private boolean isEmbeddedIn(String classOrEnumName) {
656            FieldFrame currentFrame = this;
657            while (currentFrame != null) {
658                if (Objects.equals(currentFrame.frameName, classOrEnumName)) {
659                    return true;
660                }
661                currentFrame = currentFrame.parent;
662            }
663            return false;
664        }
665    }
666}