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 * <p> 040 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 041 * </p> 042 * 043 * <p> 044 * Violation Message Keys: 045 * </p> 046 * 047 * <ul> 048 * <li> 049 * {@code unnecessary.nullcheck.with.instanceof} 050 * </li> 051 * </ul> 052 * 053 * @since 10.25.0 054 */ 055@StatelessCheck 056public class UnnecessaryNullCheckWithInstanceOfCheck extends AbstractCheck { 057 058 /** 059 * The error message key for reporting unnecessary null checks. 060 */ 061 public static final String MSG_UNNECESSARY_NULLCHECK = "unnecessary.nullcheck.with.instanceof"; 062 063 @Override 064 public int[] getDefaultTokens() { 065 return getRequiredTokens(); 066 } 067 068 @Override 069 public int[] getAcceptableTokens() { 070 return getRequiredTokens(); 071 } 072 073 @Override 074 public int[] getRequiredTokens() { 075 return new int[] {TokenTypes.LITERAL_INSTANCEOF}; 076 } 077 078 @Override 079 public void visitToken(DetailAST instanceofNode) { 080 findUnnecessaryNullCheck(instanceofNode) 081 .ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK)); 082 } 083 084 /** 085 * Checks for an unnecessary null check within a logical AND expression. 086 * 087 * @param instanceOfNode the AST node representing the instanceof expression 088 * @return the identifier if the check is redundant, otherwise {@code null} 089 */ 090 private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) { 091 DetailAST currentParent = instanceOfNode; 092 093 while (currentParent.getParent().getType() == TokenTypes.LAND) { 094 currentParent = currentParent.getParent(); 095 } 096 return findRedundantNullCheck(currentParent, instanceOfNode) 097 .map(DetailAST::getFirstChild); 098 } 099 100 /** 101 * Finds a redundant null check in a logical AND expression combined with an instanceof check. 102 * 103 * @param logicalAndNode the root node of the logical AND expression 104 * @param instanceOfNode the instanceof expression node 105 * @return the AST node representing the redundant null check, or null if not found 106 */ 107 private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode, 108 DetailAST instanceOfNode) { 109 110 DetailAST nullCheckNode = null; 111 final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT); 112 113 if (instanceOfIdent != null 114 && !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) { 115 116 DetailAST currentChild = logicalAndNode.getFirstChild(); 117 while (currentChild != null) { 118 if (isNotEqual(currentChild) 119 && isNullCheckRedundant(instanceOfIdent, currentChild)) { 120 nullCheckNode = currentChild; 121 } 122 else if (nullCheckNode == null && currentChild.getType() == TokenTypes.LAND) { 123 nullCheckNode = findRedundantNullCheck(currentChild, instanceOfNode) 124 .orElse(null); 125 } 126 currentChild = currentChild.getNextSibling(); 127 } 128 } 129 return Optional.ofNullable(nullCheckNode); 130 } 131 132 /** 133 * Checks if the given AST node contains a method call or field access 134 * on the specified variable. 135 * 136 * @param node the AST node to check 137 * @param variableName the name of the variable 138 * @return true if the variable is dereferenced, false otherwise 139 */ 140 private static boolean containsVariableDereference(DetailAST node, String variableName) { 141 142 boolean found = false; 143 144 if (node.getType() == TokenTypes.DOT 145 || node.getType() == TokenTypes.METHOD_CALL || node.getType() == TokenTypes.LAND) { 146 147 DetailAST firstChild = node.getFirstChild(); 148 149 while (firstChild != null) { 150 if (variableName.equals(firstChild.getText()) 151 && firstChild.getNextSibling().getType() != TokenTypes.ELIST 152 || containsVariableDereference(firstChild, variableName)) { 153 found = true; 154 break; 155 } 156 firstChild = firstChild.getNextSibling(); 157 } 158 } 159 return found; 160 } 161 162 /** 163 * Checks if the given AST node represents a {@code !=} (not equal) operator. 164 * 165 * @param node the AST node to check 166 * @return {@code true} if the node is a not equal operator, otherwise {@code false} 167 */ 168 private static boolean isNotEqual(DetailAST node) { 169 return node.getType() == TokenTypes.NOT_EQUAL; 170 } 171 172 /** 173 * Checks if the given AST node is a null literal. 174 * 175 * @param node AST node to check 176 * @return true if the node is a null literal, false otherwise 177 */ 178 private static boolean isNullLiteral(DetailAST node) { 179 return node.getType() == TokenTypes.LITERAL_NULL; 180 } 181 182 /** 183 * Determines if the null check is redundant with the instanceof check. 184 * 185 * @param instanceOfIdent the identifier from the instanceof check 186 * @param nullCheckNode the node representing the null check 187 * @return true if the null check is unnecessary, false otherwise 188 */ 189 private static boolean isNullCheckRedundant(DetailAST instanceOfIdent, 190 final DetailAST nullCheckNode) { 191 192 final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT); 193 return nullCheckIdent != null 194 && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling()) 195 || isNullLiteral(nullCheckNode.getFirstChild())) 196 && instanceOfIdent.getText().equals(nullCheckIdent.getText()); 197 } 198}