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 @Override 050 public int[] getDefaultTokens() { 051 return getRequiredTokens(); 052 } 053 054 @Override 055 public int[] getAcceptableTokens() { 056 return getRequiredTokens(); 057 } 058 059 @Override 060 public int[] getRequiredTokens() { 061 return new int[] {TokenTypes.LITERAL_INSTANCEOF}; 062 } 063 064 @Override 065 public void visitToken(DetailAST instanceofNode) { 066 findUnnecessaryNullCheck(instanceofNode) 067 .ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK)); 068 } 069 070 /** 071 * Checks for an unnecessary null check within a logical AND expression. 072 * 073 * @param instanceOfNode the AST node representing the instanceof expression 074 * @return the identifier if the check is redundant, otherwise {@code null} 075 */ 076 private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) { 077 final DetailAST topLevelExpr = findTopLevelLogicalExpression(instanceOfNode); 078 079 Optional<DetailAST> result = Optional.empty(); 080 if (topLevelExpr.getType() == TokenTypes.LAND) { 081 result = findRedundantNullCheck(topLevelExpr, instanceOfNode) 082 .map(DetailAST::getFirstChild); 083 } 084 return result; 085 } 086 087 /** 088 * Traverses up through LAND and LOR operators to find the top-level logical expression. 089 * 090 * @param node the starting node 091 * @return the top-level logical expression node 092 */ 093 private static DetailAST findTopLevelLogicalExpression(DetailAST node) { 094 DetailAST currentParent = node; 095 while (currentParent.getParent().getType() == TokenTypes.LAND 096 || currentParent.getParent().getType() == TokenTypes.LOR) { 097 currentParent = currentParent.getParent(); 098 } 099 return currentParent; 100 } 101 102 /** 103 * Finds a redundant null check in a logical AND expression combined with an instanceof check. 104 * 105 * @param logicalAndNode the root node of the logical AND expression 106 * @param instanceOfNode the instanceof expression node 107 * @return the AST node representing the redundant null check, or null if not found 108 */ 109 private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode, 110 DetailAST instanceOfNode) { 111 112 Optional<DetailAST> nullCheckNode = Optional.empty(); 113 final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT); 114 115 if (instanceOfIdent != null 116 && !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) { 117 118 nullCheckNode = searchForNullCheck(logicalAndNode, instanceOfNode, instanceOfIdent); 119 } 120 return nullCheckNode; 121 } 122 123 /** 124 * Searches for a redundant null check in the children of a logical AND node. 125 * 126 * @param logicalAndNode the LAND node to search 127 * @param instanceOfNode the instanceof expression node 128 * @param instanceOfIdent the identifier from the instanceof expression 129 * @return the null check node if found, null otherwise 130 */ 131 private static Optional<DetailAST> searchForNullCheck(DetailAST logicalAndNode, 132 DetailAST instanceOfNode, DetailAST instanceOfIdent) { 133 134 Optional<DetailAST> nullCheckNode = Optional.empty(); 135 136 final Optional<DetailAST> instanceOfSubtree = 137 findDirectChildContaining(logicalAndNode, instanceOfNode); 138 139 final DetailAST instanceOfSubtreeNode = 140 instanceOfSubtree.orElse(null); 141 142 final boolean instanceOfInLor = 143 instanceOfSubtreeNode != null 144 && instanceOfSubtreeNode.getType() == TokenTypes.LOR; 145 146 DetailAST currentChild = logicalAndNode.getFirstChild(); 147 while (currentChild != null) { 148 if (instanceOfInLor && currentChild.equals(instanceOfSubtreeNode)) { 149 break; 150 } 151 152 if (nullCheckNode.isEmpty()) { 153 nullCheckNode = checkChildForNullCheck( 154 currentChild, instanceOfNode, instanceOfIdent); 155 } 156 157 currentChild = currentChild.getNextSibling(); 158 } 159 160 return nullCheckNode; 161 } 162 163 /** 164 * Checks a child node for a redundant null check. 165 * 166 * @param currentChild the child node to check 167 * @param instanceOfNode the instanceof expression node 168 * @param instanceOfIdent the identifier from the instanceof expression 169 * @return the null check node if found, otherwise empty 170 */ 171 private static Optional<DetailAST> checkChildForNullCheck(DetailAST currentChild, 172 DetailAST instanceOfNode, DetailAST instanceOfIdent) { 173 174 Optional<DetailAST> result = Optional.empty(); 175 176 if (isNotEqual(currentChild) 177 && isNullCheckRedundant(instanceOfIdent, currentChild)) { 178 result = Optional.of(currentChild); 179 } 180 else if (currentChild.getType() == TokenTypes.LAND) { 181 result = findRedundantNullCheck(currentChild, instanceOfNode); 182 } 183 184 return result; 185 } 186 187 /** 188 * Finds the direct child of parent that contains the target node. 189 * 190 * @param parent the parent node 191 * @param target the target node to find 192 * @return the direct child containing target, or null if not found 193 */ 194 private static Optional<DetailAST> findDirectChildContaining(DetailAST parent, 195 DetailAST target) { 196 197 DetailAST result = null; 198 DetailAST child = parent.getFirstChild(); 199 while (child != null) { 200 if (isAncestorOf(child, target)) { 201 result = child; 202 break; 203 } 204 child = child.getNextSibling(); 205 } 206 return Optional.ofNullable(result); 207 } 208 209 /** 210 * Checks if the given node is an ancestor of (or the same as) the target node. 211 * 212 * @param node the potential ancestor node 213 * @param target the target node to check 214 * @return true if node is an ancestor of target, false otherwise 215 */ 216 private static boolean isAncestorOf(DetailAST node, DetailAST target) { 217 boolean found = false; 218 DetailAST current = target; 219 while (current != null) { 220 if (current.equals(node)) { 221 found = true; 222 break; 223 } 224 current = current.getParent(); 225 } 226 return found; 227 } 228 229 /** 230 * Checks if the given AST node contains a method call or field access 231 * on the specified variable. 232 * 233 * @param node the AST node to check 234 * @param variableName the name of the variable 235 * @return true if the variable is dereferenced, false otherwise 236 */ 237 private static boolean containsVariableDereference(DetailAST node, String variableName) { 238 239 boolean found = false; 240 241 if (node.getType() == TokenTypes.DOT 242 || node.getType() == TokenTypes.METHOD_CALL 243 || node.getType() == TokenTypes.LAND 244 || node.getType() == TokenTypes.LOR) { 245 246 DetailAST firstChild = node.getFirstChild(); 247 248 while (firstChild != null) { 249 if (variableName.equals(firstChild.getText()) 250 && firstChild.getNextSibling().getType() != TokenTypes.ELIST 251 || containsVariableDereference(firstChild, variableName)) { 252 found = true; 253 break; 254 } 255 firstChild = firstChild.getNextSibling(); 256 } 257 } 258 return found; 259 } 260 261 /** 262 * Checks if the given AST node represents a {@code !=} (not equal) operator. 263 * 264 * @param node the AST node to check 265 * @return {@code true} if the node is a not equal operator, otherwise {@code false} 266 */ 267 private static boolean isNotEqual(DetailAST node) { 268 return node.getType() == TokenTypes.NOT_EQUAL; 269 } 270 271 /** 272 * Checks if the given AST node is a null literal. 273 * 274 * @param node AST node to check 275 * @return true if the node is a null literal, false otherwise 276 */ 277 private static boolean isNullLiteral(DetailAST node) { 278 return node.getType() == TokenTypes.LITERAL_NULL; 279 } 280 281 /** 282 * Determines if the null check is redundant with the instanceof check. 283 * 284 * @param instanceOfIdent the identifier from the instanceof check 285 * @param nullCheckNode the node representing the null check 286 * @return true if the null check is unnecessary, false otherwise 287 */ 288 private static boolean isNullCheckRedundant(DetailAST instanceOfIdent, 289 final DetailAST nullCheckNode) { 290 291 final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT); 292 return nullCheckIdent != null 293 && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling()) 294 || isNullLiteral(nullCheckNode.getFirstChild())) 295 && instanceOfIdent.getText().equals(nullCheckIdent.getText()); 296 } 297}