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