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.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}