Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2018 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.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
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.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import java.util.Collection;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Checks that protocol buffers built with chained builders don't set the same field twice.
*
* @author [email protected] (Graeme Morgan)
*/
@BugPattern(
summary = "A field on a protocol buffer was set twice in the same chained expression.",
severity = WARNING,
tags = StandardTags.FRAGILE_CODE)
public final class ProtoRedundantSet extends BugChecker implements MethodInvocationTreeMatcher {
/** Matches a chainable proto builder method. */
private static final Matcher PROTO_FLUENT_METHOD =
instanceMethod()
.onDescendantOfAny(
"com.google.protobuf.GeneratedMessage.Builder",
"com.google.protobuf.GeneratedMessageLite.Builder")
.withNameMatching(Pattern.compile("^(set|add|clear|put).+"));
/**
* Matches a terminal proto builder method. That is, a chainable builder method which is either
* not followed by another method invocation, or by a method invocation which is not a {@link
* #PROTO_FLUENT_METHOD}.
*/
private static final Matcher TERMINAL_PROTO_FLUENT_METHOD =
allOf(
PROTO_FLUENT_METHOD,
(tree, state) ->
!(state.getPath().getParentPath().getLeaf() instanceof MemberSelectTree
&& PROTO_FLUENT_METHOD.matches(
(ExpressionTree) state.getPath().getParentPath().getParentPath().getLeaf(),
state)));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!TERMINAL_PROTO_FLUENT_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
ListMultimap setters = ArrayListMultimap.create();
Type type = ASTHelpers.getReturnType(tree);
for (ExpressionTree current = tree;
PROTO_FLUENT_METHOD.matches(current, state);
current = ASTHelpers.getReceiver(current)) {
MethodInvocationTree method = (MethodInvocationTree) current;
if (!ASTHelpers.isSameType(type, ASTHelpers.getReturnType(current), state)) {
break;
}
Symbol symbol = ASTHelpers.getSymbol(current);
if (!(symbol instanceof MethodSymbol)) {
break;
}
String methodName = symbol.getSimpleName().toString();
// Break on methods like "addFooBuilder", otherwise we might be building a nested proto of the
// same type.
if (methodName.endsWith("Builder")) {
break;
}
match(method, methodName, setters);
}
setters.asMap().entrySet().removeIf(entry -> entry.getValue().size() <= 1);
if (setters.isEmpty()) {
return Description.NO_MATCH;
}
for (Map.Entry> entry : setters.asMap().entrySet()) {
ProtoField protoField = entry.getKey();
Collection values = entry.getValue();
state.reportMatch(describe(protoField, values, state));
}
return Description.NO_MATCH;
}
private Description describe(
ProtoField protoField, Collection locations, VisitorState state) {
// We flag up all duplicate sets, but only suggest a fix if the setter is given the same
// argument (based on source code). This is to avoid the temptation to apply the fix in
// cases like,
// MyProto.newBuilder().setFoo(copy.getFoo()).setFoo(copy.getBar())
// where the correct fix is probably to replace the second 'setFoo' with 'setBar'.
SuggestedFix.Builder fix = SuggestedFix.builder();
long values =
locations.stream().map(l -> state.getSourceForNode(l.getArgument())).distinct().count();
if (values == 1) {
for (FieldWithValue field : Iterables.skip(locations, 1)) {
MethodInvocationTree method = field.getMethodInvocation();
int startPos = state.getEndPosition(ASTHelpers.getReceiver(method));
int endPos = state.getEndPosition(method);
fix.replace(startPos, endPos, "");
}
}
return buildDescription(locations.iterator().next().getArgument())
.setMessage(
String.format(
"%s was called %s with %s. Setting the same field multiple times is redundant, and "
+ "could mask a bug.",
protoField,
nTimes(locations.size()),
values == 1 ? "the same argument" : "different arguments"))
.addFix(fix.build())
.build();
}
private static void match(
MethodInvocationTree method,
String methodName,
ListMultimap setters) {
for (FieldType fieldType : FieldType.values()) {
FieldWithValue match = fieldType.match(methodName, method);
if (match != null) {
setters.put(match.getField(), match);
}
}
}
private static String nTimes(int n) {
return n == 2 ? "twice" : String.format("%d times", n);
}
interface ProtoField {}
enum FieldType {
SINGLE {
@Override
FieldWithValue match(String name, MethodInvocationTree tree) {
if (name.startsWith("set") && tree.getArguments().size() == 1) {
return FieldWithValue.of(SingleField.of(name), tree, tree.getArguments().get(0));
}
return null;
}
},
REPEATED {
@Override
FieldWithValue match(String name, MethodInvocationTree tree) {
if (name.startsWith("set") && tree.getArguments().size() == 2) {
Integer index = ASTHelpers.constValue(tree.getArguments().get(0), Integer.class);
if (index != null) {
return FieldWithValue.of(
RepeatedField.of(name, index), tree, tree.getArguments().get(1));
}
}
return null;
}
},
MAP {
@Override
FieldWithValue match(String name, MethodInvocationTree tree) {
if (name.startsWith("put") && tree.getArguments().size() == 2) {
Object key = ASTHelpers.constValue(tree.getArguments().get(0), Object.class);
if (key != null) {
return FieldWithValue.of(MapField.of(name, key), tree, tree.getArguments().get(1));
}
}
return null;
}
};
abstract FieldWithValue match(String name, MethodInvocationTree tree);
}
@AutoValue
abstract static class SingleField implements ProtoField {
abstract String getName();
static SingleField of(String name) {
return new AutoValue_ProtoRedundantSet_SingleField(name);
}
@Override
public final String toString() {
return String.format("%s(..)", getName());
}
}
@AutoValue
abstract static class RepeatedField implements ProtoField {
abstract String getName();
abstract int getIndex();
static RepeatedField of(String name, int index) {
return new AutoValue_ProtoRedundantSet_RepeatedField(name, index);
}
@Override
public final String toString() {
return String.format("%s(%s, ..)", getName(), getIndex());
}
}
@AutoValue
abstract static class MapField implements ProtoField {
abstract String getName();
abstract Object getKey();
static MapField of(String name, Object key) {
return new AutoValue_ProtoRedundantSet_MapField(name, key);
}
@Override
public final String toString() {
return String.format("%s(%s, ..)", getName(), getKey());
}
}
@AutoValue
abstract static class FieldWithValue {
abstract ProtoField getField();
abstract MethodInvocationTree getMethodInvocation();
abstract ExpressionTree getArgument();
static FieldWithValue of(
ProtoField field, MethodInvocationTree methodInvocationTree, ExpressionTree argumentTree) {
return new AutoValue_ProtoRedundantSet_FieldWithValue(
field, methodInvocationTree, argumentTree);
}
}
}