All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.errorprone.bugpatterns.AbstractReferenceEquality Maven / Gradle / Ivy

There is a newer version: 2.27.1
Show newest version
/*
 * Copyright 2015 The Error Prone Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.errorprone.bugpatterns;

import static com.google.errorprone.dataflow.nullnesspropagation.Nullness.NONNULL;
import static com.google.errorprone.dataflow.nullnesspropagation.Nullness.NULL;
import static com.google.errorprone.matchers.Matchers.instanceEqualsInvocation;
import static com.google.errorprone.matchers.Matchers.staticEqualsInvocation;

import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.BinaryTreeMatcher;
import com.google.errorprone.dataflow.nullnesspropagation.Nullness;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TreePath;
import java.util.List;
import java.util.Optional;

/**
 * Abstract implementation of a BugPattern that detects the use of reference equality to compare
 * classes with value semantics.
 *
 * 

See e.g. {@link NumericEquality}, {@link OptionalEquality}, {@link * ProtoStringFieldReferenceEquality}, and {@link StringEquality}. * * @author [email protected] (Liam Miller-Cushon) */ public abstract class AbstractReferenceEquality extends BugChecker implements BinaryTreeMatcher { private static final Matcher EQUALS_STATIC_METHODS = staticEqualsInvocation(); private static final Matcher OBJECT_INSTANCE_EQUALS = instanceEqualsInvocation(); protected abstract boolean matchArgument(ExpressionTree tree, VisitorState state); @Override public final Description matchBinary(BinaryTree tree, VisitorState state) { switch (tree.getKind()) { case EQUAL_TO: case NOT_EQUAL_TO: break; default: return Description.NO_MATCH; } if (tree.getLeftOperand().getKind() == Kind.NULL_LITERAL || !matchArgument(tree.getLeftOperand(), state)) { return Description.NO_MATCH; } if (tree.getRightOperand().getKind() == Kind.NULL_LITERAL || !matchArgument(tree.getRightOperand(), state)) { return Description.NO_MATCH; } Description.Builder builder = buildDescription(tree); addFixes(builder, tree, state); return builder.build(); } protected void addFixes(Description.Builder builder, BinaryTree tree, VisitorState state) { ExpressionTree lhs = tree.getLeftOperand(); ExpressionTree rhs = tree.getRightOperand(); Optional fixToReplaceOrStatement = inOrStatementWithEqualsCheck(state, tree); if (fixToReplaceOrStatement.isPresent()) { builder.addFix(fixToReplaceOrStatement.get()); return; } // Swap the order (e.g. rhs.equals(lhs) if the rhs is a non-null constant, and the lhs is not if (ASTHelpers.constValue(lhs) == null && ASTHelpers.constValue(rhs) != null) { ExpressionTree tmp = lhs; lhs = rhs; rhs = tmp; } String prefix = tree.getKind() == Kind.NOT_EQUAL_TO ? "!" : ""; String lhsSource = state.getSourceForNode(lhs); String rhsSource = state.getSourceForNode(rhs); Nullness nullness = getNullness(lhs, state); // If the lhs is possibly-null, provide both options. if (nullness != NONNULL) { if (state.isAndroidCompatible()) { builder.addFix( SuggestedFix.builder() .replace( tree, String.format("%sObjects.equal(%s, %s)", prefix, lhsSource, rhsSource)) .addImport("com.google.common.base.Objects") .build()); } else { builder.addFix( SuggestedFix.builder() .replace( tree, String.format("%sObjects.equals(%s, %s)", prefix, lhsSource, rhsSource)) .addImport("java.util.Objects") .build()); } } if (nullness != NULL) { builder.addFix( SuggestedFix.replace( tree, String.format( "%s%s.equals(%s)", prefix, lhs instanceof BinaryTree ? String.format("(%s)", lhsSource) : lhsSource, rhsSource))); } } private static Optional inOrStatementWithEqualsCheck(VisitorState state, BinaryTree tree) { // Only attempt to handle a == b || a.equals(b); if (tree.getKind() == Kind.NOT_EQUAL_TO) { return Optional.empty(); } ExpressionTree lhs = tree.getLeftOperand(); ExpressionTree rhs = tree.getRightOperand(); Tree parent = state.getPath().getParentPath().getLeaf(); if (parent.getKind() != Kind.CONDITIONAL_OR) { return Optional.empty(); } BinaryTree p = (BinaryTree) parent; if (p.getLeftOperand() != tree) { // a == b is on the RHS, ignore this construction return Optional.empty(); } // If the other half of this or statement is foo.equals(bar) or Objects.equals(foo, bar) // replace the or statement with the other half as already written. ExpressionTree otherExpression = ASTHelpers.stripParentheses(p.getRightOperand()); if (!(otherExpression instanceof MethodInvocationTree)) { return Optional.empty(); } MethodInvocationTree other = (MethodInvocationTree) otherExpression; // a == b || Objects.equals(a, b) => Objects.equals(a, b) if (EQUALS_STATIC_METHODS.matches(other, state)) { List arguments = other.getArguments(); if (treesMatch(arguments.get(0), arguments.get(1), lhs, rhs)) { return Optional.of(SuggestedFix.replace(parent, state.getSourceForNode(otherExpression))); } } // a == b || a.equals(b) => a.equals(b) if (OBJECT_INSTANCE_EQUALS.matches(otherExpression, state)) { if (treesMatch(ASTHelpers.getReceiver(other), other.getArguments().get(0), lhs, rhs)) { return Optional.of(SuggestedFix.replace(parent, state.getSourceForNode(otherExpression))); } } return Optional.empty(); } private static Nullness getNullness(ExpressionTree expr, VisitorState state) { TreePath pathToExpr = new TreePath(state.getPath(), expr); return state.getNullnessAnalysis().getNullness(pathToExpr, state.context); } private static boolean treesMatch( ExpressionTree lhs1, ExpressionTree rhs1, ExpressionTree lhs2, ExpressionTree rhs2) { return (ASTHelpers.sameVariable(lhs1, lhs2) && ASTHelpers.sameVariable(rhs1, rhs2)) || (ASTHelpers.sameVariable(lhs1, rhs2) && ASTHelpers.sameVariable(rhs1, lhs2)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy