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