View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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   * @since 10.25.0
40   */
41  @StatelessCheck
42  public class UnnecessaryNullCheckWithInstanceOfCheck extends AbstractCheck {
43  
44      /**
45       * The error message key for reporting unnecessary null checks.
46       */
47      public static final String MSG_UNNECESSARY_NULLCHECK = "unnecessary.nullcheck.with.instanceof";
48  
49      @Override
50      public int[] getDefaultTokens() {
51          return getRequiredTokens();
52      }
53  
54      @Override
55      public int[] getAcceptableTokens() {
56          return getRequiredTokens();
57      }
58  
59      @Override
60      public int[] getRequiredTokens() {
61          return new int[] {TokenTypes.LITERAL_INSTANCEOF};
62      }
63  
64      @Override
65      public void visitToken(DetailAST instanceofNode) {
66          findUnnecessaryNullCheck(instanceofNode)
67                  .ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK));
68      }
69  
70      /**
71       * Checks for an unnecessary null check within a logical AND expression.
72       *
73       * @param instanceOfNode the AST node representing the instanceof expression
74       * @return the identifier if the check is redundant, otherwise {@code null}
75       */
76      private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) {
77          final DetailAST topLevelExpr = findTopLevelLogicalExpression(instanceOfNode);
78  
79          Optional<DetailAST> result = Optional.empty();
80          if (topLevelExpr.getType() == TokenTypes.LAND) {
81              result = findRedundantNullCheck(topLevelExpr, instanceOfNode)
82                      .map(DetailAST::getFirstChild);
83          }
84          return result;
85      }
86  
87      /**
88       * Traverses up through LAND and LOR operators to find the top-level logical expression.
89       *
90       * @param node the starting node
91       * @return the top-level logical expression node
92       */
93      private static DetailAST findTopLevelLogicalExpression(DetailAST node) {
94          DetailAST currentParent = node;
95          while (currentParent.getParent().getType() == TokenTypes.LAND
96                  || currentParent.getParent().getType() == TokenTypes.LOR) {
97              currentParent = currentParent.getParent();
98          }
99          return currentParent;
100     }
101 
102     /**
103      * Finds a redundant null check in a logical AND expression combined with an instanceof check.
104      *
105      * @param logicalAndNode the root node of the logical AND expression
106      * @param instanceOfNode the instanceof expression node
107      * @return the AST node representing the redundant null check, or null if not found
108      */
109     private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode,
110         DetailAST instanceOfNode) {
111 
112         Optional<DetailAST> nullCheckNode = Optional.empty();
113         final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT);
114 
115         if (instanceOfIdent != null
116             && !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) {
117 
118             nullCheckNode = searchForNullCheck(logicalAndNode, instanceOfNode, instanceOfIdent);
119         }
120         return nullCheckNode;
121     }
122 
123     /**
124      * Searches for a redundant null check in the children of a logical AND node.
125      *
126      * @param logicalAndNode the LAND node to search
127      * @param instanceOfNode the instanceof expression node
128      * @param instanceOfIdent the identifier from the instanceof expression
129      * @return the null check node if found, null otherwise
130      */
131     private static Optional<DetailAST> searchForNullCheck(DetailAST logicalAndNode,
132         DetailAST instanceOfNode, DetailAST instanceOfIdent) {
133 
134         Optional<DetailAST> nullCheckNode = Optional.empty();
135 
136         final Optional<DetailAST> instanceOfSubtree =
137                 findDirectChildContaining(logicalAndNode, instanceOfNode);
138 
139         final DetailAST instanceOfSubtreeNode =
140                 instanceOfSubtree.orElse(null);
141 
142         final boolean instanceOfInLor =
143                 instanceOfSubtreeNode != null
144                         && instanceOfSubtreeNode.getType() == TokenTypes.LOR;
145 
146         DetailAST currentChild = logicalAndNode.getFirstChild();
147         while (currentChild != null) {
148             if (instanceOfInLor && currentChild.equals(instanceOfSubtreeNode)) {
149                 break;
150             }
151 
152             if (nullCheckNode.isEmpty()) {
153                 nullCheckNode = checkChildForNullCheck(
154                         currentChild, instanceOfNode, instanceOfIdent);
155             }
156 
157             currentChild = currentChild.getNextSibling();
158         }
159 
160         return nullCheckNode;
161     }
162 
163     /**
164      * Checks a child node for a redundant null check.
165      *
166      * @param currentChild the child node to check
167      * @param instanceOfNode the instanceof expression node
168      * @param instanceOfIdent the identifier from the instanceof expression
169      * @return the null check node if found, otherwise empty
170      */
171     private static Optional<DetailAST> checkChildForNullCheck(DetailAST currentChild,
172         DetailAST instanceOfNode, DetailAST instanceOfIdent) {
173 
174         Optional<DetailAST> result = Optional.empty();
175 
176         if (isNotEqual(currentChild)
177                 && isNullCheckRedundant(instanceOfIdent, currentChild)) {
178             result = Optional.of(currentChild);
179         }
180         else if (currentChild.getType() == TokenTypes.LAND) {
181             result = findRedundantNullCheck(currentChild, instanceOfNode);
182         }
183 
184         return result;
185     }
186 
187     /**
188      * Finds the direct child of parent that contains the target node.
189      *
190      * @param parent the parent node
191      * @param target the target node to find
192      * @return the direct child containing target, or null if not found
193      */
194     private static Optional<DetailAST> findDirectChildContaining(DetailAST parent,
195         DetailAST target) {
196 
197         DetailAST result = null;
198         DetailAST child = parent.getFirstChild();
199         while (child != null) {
200             if (isAncestorOf(child, target)) {
201                 result = child;
202                 break;
203             }
204             child = child.getNextSibling();
205         }
206         return Optional.ofNullable(result);
207     }
208 
209     /**
210      * Checks if the given node is an ancestor of (or the same as) the target node.
211      *
212      * @param node the potential ancestor node
213      * @param target the target node to check
214      * @return true if node is an ancestor of target, false otherwise
215      */
216     private static boolean isAncestorOf(DetailAST node, DetailAST target) {
217         boolean found = false;
218         DetailAST current = target;
219         while (current != null) {
220             if (current.equals(node)) {
221                 found = true;
222                 break;
223             }
224             current = current.getParent();
225         }
226         return found;
227     }
228 
229     /**
230      * Checks if the given AST node contains a method call or field access
231      * on the specified variable.
232      *
233      * @param node the AST node to check
234      * @param variableName the name of the variable
235      * @return true if the variable is dereferenced, false otherwise
236      */
237     private static boolean containsVariableDereference(DetailAST node, String variableName) {
238 
239         boolean found = false;
240 
241         if (node.getType() == TokenTypes.DOT
242             || node.getType() == TokenTypes.METHOD_CALL
243             || node.getType() == TokenTypes.LAND
244             || node.getType() == TokenTypes.LOR) {
245 
246             DetailAST firstChild = node.getFirstChild();
247 
248             while (firstChild != null) {
249                 if (variableName.equals(firstChild.getText())
250                         && firstChild.getNextSibling().getType() != TokenTypes.ELIST
251                             || containsVariableDereference(firstChild, variableName)) {
252                     found = true;
253                     break;
254                 }
255                 firstChild = firstChild.getNextSibling();
256             }
257         }
258         return found;
259     }
260 
261     /**
262      * Checks if the given AST node represents a {@code !=} (not equal) operator.
263      *
264      * @param node the AST node to check
265      * @return {@code true} if the node is a not equal operator, otherwise {@code false}
266      */
267     private static boolean isNotEqual(DetailAST node) {
268         return node.getType() == TokenTypes.NOT_EQUAL;
269     }
270 
271     /**
272      * Checks if the given AST node is a null literal.
273      *
274      * @param node AST node to check
275      * @return true if the node is a null literal, false otherwise
276      */
277     private static boolean isNullLiteral(DetailAST node) {
278         return node.getType() == TokenTypes.LITERAL_NULL;
279     }
280 
281     /**
282      * Determines if the null check is redundant with the instanceof check.
283      *
284      * @param instanceOfIdent the identifier from the instanceof check
285      * @param nullCheckNode the node representing the null check
286      * @return true if the null check is unnecessary, false otherwise
287      */
288     private static boolean isNullCheckRedundant(DetailAST instanceOfIdent,
289         final DetailAST nullCheckNode) {
290 
291         final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT);
292         return nullCheckIdent != null
293                 && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling())
294                     || isNullLiteral(nullCheckNode.getFirstChild()))
295                 && instanceOfIdent.getText().equals(nullCheckIdent.getText());
296     }
297 }