001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 DetailAST currentParent = instanceOfNode; 078 079 while (currentParent.getParent().getType() == TokenTypes.LAND) { 080 currentParent = currentParent.getParent(); 081 } 082 return findRedundantNullCheck(currentParent, instanceOfNode) 083 .map(DetailAST::getFirstChild); 084 } 085 086 /** 087 * Finds a redundant null check in a logical AND expression combined with an instanceof check. 088 * 089 * @param logicalAndNode the root node of the logical AND expression 090 * @param instanceOfNode the instanceof expression node 091 * @return the AST node representing the redundant null check, or null if not found 092 */ 093 private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode, 094 DetailAST instanceOfNode) { 095 096 DetailAST nullCheckNode = null; 097 final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT); 098 099 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}