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

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

There is a newer version: 2.27.1
Show newest version
/*
 * Copyright 2016 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.matchers.Description.NO_MATCH;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.Matchers.symbolHasAnnotation;
import static com.google.errorprone.util.ASTHelpers.getReceiver;
import static com.google.errorprone.util.ASTHelpers.getSymbol;
import static com.google.errorprone.util.ASTHelpers.getType;

import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.bugpatterns.BugChecker.BinaryTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.CompoundAssignmentTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.predicates.TypePredicate;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
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 com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.MethodType;
import java.util.List;
import java.util.Optional;
import javax.lang.model.type.TypeKind;

/**
 * An abstract matcher for implicit and explicit calls to {@code Object.toString()}, for use on
 * types that do not have a human-readable {@code toString()} implementation.
 *
 * 

See examples in {@link StreamToString} and {@link ArrayToString}. */ public abstract class AbstractToString extends BugChecker implements BinaryTreeMatcher, MethodInvocationTreeMatcher, CompoundAssignmentTreeMatcher { /** The type to match on. */ protected abstract TypePredicate typePredicate(); /** * Constructs a fix for an implicit toString call, e.g. from string concatenation or from passing * an argument to {@code println} or {@code StringBuilder.append}. * * @param tree the tree node for the expression being converted to a String */ protected abstract Optional implicitToStringFix(ExpressionTree tree, VisitorState state); /** Adds the description message for match on the type without fixes. */ protected Optional descriptionMessageForDefaultMatch(Type type, VisitorState state) { return Optional.empty(); } /** Whether this kind of toString call is allowable for this check. */ protected boolean allowableToStringKind(ToStringKind toStringKind) { return false; } /** * Constructs a fix for an explicit toString call, e.g. from {@code Object.toString()} or {@code * String.valueOf()}. * * @param parent the expression's parent (e.g. {@code String.valueOf(expression)}) */ protected abstract Optional toStringFix( Tree parent, ExpressionTree expression, VisitorState state); private static final Matcher TO_STRING = instanceMethod().anyClass().named("toString").withNoParameters(); private static final Matcher FLOGGER_LOG = instanceMethod().onDescendantOf("com.google.common.flogger.LoggingApi").named("log"); private static final Matcher FORMAT_METHOD = symbolHasAnnotation(FormatMethod.class); private static final Matcher STRING_FORMAT = staticMethod().onClass("java.lang.String").named("format"); private static final Matcher VALUE_OF = staticMethod() .onClass("java.lang.String") .named("valueOf") .withParameters("java.lang.Object"); private static final Matcher PRINT_STRING = anyOf( instanceMethod() .onDescendantOf("java.io.PrintStream") .namedAnyOf("print", "println") .withParameters("java.lang.Object"), instanceMethod() .onExactClass("java.lang.StringBuilder") .named("append") .withParameters("java.lang.Object")); private static boolean isInVarargsPosition( ExpressionTree argTree, MethodInvocationTree methodInvocationTree, VisitorState state) { int parameterCount = getSymbol(methodInvocationTree).getParameters().size(); List arguments = methodInvocationTree.getArguments(); // Don't match if we're passing an array into a varargs parameter, but do match if there are // other parameters along with it. return (arguments.size() > parameterCount || !state.getTypes().isArray(getType(argTree))) && arguments.indexOf(argTree) >= parameterCount - 1; } @Override public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { if (PRINT_STRING.matches(tree, state)) { for (ExpressionTree argTree : tree.getArguments()) { handleStringifiedTree(argTree, ToStringKind.IMPLICIT, state); } } if (VALUE_OF.matches(tree, state)) { for (ExpressionTree argTree : tree.getArguments()) { handleStringifiedTree( tree, argTree, ToStringKind.EXPLICIT, state.withPath(new TreePath(state.getPath(), argTree))); } } if (TO_STRING.matches(tree, state)) { ExpressionTree receiver = getReceiver(tree); if (receiver != null) { handleStringifiedTree(tree, receiver, ToStringKind.EXPLICIT, state); } } if (FORMAT_METHOD.matches(tree, state)) { for (ExpressionTree argTree : tree.getArguments()) { if (isInVarargsPosition(argTree, tree, state)) { handleStringifiedTree(argTree, ToStringKind.FORMAT_METHOD, state); } } } if (STRING_FORMAT.matches(tree, state)) { for (ExpressionTree argTree : tree.getArguments()) { if (isInVarargsPosition(argTree, tree, state)) { handleStringifiedTree(argTree, ToStringKind.IMPLICIT, state); } } } if (FLOGGER_LOG.matches(tree, state)) { for (ExpressionTree argTree : tree.getArguments()) { handleStringifiedTree(argTree, ToStringKind.FLOGGER, state); } } return NO_MATCH; } @Override public Description matchBinary(BinaryTree tree, VisitorState state) { if (!state.getTypes().isSameType(getType(tree), state.getSymtab().stringType)) { return NO_MATCH; } if (tree.getKind() == Kind.PLUS) { handleStringifiedTree(tree.getLeftOperand(), ToStringKind.IMPLICIT, state); handleStringifiedTree(tree.getRightOperand(), ToStringKind.IMPLICIT, state); } if (tree.getKind() == Kind.PLUS_ASSIGNMENT) { handleStringifiedTree(tree.getRightOperand(), ToStringKind.IMPLICIT, state); } return NO_MATCH; } @Override public Description matchCompoundAssignment(CompoundAssignmentTree tree, VisitorState state) { if (state.getTypes().isSameType(getType(tree.getVariable()), state.getSymtab().stringType) && tree.getKind() == Kind.PLUS_ASSIGNMENT) { handleStringifiedTree(tree.getExpression(), ToStringKind.IMPLICIT, state); } return NO_MATCH; } private void handleStringifiedTree( ExpressionTree tree, ToStringKind toStringKind, VisitorState state) { handleStringifiedTree(tree, tree, toStringKind, state); } private void handleStringifiedTree( Tree parent, ExpressionTree tree, ToStringKind toStringKind, VisitorState state) { Type type = type(tree); if (type.getKind() == TypeKind.NULL || !typePredicate().apply(type, state) || allowableToStringKind(toStringKind)) { return; } state.reportMatch(maybeFix(tree, state, type, getFix(tree, state, parent, toStringKind))); } private static Type type(ExpressionTree tree) { Type type = getType(tree); if (type instanceof MethodType) { return type.getReturnType(); } return type; } private Optional getFix( ExpressionTree tree, VisitorState state, Tree parent, ToStringKind toStringKind) { switch (toStringKind) { case IMPLICIT: case FLOGGER: case FORMAT_METHOD: return implicitToStringFix(tree, state); case EXPLICIT: return toStringFix(parent, tree, state); case NONE: // fall out } throw new AssertionError(); } private Description maybeFix(Tree tree, VisitorState state, Type matchedType, Optional fix) { Description.Builder description = buildDescription(tree); fix.ifPresent(description::addFix); descriptionMessageForDefaultMatch(matchedType, state).ifPresent(description::setMessage); return description.build(); } enum ToStringKind { /** String concatenation, or an enclosing print method. */ IMPLICIT, /** {@code String.valueOf()} or {@code #toString()}. */ EXPLICIT, FORMAT_METHOD, FLOGGER, NONE, } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy