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   * @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          DetailAST currentParent = instanceOfNode;
78  
79          while (currentParent.getParent().getType() == TokenTypes.LAND) {
80              currentParent = currentParent.getParent();
81          }
82          return findRedundantNullCheck(currentParent, instanceOfNode)
83              .map(DetailAST::getFirstChild);
84      }
85  
86      /**
87       * Finds a redundant null check in a logical AND expression combined with an instanceof check.
88       *
89       * @param logicalAndNode the root node of the logical AND expression
90       * @param instanceOfNode the instanceof expression node
91       * @return the AST node representing the redundant null check, or null if not found
92       */
93      private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode,
94          DetailAST instanceOfNode) {
95  
96          DetailAST nullCheckNode = null;
97          final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT);
98  
99          if (instanceOfIdent != null
100             && !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) {
101 
102             DetailAST currentChild = logicalAndNode.getFirstChild();
103             while (currentChild != null) {
104                 if (isNotEqual(currentChild)
105                         && isNullCheckRedundant(instanceOfIdent, currentChild)) {
106                     nullCheckNode = currentChild;
107                 }
108                 else if (nullCheckNode == null && currentChild.getType() == TokenTypes.LAND) {
109                     nullCheckNode = findRedundantNullCheck(currentChild, instanceOfNode)
110                             .orElse(null);
111                 }
112                 currentChild = currentChild.getNextSibling();
113             }
114         }
115         return Optional.ofNullable(nullCheckNode);
116     }
117 
118     /**
119      * Checks if the given AST node contains a method call or field access
120      * on the specified variable.
121      *
122      * @param node the AST node to check
123      * @param variableName the name of the variable
124      * @return true if the variable is dereferenced, false otherwise
125      */
126     private static boolean containsVariableDereference(DetailAST node, String variableName) {
127 
128         boolean found = false;
129 
130         if (node.getType() == TokenTypes.DOT
131             || node.getType() == TokenTypes.METHOD_CALL || node.getType() == TokenTypes.LAND) {
132 
133             DetailAST firstChild = node.getFirstChild();
134 
135             while (firstChild != null) {
136                 if (variableName.equals(firstChild.getText())
137                         && firstChild.getNextSibling().getType() != TokenTypes.ELIST
138                             || containsVariableDereference(firstChild, variableName)) {
139                     found = true;
140                     break;
141                 }
142                 firstChild = firstChild.getNextSibling();
143             }
144         }
145         return found;
146     }
147 
148     /**
149      * Checks if the given AST node represents a {@code !=} (not equal) operator.
150      *
151      * @param node the AST node to check
152      * @return {@code true} if the node is a not equal operator, otherwise {@code false}
153      */
154     private static boolean isNotEqual(DetailAST node) {
155         return node.getType() == TokenTypes.NOT_EQUAL;
156     }
157 
158     /**
159      * Checks if the given AST node is a null literal.
160      *
161      * @param node AST node to check
162      * @return true if the node is a null literal, false otherwise
163      */
164     private static boolean isNullLiteral(DetailAST node) {
165         return node.getType() == TokenTypes.LITERAL_NULL;
166     }
167 
168     /**
169      * Determines if the null check is redundant with the instanceof check.
170      *
171      * @param instanceOfIdent the identifier from the instanceof check
172      * @param nullCheckNode the node representing the null check
173      * @return true if the null check is unnecessary, false otherwise
174      */
175     private static boolean isNullCheckRedundant(DetailAST instanceOfIdent,
176         final DetailAST nullCheckNode) {
177 
178         final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT);
179         return nullCheckIdent != null
180                 && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling())
181                     || isNullLiteral(nullCheckNode.getFirstChild()))
182                 && instanceOfIdent.getText().equals(nullCheckIdent.getText());
183     }
184 }