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