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 }