View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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.Optional;
23  
24  import com.puppycrawl.tools.checkstyle.StatelessCheck;
25  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28  
29  /**
30   * <div>
31   * Checks for redundant null checks with the instanceof operator.
32   * </div>
33   *
34   * <p>
35   * The instanceof operator inherently returns false when the left operand is null,
36   * making explicit null checks redundant in boolean expressions with instanceof.
37   * </p>
38   *
39   * <p>
40   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
41   * </p>
42   *
43   * <p>
44   * Violation Message Keys:
45   * </p>
46   *
47   * <ul>
48   *   <li>
49   *     {@code unnecessary.nullcheck.with.instanceof}
50   *   </li>
51   * </ul>
52   *
53   * @since 10.25.0
54   */
55  @StatelessCheck
56  public class UnnecessaryNullCheckWithInstanceOfCheck extends AbstractCheck {
57  
58      /**
59       * The error message key for reporting unnecessary null checks.
60       */
61      public static final String MSG_UNNECESSARY_NULLCHECK = "unnecessary.nullcheck.with.instanceof";
62  
63      @Override
64      public int[] getDefaultTokens() {
65          return getRequiredTokens();
66      }
67  
68      @Override
69      public int[] getAcceptableTokens() {
70          return getRequiredTokens();
71      }
72  
73      @Override
74      public int[] getRequiredTokens() {
75          return new int[] {TokenTypes.LITERAL_INSTANCEOF};
76      }
77  
78      @Override
79      public void visitToken(DetailAST instanceofNode) {
80          findUnnecessaryNullCheck(instanceofNode)
81                  .ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK));
82      }
83  
84      /**
85       * Checks for an unnecessary null check within a logical AND expression.
86       *
87       * @param instanceOfNode the AST node representing the instanceof expression
88       * @return the identifier if the check is redundant, otherwise {@code null}
89       */
90      private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) {
91          DetailAST currentParent = instanceOfNode;
92  
93          while (currentParent.getParent().getType() == TokenTypes.LAND) {
94              currentParent = currentParent.getParent();
95          }
96          return findRedundantNullCheck(currentParent, instanceOfNode)
97              .map(DetailAST::getFirstChild);
98      }
99  
100     /**
101      * Finds a redundant null check in a logical AND expression combined with an instanceof check.
102      *
103      * @param logicalAndNode the root node of the logical AND expression
104      * @param instanceOfNode the instanceof expression node
105      * @return the AST node representing the redundant null check, or null if not found
106      */
107     private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode,
108         DetailAST instanceOfNode) {
109 
110         DetailAST nullCheckNode = null;
111         final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT);
112 
113         if (instanceOfIdent != null
114             && !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) {
115 
116             DetailAST currentChild = logicalAndNode.getFirstChild();
117             while (currentChild != null) {
118                 if (isNotEqual(currentChild)
119                         && isNullCheckRedundant(instanceOfIdent, currentChild)) {
120                     nullCheckNode = currentChild;
121                 }
122                 else if (nullCheckNode == null && currentChild.getType() == TokenTypes.LAND) {
123                     nullCheckNode = findRedundantNullCheck(currentChild, instanceOfNode)
124                             .orElse(null);
125                 }
126                 currentChild = currentChild.getNextSibling();
127             }
128         }
129         return Optional.ofNullable(nullCheckNode);
130     }
131 
132     /**
133      * Checks if the given AST node contains a method call or field access
134      * on the specified variable.
135      *
136      * @param node the AST node to check
137      * @param variableName the name of the variable
138      * @return true if the variable is dereferenced, false otherwise
139      */
140     private static boolean containsVariableDereference(DetailAST node, String variableName) {
141 
142         boolean found = false;
143 
144         if (node.getType() == TokenTypes.DOT
145             || node.getType() == TokenTypes.METHOD_CALL || node.getType() == TokenTypes.LAND) {
146 
147             DetailAST firstChild = node.getFirstChild();
148 
149             while (firstChild != null) {
150                 if (variableName.equals(firstChild.getText())
151                         && firstChild.getNextSibling().getType() != TokenTypes.ELIST
152                             || containsVariableDereference(firstChild, variableName)) {
153                     found = true;
154                     break;
155                 }
156                 firstChild = firstChild.getNextSibling();
157             }
158         }
159         return found;
160     }
161 
162     /**
163      * Checks if the given AST node represents a {@code !=} (not equal) operator.
164      *
165      * @param node the AST node to check
166      * @return {@code true} if the node is a not equal operator, otherwise {@code false}
167      */
168     private static boolean isNotEqual(DetailAST node) {
169         return node.getType() == TokenTypes.NOT_EQUAL;
170     }
171 
172     /**
173      * Checks if the given AST node is a null literal.
174      *
175      * @param node AST node to check
176      * @return true if the node is a null literal, false otherwise
177      */
178     private static boolean isNullLiteral(DetailAST node) {
179         return node.getType() == TokenTypes.LITERAL_NULL;
180     }
181 
182     /**
183      * Determines if the null check is redundant with the instanceof check.
184      *
185      * @param instanceOfIdent the identifier from the instanceof check
186      * @param nullCheckNode the node representing the null check
187      * @return true if the null check is unnecessary, false otherwise
188      */
189     private static boolean isNullCheckRedundant(DetailAST instanceOfIdent,
190         final DetailAST nullCheckNode) {
191 
192         final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT);
193         return nullCheckIdent != null
194                 && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling())
195                     || isNullLiteral(nullCheckNode.getFirstChild()))
196                 && instanceOfIdent.getText().equals(nullCheckIdent.getText());
197     }
198 }