UnnecessaryNullCheckWithInstanceOfCheck.java
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2025 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
///////////////////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.coding;
import java.util.Optional;
import com.puppycrawl.tools.checkstyle.StatelessCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
* <div>
* Checks for redundant null checks with the instanceof operator.
* </div>
*
* <p>
* The instanceof operator inherently returns false when the left operand is null,
* making explicit null checks redundant in boolean expressions with instanceof.
* </p>
*
* <p>
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
* </p>
*
* <p>
* Violation Message Keys:
* </p>
*
* <ul>
* <li>
* {@code unnecessary.nullcheck.with.instanceof}
* </li>
* </ul>
*
* @since 10.25.0
*/
@StatelessCheck
public class UnnecessaryNullCheckWithInstanceOfCheck extends AbstractCheck {
/**
* The error message key for reporting unnecessary null checks.
*/
public static final String MSG_UNNECESSARY_NULLCHECK = "unnecessary.nullcheck.with.instanceof";
@Override
public int[] getDefaultTokens() {
return getRequiredTokens();
}
@Override
public int[] getAcceptableTokens() {
return getRequiredTokens();
}
@Override
public int[] getRequiredTokens() {
return new int[] {TokenTypes.LITERAL_INSTANCEOF};
}
@Override
public void visitToken(DetailAST instanceofNode) {
findUnnecessaryNullCheck(instanceofNode)
.ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK));
}
/**
* Checks for an unnecessary null check within a logical AND expression.
*
* @param instanceOfNode the AST node representing the instanceof expression
* @return the identifier if the check is redundant, otherwise {@code null}
*/
private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) {
DetailAST currentParent = instanceOfNode;
while (currentParent.getParent().getType() == TokenTypes.LAND) {
currentParent = currentParent.getParent();
}
return findRedundantNullCheck(currentParent, instanceOfNode)
.map(DetailAST::getFirstChild);
}
/**
* Finds a redundant null check in a logical AND expression combined with an instanceof check.
*
* @param logicalAndNode the root node of the logical AND expression
* @param instanceOfNode the instanceof expression node
* @return the AST node representing the redundant null check, or null if not found
*/
private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode,
DetailAST instanceOfNode) {
DetailAST nullCheckNode = null;
final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT);
if (instanceOfIdent != null
&& !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) {
DetailAST currentChild = logicalAndNode.getFirstChild();
while (currentChild != null) {
if (isNotEqual(currentChild)
&& isNullCheckRedundant(instanceOfIdent, currentChild)) {
nullCheckNode = currentChild;
}
else if (nullCheckNode == null && currentChild.getType() == TokenTypes.LAND) {
nullCheckNode = findRedundantNullCheck(currentChild, instanceOfNode)
.orElse(null);
}
currentChild = currentChild.getNextSibling();
}
}
return Optional.ofNullable(nullCheckNode);
}
/**
* Checks if the given AST node contains a method call or field access
* on the specified variable.
*
* @param node the AST node to check
* @param variableName the name of the variable
* @return true if the variable is dereferenced, false otherwise
*/
private static boolean containsVariableDereference(DetailAST node, String variableName) {
boolean found = false;
if (node.getType() == TokenTypes.DOT
|| node.getType() == TokenTypes.METHOD_CALL || node.getType() == TokenTypes.LAND) {
DetailAST firstChild = node.getFirstChild();
while (firstChild != null) {
if (variableName.equals(firstChild.getText())
&& firstChild.getNextSibling().getType() != TokenTypes.ELIST
|| containsVariableDereference(firstChild, variableName)) {
found = true;
break;
}
firstChild = firstChild.getNextSibling();
}
}
return found;
}
/**
* Checks if the given AST node represents a {@code !=} (not equal) operator.
*
* @param node the AST node to check
* @return {@code true} if the node is a not equal operator, otherwise {@code false}
*/
private static boolean isNotEqual(DetailAST node) {
return node.getType() == TokenTypes.NOT_EQUAL;
}
/**
* Checks if the given AST node is a null literal.
*
* @param node AST node to check
* @return true if the node is a null literal, false otherwise
*/
private static boolean isNullLiteral(DetailAST node) {
return node.getType() == TokenTypes.LITERAL_NULL;
}
/**
* Determines if the null check is redundant with the instanceof check.
*
* @param instanceOfIdent the identifier from the instanceof check
* @param nullCheckNode the node representing the null check
* @return true if the null check is unnecessary, false otherwise
*/
private static boolean isNullCheckRedundant(DetailAST instanceOfIdent,
final DetailAST nullCheckNode) {
final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT);
return nullCheckIdent != null
&& (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling())
|| isNullLiteral(nullCheckNode.getFirstChild()))
&& instanceOfIdent.getText().equals(nullCheckIdent.getText());
}
}