View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.Set;
25  
26  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  
31  /**
32   * Checks that any combination of String literals
33   * is on the left side of an equals() comparison.
34   * Also checks for String literals assigned to some field
35   * (such as {@code someString.equals(anotherString = "text")}).
36   *
37   * <p>Rationale: Calling the equals() method on String literals
38   * will avoid a potential NullPointerException.  Also, it is
39   * pretty common to see null check right before equals comparisons
40   * which is not necessary in the below example.
41   *
42   * <p>For example:
43   *
44   * <pre>
45   *  {@code
46   *    String nullString = null;
47   *    nullString.equals(&quot;My_Sweet_String&quot;);
48   *  }
49   * </pre>
50   * should be refactored to
51   *
52   * <pre>
53   *  {@code
54   *    String nullString = null;
55   *    &quot;My_Sweet_String&quot;.equals(nullString);
56   *  }
57   * </pre>
58   *
59   * @author Travis Schneeberger
60   * @author Vladislav Lisetskiy
61   */
62  @FileStatefulCheck
63  public class EqualsAvoidNullCheck extends AbstractCheck {
64  
65      /**
66       * A key is pointing to the warning message text in "messages.properties"
67       * file.
68       */
69      public static final String MSG_EQUALS_AVOID_NULL = "equals.avoid.null";
70  
71      /**
72       * A key is pointing to the warning message text in "messages.properties"
73       * file.
74       */
75      public static final String MSG_EQUALS_IGNORE_CASE_AVOID_NULL = "equalsIgnoreCase.avoid.null";
76  
77      /** Method name for comparison. */
78      private static final String EQUALS = "equals";
79  
80      /** Type name for comparison. */
81      private static final String STRING = "String";
82  
83      /** Whether to process equalsIgnoreCase() invocations. */
84      private boolean ignoreEqualsIgnoreCase;
85  
86      /** Stack of sets of field names, one for each class of a set of nested classes. */
87      private FieldFrame currentFrame;
88  
89      @Override
90      public int[] getDefaultTokens() {
91          return getRequiredTokens();
92      }
93  
94      @Override
95      public int[] getAcceptableTokens() {
96          return getRequiredTokens();
97      }
98  
99      @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         /** Parent frame. */
517         private final FieldFrame parent;
518 
519         /** Set of frame's children. */
520         private final Set<FieldFrame> children = new HashSet<>();
521 
522         /** Set of fields. */
523         private final Set<DetailAST> fields = new HashSet<>();
524 
525         /** Set of equals calls. */
526         private final Set<DetailAST> methodCalls = new HashSet<>();
527 
528         /** Name of the class, enum or enum constant declaration. */
529         private String frameName;
530 
531         /** Whether the frame is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. */
532         private boolean classOrEnumOrEnumConstDef;
533 
534         /**
535          * Creates new frame.
536          * @param parent parent frame.
537          */
538         FieldFrame(FieldFrame parent) {
539             this.parent = parent;
540         }
541 
542         /**
543          * Set the frame name.
544          * @param frameName value to set.
545          */
546         public void setFrameName(String frameName) {
547             this.frameName = frameName;
548         }
549 
550         /**
551          * Getter for the frame name.
552          * @return frame name.
553          */
554         public String getFrameName() {
555             return frameName;
556         }
557 
558         /**
559          * Getter for the parent frame.
560          * @return parent frame.
561          */
562         public FieldFrame getParent() {
563             return parent;
564         }
565 
566         /**
567          * Getter for frame's children.
568          * @return children of this frame.
569          */
570         public Set<FieldFrame> getChildren() {
571             return Collections.unmodifiableSet(children);
572         }
573 
574         /**
575          * Add child frame to this frame.
576          * @param child frame to add.
577          */
578         public void addChild(FieldFrame child) {
579             children.add(child);
580         }
581 
582         /**
583          * Add field to this FieldFrame.
584          * @param field the ast of the field.
585          */
586         public void addField(DetailAST field) {
587             fields.add(field);
588         }
589 
590         /**
591          * Sets isClassOrEnum.
592          * @param value value to set.
593          */
594         public void setClassOrEnumOrEnumConstDef(boolean value) {
595             classOrEnumOrEnumConstDef = value;
596         }
597 
598         /**
599          * Getter for classOrEnumOrEnumConstDef.
600          * @return classOrEnumOrEnumConstDef.
601          */
602         public boolean isClassOrEnumOrEnumConstDef() {
603             return classOrEnumOrEnumConstDef;
604         }
605 
606         /**
607          * Add method call to this frame.
608          * @param methodCall METHOD_CALL ast.
609          */
610         public void addMethodCall(DetailAST methodCall) {
611             methodCalls.add(methodCall);
612         }
613 
614         /**
615          * Determines whether this FieldFrame contains the field.
616          * @param name name of the field to check.
617          * @return true if this FieldFrame contains instance field field.
618          */
619         public DetailAST findField(String name) {
620             DetailAST resultField = null;
621             for (DetailAST field: fields) {
622                 if (getFieldName(field).equals(name)) {
623                     resultField = field;
624                     break;
625                 }
626             }
627             return resultField;
628         }
629 
630         /**
631          * Getter for frame's method calls.
632          * @return method calls of this frame.
633          */
634         public Set<DetailAST> getMethodCalls() {
635             return Collections.unmodifiableSet(methodCalls);
636         }
637 
638         /**
639          * Get the name of the field.
640          * @param field to get the name from.
641          * @return name of the field.
642          */
643         private static String getFieldName(DetailAST field) {
644             return field.findFirstToken(TokenTypes.IDENT).getText();
645         }
646     }
647 }