001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.Optional; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028 029/** 030 * <div> 031 * Checks for redundant null checks with the instanceof operator. 032 * </div> 033 * 034 * <p> 035 * The instanceof operator inherently returns false when the left operand is null, 036 * making explicit null checks redundant in boolean expressions with instanceof. 037 * </p> 038 * 039 * @since 10.25.0 040 */ 041@StatelessCheck 042public class UnnecessaryNullCheckWithInstanceOfCheck extends AbstractCheck { 043 044 /** 045 * The error message key for reporting unnecessary null checks. 046 */ 047 public static final String MSG_UNNECESSARY_NULLCHECK = "unnecessary.nullcheck.with.instanceof"; 048 049 /** 050 * Creates a new {@code UnnecessaryNullCheckWithInstanceOfCheck} instance. 051 */ 052 public UnnecessaryNullCheckWithInstanceOfCheck() { 053 // no code by default 054 } 055 056 @Override 057 public int[] getDefaultTokens() { 058 return getRequiredTokens(); 059 } 060 061 @Override 062 public int[] getAcceptableTokens() { 063 return getRequiredTokens(); 064 } 065 066 @Override 067 public int[] getRequiredTokens() { 068 return new int[] {TokenTypes.LITERAL_INSTANCEOF}; 069 } 070 071 @Override 072 public void visitToken(DetailAST instanceofNode) { 073 findUnnecessaryNullCheck(instanceofNode) 074 .ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK)); 075 } 076 077 /** 078 * Checks for an unnecessary null check within a logical AND expression. 079 * 080 * @param instanceOfNode the AST node representing the instanceof expression 081 * @return the identifier if the check is redundant, otherwise {@code null} 082 */ 083 private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) { 084 final DetailAST topLevelExpr = findTopLevelLogicalExpression(instanceOfNode); 085 086 Optional<DetailAST> result = Optional.empty(); 087 if (topLevelExpr.getType() == TokenTypes.LAND) { 088 result = findRedundantNullCheck(topLevelExpr, instanceOfNode) 089 .map(DetailAST::getFirstChild); 090 } 091 return result; 092 } 093 094 /** 095 * Traverses up through LAND and LOR operators to find the top-level logical expression. 096 * 097 * @param node the starting node 098 * @return the top-level logical expression node 099 */ 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)) { 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 field access or method call on the 238 * variable of the instanceof operand, evaluated before that operand. 239 * 240 * @param node the AST node to check 241 * @param instanceOfIdent the identifier from the instanceof expression 242 * @return true if the variable is dereferenced before the instanceof, false otherwise 243 */ 244 private static boolean containsVariableDereference(DetailAST node, 245 DetailAST instanceOfIdent) { 246 247 boolean found = false; 248 DetailAST child = node.getFirstChild(); 249 250 while (child != null && !found) { 251 found = node.getType() == TokenTypes.DOT 252 && instanceOfIdent.getText().equals(child.getText()) 253 && child.getNextSibling() != null 254 && isBefore(child, instanceOfIdent) 255 || containsVariableDereference(child, instanceOfIdent); 256 child = child.getNextSibling(); 257 } 258 return found; 259 } 260 261 /** 262 * Checks if the first node appears earlier in the source than the second node. 263 * 264 * @param first the node expected to appear first 265 * @param second the node expected to appear second 266 * @return true if first is located before second, false otherwise 267 */ 268 private static boolean isBefore(DetailAST first, DetailAST second) { 269 int result = Integer.compare(first.getLineNo(), second.getLineNo()); 270 if (result == 0) { 271 result = Integer.compare(first.getColumnNo(), second.getColumnNo()); 272 } 273 return result == -1; 274 } 275 276 /** 277 * Checks if the given AST node represents a {@code !=} (not equal) operator. 278 * 279 * @param node the AST node to check 280 * @return {@code true} if the node is a not equal operator, otherwise {@code false} 281 */ 282 private static boolean isNotEqual(DetailAST node) { 283 return node.getType() == TokenTypes.NOT_EQUAL; 284 } 285 286 /** 287 * Checks if the given AST node is a null literal. 288 * 289 * @param node AST node to check 290 * @return true if the node is a null literal, false otherwise 291 */ 292 private static boolean isNullLiteral(DetailAST node) { 293 return node.getType() == TokenTypes.LITERAL_NULL; 294 } 295 296 /** 297 * Determines if the null check is redundant with the instanceof check. 298 * 299 * @param instanceOfIdent the identifier from the instanceof check 300 * @param nullCheckNode the node representing the null check 301 * @return true if the null check is unnecessary, false otherwise 302 */ 303 private static boolean isNullCheckRedundant(DetailAST instanceOfIdent, 304 final DetailAST nullCheckNode) { 305 306 final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT); 307 return nullCheckIdent != null 308 && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling()) 309 || isNullLiteral(nullCheckNode.getFirstChild())) 310 && instanceOfIdent.getText().equals(nullCheckIdent.getText()); 311 } 312 313}