tech.picnic.errorprone.bugpatterns.FluxImplicitBlock Maven / Gradle / Ivy
Show all versions of error-prone-contrib Show documentation
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Preconditions.checkState;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.CONCURRENCY;
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.Position;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import tech.picnic.errorprone.utils.ThirdPartyLibrary;
/**
* A {@link BugChecker} that flags {@link reactor.core.publisher.Flux} operator usages that may
* implicitly cause the calling thread to be blocked.
*
* Note that the methods flagged here are not themselves blocking, but iterating over the
* resulting {@link Iterable} or {@link Stream} may be.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid iterating over `Flux`es in an implicitly blocking manner",
link = BUG_PATTERNS_BASE_URL + "FluxImplicitBlock",
linkType = CUSTOM,
severity = WARNING,
tags = {CONCURRENCY, PERFORMANCE})
public final class FluxImplicitBlock extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher FLUX_WITH_IMPLICIT_BLOCK =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.namedAnyOf("toIterable", "toStream")
.withNoParameters();
private static final Supplier STREAM =
Suppliers.typeFromString(Stream.class.getCanonicalName());
/** Instantiates a new {@link FluxImplicitBlock} instance. */
public FluxImplicitBlock() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!FLUX_WITH_IMPLICIT_BLOCK.matches(tree, state)) {
return Description.NO_MATCH;
}
Description.Builder description =
buildDescription(tree).addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()));
if (ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)) {
description.addFix(
suggestBlockingElementCollection(
tree, ImmutableList.class.getCanonicalName() + ".toImmutableList", state));
}
description.addFix(
suggestBlockingElementCollection(
tree, Collectors.class.getCanonicalName() + ".toList", state));
return description.build();
}
private static SuggestedFix suggestBlockingElementCollection(
MethodInvocationTree tree, String fullyQualifiedCollectorMethod, VisitorState state) {
SuggestedFix.Builder importSuggestion = SuggestedFix.builder();
String replacementMethodInvocation =
SuggestedFixes.qualifyStaticImport(fullyQualifiedCollectorMethod, importSuggestion, state);
boolean isStream =
ASTHelpers.isSubtype(ASTHelpers.getResultType(tree), STREAM.get(state), state);
String replacement =
String.format(
".collect(%s()).block()%s", replacementMethodInvocation, isStream ? ".stream()" : "");
return importSuggestion.merge(replaceMethodInvocation(tree, replacement, state)).build();
}
private static SuggestedFix.Builder replaceMethodInvocation(
MethodInvocationTree tree, String replacement, VisitorState state) {
int startPosition = state.getEndPosition(ASTHelpers.getReceiver(tree));
int endPosition = state.getEndPosition(tree);
checkState(
startPosition != Position.NOPOS && endPosition != Position.NOPOS,
"Cannot locate method to be replaced in source code");
return SuggestedFix.builder().replace(startPosition, endPosition, replacement);
}
}