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 }