001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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.Collections;
023import java.util.HashSet;
024import java.util.Set;
025
026import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
031
032/**
033 * <p>
034 * Checks that any combination of String literals
035 * is on the left side of an {@code equals()} comparison.
036 * Also checks for String literals assigned to some field
037 * (such as {@code someString.equals(anotherString = "text")}).
038 * </p>
039 * <p>Rationale: Calling the {@code equals()} method on String literals
040 * will avoid a potential {@code NullPointerException}. Also, it is
041 * pretty common to see null checks right before equals comparisons,
042 * which is not necessary in the example below.
043 * </p>
044 * <p>
045 * For example, this code:
046 * </p>
047 * <pre>
048 * String nullString = null;
049 * nullString.equals(&quot;My_Sweet_String&quot;);
050 * </pre>
051 * <p>
052 * should be refactored to:
053 * </p>
054 * <pre>
055 * String nullString = null;
056 * &quot;My_Sweet_String&quot;.equals(nullString);
057 * </pre>
058 * <ul>
059 * <li>
060 * Property {@code ignoreEqualsIgnoreCase} - Control whether to ignore
061 * {@code String.equalsIgnoreCase(String)} invocations.
062 * Default value is {@code false}.
063 * </li>
064 * </ul>
065 * <p>
066 * To configure the check:
067 * </p>
068 * <pre>
069 * &lt;module name=&quot;EqualsAvoidNull&quot;/&gt;
070 * </pre>
071 *
072 * @since 5.0
073 */
074@FileStatefulCheck
075public class EqualsAvoidNullCheck extends AbstractCheck {
076
077    /**
078     * A key is pointing to the warning message text in "messages.properties"
079     * file.
080     */
081    public static final String MSG_EQUALS_AVOID_NULL = "equals.avoid.null";
082
083    /**
084     * A key is pointing to the warning message text in "messages.properties"
085     * file.
086     */
087    public static final String MSG_EQUALS_IGNORE_CASE_AVOID_NULL = "equalsIgnoreCase.avoid.null";
088
089    /** Method name for comparison. */
090    private static final String EQUALS = "equals";
091
092    /** Type name for comparison. */
093    private static final String STRING = "String";
094
095    /** Curly for comparison. */
096    private static final String LEFT_CURLY = "{";
097
098    /** Control whether to ignore {@code String.equalsIgnoreCase(String)} invocations. */
099    private boolean ignoreEqualsIgnoreCase;
100
101    /** Stack of sets of field names, one for each class of a set of nested classes. */
102    private FieldFrame currentFrame;
103
104    @Override
105    public int[] getDefaultTokens() {
106        return getRequiredTokens();
107    }
108
109    @Override
110    public int[] getAcceptableTokens() {
111        return getRequiredTokens();
112    }
113
114    @Override
115    public int[] getRequiredTokens() {
116        return new int[] {
117            TokenTypes.METHOD_CALL,
118            TokenTypes.CLASS_DEF,
119            TokenTypes.METHOD_DEF,
120            TokenTypes.LITERAL_FOR,
121            TokenTypes.LITERAL_CATCH,
122            TokenTypes.LITERAL_TRY,
123            TokenTypes.LITERAL_SWITCH,
124            TokenTypes.VARIABLE_DEF,
125            TokenTypes.PARAMETER_DEF,
126            TokenTypes.CTOR_DEF,
127            TokenTypes.SLIST,
128            TokenTypes.OBJBLOCK,
129            TokenTypes.ENUM_DEF,
130            TokenTypes.ENUM_CONSTANT_DEF,
131            TokenTypes.LITERAL_NEW,
132            TokenTypes.LAMBDA,
133        };
134    }
135
136    /**
137     * Setter to control whether to ignore {@code String.equalsIgnoreCase(String)} invocations.
138     * @param newValue whether to ignore checking
139     *    {@code String.equalsIgnoreCase(String)}.
140     */
141    public void setIgnoreEqualsIgnoreCase(boolean newValue) {
142        ignoreEqualsIgnoreCase = newValue;
143    }
144
145    @Override
146    public void beginTree(DetailAST rootAST) {
147        currentFrame = new FieldFrame(null);
148    }
149
150    @Override
151    public void visitToken(final DetailAST ast) {
152        switch (ast.getType()) {
153            case TokenTypes.VARIABLE_DEF:
154            case TokenTypes.PARAMETER_DEF:
155                currentFrame.addField(ast);
156                break;
157            case TokenTypes.METHOD_CALL:
158                processMethodCall(ast);
159                break;
160            case TokenTypes.SLIST:
161                processSlist(ast);
162                break;
163            case TokenTypes.LITERAL_NEW:
164                processLiteralNew(ast);
165                break;
166            case TokenTypes.OBJBLOCK:
167                final int parentType = ast.getParent().getType();
168                if (parentType != TokenTypes.CLASS_DEF && parentType != TokenTypes.ENUM_DEF) {
169                    processFrame(ast);
170                }
171                break;
172            default:
173                processFrame(ast);
174        }
175    }
176
177    @Override
178    public void leaveToken(DetailAST ast) {
179        final int astType = ast.getType();
180        if (astType == TokenTypes.SLIST) {
181            leaveSlist(ast);
182        }
183        else if (astType == TokenTypes.LITERAL_NEW) {
184            leaveLiteralNew(ast);
185        }
186        else if (astType == TokenTypes.OBJBLOCK) {
187            final int parentType = ast.getParent().getType();
188            if (parentType != TokenTypes.CLASS_DEF && parentType != TokenTypes.ENUM_DEF) {
189                currentFrame = currentFrame.getParent();
190            }
191        }
192        else if (astType != TokenTypes.VARIABLE_DEF
193                && astType != TokenTypes.PARAMETER_DEF
194                && astType != TokenTypes.METHOD_CALL) {
195            currentFrame = currentFrame.getParent();
196        }
197    }
198
199    @Override
200    public void finishTree(DetailAST ast) {
201        traverseFieldFrameTree(currentFrame);
202    }
203
204    /**
205     * Determine whether SLIST begins a block, determined by braces, and add it as
206     * a frame in this case.
207     * @param ast SLIST ast.
208     */
209    private void processSlist(DetailAST ast) {
210        if (LEFT_CURLY.equals(ast.getText())) {
211            final FieldFrame frame = new FieldFrame(currentFrame);
212            currentFrame.addChild(frame);
213            currentFrame = frame;
214        }
215    }
216
217    /**
218     * Determine whether SLIST begins a block, determined by braces.
219     * @param ast SLIST ast.
220     */
221    private void leaveSlist(DetailAST ast) {
222        if (LEFT_CURLY.equals(ast.getText())) {
223            currentFrame = currentFrame.getParent();
224        }
225    }
226
227    /**
228     * Process CLASS_DEF, METHOD_DEF, LITERAL_IF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO,
229     * LITERAL_CATCH, LITERAL_TRY, CTOR_DEF, ENUM_DEF, ENUM_CONSTANT_DEF.
230     * @param ast processed ast.
231     */
232    private void processFrame(DetailAST ast) {
233        final FieldFrame frame = new FieldFrame(currentFrame);
234        final int astType = ast.getType();
235        if (astType == TokenTypes.CLASS_DEF
236                || astType == TokenTypes.ENUM_DEF) {
237            frame.setClassOrEnumOrEnumConstDef(true);
238            frame.setFrameName(ast.findFirstToken(TokenTypes.IDENT).getText());
239        }
240        currentFrame.addChild(frame);
241        currentFrame = frame;
242    }
243
244    /**
245     * Add the method call to the current frame if it should be processed.
246     * @param methodCall METHOD_CALL ast.
247     */
248    private void processMethodCall(DetailAST methodCall) {
249        final DetailAST dot = methodCall.getFirstChild();
250        if (dot.getType() == TokenTypes.DOT) {
251            final String methodName = dot.getLastChild().getText();
252            if (EQUALS.equals(methodName)
253                    || !ignoreEqualsIgnoreCase && "equalsIgnoreCase".equals(methodName)) {
254                currentFrame.addMethodCall(methodCall);
255            }
256        }
257    }
258
259    /**
260     * Determine whether LITERAL_NEW is an anonymous class definition and add it as
261     * a frame in this case.
262     * @param ast LITERAL_NEW ast.
263     */
264    private void processLiteralNew(DetailAST ast) {
265        if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) {
266            final FieldFrame frame = new FieldFrame(currentFrame);
267            currentFrame.addChild(frame);
268            currentFrame = frame;
269        }
270    }
271
272    /**
273     * Determine whether LITERAL_NEW is an anonymous class definition and leave
274     * the frame it is in.
275     *
276     * @param ast LITERAL_NEW ast.
277     */
278    private void leaveLiteralNew(DetailAST ast) {
279        if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) {
280            currentFrame = currentFrame.getParent();
281        }
282    }
283
284    /**
285     * Traverse the tree of the field frames to check all equals method calls.
286     * @param frame to check method calls in.
287     */
288    private void traverseFieldFrameTree(FieldFrame frame) {
289        for (FieldFrame child: frame.getChildren()) {
290            traverseFieldFrameTree(child);
291
292            currentFrame = child;
293            child.getMethodCalls().forEach(this::checkMethodCall);
294        }
295    }
296
297    /**
298     * Check whether the method call should be violated.
299     * @param methodCall method call to check.
300     */
301    private void checkMethodCall(DetailAST methodCall) {
302        DetailAST objCalledOn = methodCall.getFirstChild().getFirstChild();
303        if (objCalledOn.getType() == TokenTypes.DOT) {
304            objCalledOn = objCalledOn.getLastChild();
305        }
306        final DetailAST expr = methodCall.findFirstToken(TokenTypes.ELIST).getFirstChild();
307        if (containsOneArgument(methodCall)
308                && containsAllSafeTokens(expr)
309                && isCalledOnStringFieldOrVariable(objCalledOn)) {
310            final String methodName = methodCall.getFirstChild().getLastChild().getText();
311            if (EQUALS.equals(methodName)) {
312                log(methodCall, MSG_EQUALS_AVOID_NULL);
313            }
314            else {
315                log(methodCall, MSG_EQUALS_IGNORE_CASE_AVOID_NULL);
316            }
317        }
318    }
319
320    /**
321     * Verify that method call has one argument.
322     *
323     * @param methodCall METHOD_CALL DetailAST
324     * @return true if method call has one argument.
325     */
326    private static boolean containsOneArgument(DetailAST methodCall) {
327        final DetailAST elist = methodCall.findFirstToken(TokenTypes.ELIST);
328        return elist.getChildCount() == 1;
329    }
330
331    /**
332     * Looks for all "safe" Token combinations in the argument
333     * expression branch.
334     * @param expr the argument expression
335     * @return - true if any child matches the set of tokens, false if not
336     */
337    private static boolean containsAllSafeTokens(final DetailAST expr) {
338        DetailAST arg = expr.getFirstChild();
339        arg = skipVariableAssign(arg);
340
341        boolean argIsNotNull = false;
342        if (arg.getType() == TokenTypes.PLUS) {
343            DetailAST child = arg.getFirstChild();
344            while (child != null
345                    && !argIsNotNull) {
346                argIsNotNull = child.getType() == TokenTypes.STRING_LITERAL
347                        || child.getType() == TokenTypes.IDENT;
348                child = child.getNextSibling();
349            }
350        }
351        else {
352            argIsNotNull = arg.getType() == TokenTypes.STRING_LITERAL;
353        }
354
355        return argIsNotNull;
356    }
357
358    /**
359     * Skips over an inner assign portion of an argument expression.
360     * @param currentAST current token in the argument expression
361     * @return the next relevant token
362     */
363    private static DetailAST skipVariableAssign(final DetailAST currentAST) {
364        DetailAST result = currentAST;
365        while (result.getType() == TokenTypes.LPAREN) {
366            result = result.getNextSibling();
367        }
368        if (result.getType() == TokenTypes.ASSIGN) {
369            result = result.getFirstChild().getNextSibling();
370        }
371        return result;
372    }
373
374    /**
375     * Determine, whether equals method is called on a field of String type.
376     * @param objCalledOn object ast.
377     * @return true if the object is of String type.
378     */
379    private boolean isCalledOnStringFieldOrVariable(DetailAST objCalledOn) {
380        final boolean result;
381        final DetailAST previousSiblingAst = objCalledOn.getPreviousSibling();
382        if (previousSiblingAst == null) {
383            result = isStringFieldOrVariable(objCalledOn);
384        }
385        else {
386            if (previousSiblingAst.getType() == TokenTypes.LITERAL_THIS) {
387                result = isStringFieldOrVariableFromThisInstance(objCalledOn);
388            }
389            else {
390                final String className = previousSiblingAst.getText();
391                result = isStringFieldOrVariableFromClass(objCalledOn, className);
392            }
393        }
394        return result;
395    }
396
397    /**
398     * Whether the field or the variable is of String type.
399     * @param objCalledOn the field or the variable to check.
400     * @return true if the field or the variable is of String type.
401     */
402    private boolean isStringFieldOrVariable(DetailAST objCalledOn) {
403        boolean result = false;
404        final String name = objCalledOn.getText();
405        FieldFrame frame = currentFrame;
406        while (frame != null) {
407            final DetailAST field = frame.findField(name);
408            if (field != null
409                    && (frame.isClassOrEnumOrEnumConstDef()
410                            || checkLineNo(field, objCalledOn))) {
411                result = STRING.equals(getFieldType(field));
412                break;
413            }
414            frame = frame.getParent();
415        }
416        return result;
417    }
418
419    /**
420     * Whether the field or the variable from THIS instance is of String type.
421     * @param objCalledOn the field or the variable from THIS instance to check.
422     * @return true if the field or the variable from THIS instance is of String type.
423     */
424    private boolean isStringFieldOrVariableFromThisInstance(DetailAST objCalledOn) {
425        final String name = objCalledOn.getText();
426        final DetailAST field = getObjectFrame(currentFrame).findField(name);
427        return STRING.equals(getFieldType(field));
428    }
429
430    /**
431     * Whether the field or the variable from the specified class is of String type.
432     * @param objCalledOn the field or the variable from the specified class to check.
433     * @param className the name of the class to check in.
434     * @return true if the field or the variable from the specified class is of String type.
435     */
436    private boolean isStringFieldOrVariableFromClass(DetailAST objCalledOn,
437            final String className) {
438        boolean result = false;
439        final String name = objCalledOn.getText();
440        FieldFrame frame = getObjectFrame(currentFrame);
441        while (frame != null) {
442            if (className.equals(frame.getFrameName())) {
443                final DetailAST field = frame.findField(name);
444                result = STRING.equals(getFieldType(field));
445                break;
446            }
447            frame = getObjectFrame(frame.getParent());
448        }
449        return result;
450    }
451
452    /**
453     * Get the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF.
454     * @param frame to start the search from.
455     * @return the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF.
456     */
457    private static FieldFrame getObjectFrame(FieldFrame frame) {
458        FieldFrame objectFrame = frame;
459        while (objectFrame != null && !objectFrame.isClassOrEnumOrEnumConstDef()) {
460            objectFrame = objectFrame.getParent();
461        }
462        return objectFrame;
463    }
464
465    /**
466     * Check whether the field is declared before the method call in case of
467     * methods and initialization blocks.
468     * @param field field to check.
469     * @param objCalledOn object equals method called on.
470     * @return true if the field is declared before the method call.
471     */
472    private static boolean checkLineNo(DetailAST field, DetailAST objCalledOn) {
473        boolean result = false;
474        if (CheckUtil.isBeforeInSource(field, objCalledOn)) {
475            result = true;
476        }
477        return result;
478    }
479
480    /**
481     * Get field type.
482     * @param field to get the type from.
483     * @return type of the field.
484     */
485    private static String getFieldType(DetailAST field) {
486        String fieldType = null;
487        final DetailAST identAst = field.findFirstToken(TokenTypes.TYPE)
488                .findFirstToken(TokenTypes.IDENT);
489        if (identAst != null) {
490            fieldType = identAst.getText();
491        }
492        return fieldType;
493    }
494
495    /**
496     * Holds the names of fields of a type.
497     */
498    private static class FieldFrame {
499
500        /** Parent frame. */
501        private final FieldFrame parent;
502
503        /** Set of frame's children. */
504        private final Set<FieldFrame> children = new HashSet<>();
505
506        /** Set of fields. */
507        private final Set<DetailAST> fields = new HashSet<>();
508
509        /** Set of equals calls. */
510        private final Set<DetailAST> methodCalls = new HashSet<>();
511
512        /** Name of the class, enum or enum constant declaration. */
513        private String frameName;
514
515        /** Whether the frame is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. */
516        private boolean classOrEnumOrEnumConstDef;
517
518        /**
519         * Creates new frame.
520         * @param parent parent frame.
521         */
522        /* package */ FieldFrame(FieldFrame parent) {
523            this.parent = parent;
524        }
525
526        /**
527         * Set the frame name.
528         * @param frameName value to set.
529         */
530        public void setFrameName(String frameName) {
531            this.frameName = frameName;
532        }
533
534        /**
535         * Getter for the frame name.
536         * @return frame name.
537         */
538        public String getFrameName() {
539            return frameName;
540        }
541
542        /**
543         * Getter for the parent frame.
544         * @return parent frame.
545         */
546        public FieldFrame getParent() {
547            return parent;
548        }
549
550        /**
551         * Getter for frame's children.
552         * @return children of this frame.
553         */
554        public Set<FieldFrame> getChildren() {
555            return Collections.unmodifiableSet(children);
556        }
557
558        /**
559         * Add child frame to this frame.
560         * @param child frame to add.
561         */
562        public void addChild(FieldFrame child) {
563            children.add(child);
564        }
565
566        /**
567         * Add field to this FieldFrame.
568         * @param field the ast of the field.
569         */
570        public void addField(DetailAST field) {
571            if (field.findFirstToken(TokenTypes.IDENT) != null) {
572                fields.add(field);
573            }
574        }
575
576        /**
577         * Sets isClassOrEnum.
578         * @param value value to set.
579         */
580        public void setClassOrEnumOrEnumConstDef(boolean value) {
581            classOrEnumOrEnumConstDef = value;
582        }
583
584        /**
585         * Getter for classOrEnumOrEnumConstDef.
586         * @return classOrEnumOrEnumConstDef.
587         */
588        public boolean isClassOrEnumOrEnumConstDef() {
589            return classOrEnumOrEnumConstDef;
590        }
591
592        /**
593         * Add method call to this frame.
594         * @param methodCall METHOD_CALL ast.
595         */
596        public void addMethodCall(DetailAST methodCall) {
597            methodCalls.add(methodCall);
598        }
599
600        /**
601         * Determines whether this FieldFrame contains the field.
602         * @param name name of the field to check.
603         * @return true if this FieldFrame contains instance field field.
604         */
605        public DetailAST findField(String name) {
606            DetailAST resultField = null;
607            for (DetailAST field: fields) {
608                if (getFieldName(field).equals(name)) {
609                    resultField = field;
610                    break;
611                }
612            }
613            return resultField;
614        }
615
616        /**
617         * Getter for frame's method calls.
618         * @return method calls of this frame.
619         */
620        public Set<DetailAST> getMethodCalls() {
621            return Collections.unmodifiableSet(methodCalls);
622        }
623
624        /**
625         * Get the name of the field.
626         * @param field to get the name from.
627         * @return name of the field.
628         */
629        private static String getFieldName(DetailAST field) {
630            return field.findFirstToken(TokenTypes.IDENT).getText();
631        }
632
633    }
634
635}