mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Stream inlining: more precise source/terminal handling
Fixes IDEA-176129 Provide Control flow related inspection for Stream.anyMatch() / Stream.allMatch() / Stream.noneMatch()
This commit is contained in:
+239
-30
@@ -15,10 +15,8 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.dataFlow.inliner;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.CFGBuilder;
|
||||
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
|
||||
import com.intellij.codeInspection.dataFlow.NullabilityProblem;
|
||||
import com.intellij.codeInspection.dataFlow.Nullness;
|
||||
import com.intellij.codeInspection.dataFlow.*;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
@@ -40,8 +38,14 @@ public class StreamChainInliner implements CallInliner {
|
||||
"average", "forEach", "forEachOrdered", "min", "max", "toList", "toSet"};
|
||||
private static final CallMatcher TERMINAL_CALL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, TERMINALS);
|
||||
|
||||
private static final CallMatcher LAMBDA_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch",
|
||||
"noneMatch", "forEach", "forEachOrdered").parameterCount(1);
|
||||
private static final CallMatcher FOR_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "forEach", "forEachOrdered").parameterCount(1);
|
||||
private static final CallMatcher MATCH_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch",
|
||||
"noneMatch").parameterCount(1);
|
||||
private static final CallMatcher SUM_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "sum", "count").parameterCount(0);
|
||||
private static final CallMatcher OPTIONAL_TERMINAL =
|
||||
anyOf(instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "min", "max").parameterCount(0),
|
||||
instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "min", "max", "reduce").parameterCount(1),
|
||||
instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "findFirst", "findAny").parameterCount(0));
|
||||
|
||||
private static final CallMatcher SKIP_STEP =
|
||||
instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "unordered", "parallel", "sequential", "sorted").parameterCount(0);
|
||||
@@ -61,6 +65,23 @@ public class StreamChainInliner implements CallInliner {
|
||||
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "generate").parameterCount(1),
|
||||
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "generate").parameterCount(1),
|
||||
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "generate").parameterCount(1));
|
||||
private static final CallMatcher STREAM_EMPTY = anyOf(
|
||||
staticCall(JAVA_UTIL_STREAM_STREAM, "empty").parameterCount(0),
|
||||
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "empty").parameterCount(0),
|
||||
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "empty").parameterCount(0),
|
||||
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "empty").parameterCount(0));
|
||||
private static final CallMatcher STREAM_OF = anyOf(
|
||||
staticCall(JAVA_UTIL_STREAM_STREAM, "of").parameterTypes("T"),
|
||||
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "of").parameterTypes("int"),
|
||||
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "of").parameterTypes("long"),
|
||||
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "of").parameterTypes("double"));
|
||||
private static final CallMatcher STREAM_OF_ARRAY = anyOf(
|
||||
staticCall(JAVA_UTIL_STREAM_STREAM, "of").parameterTypes("T[]"),
|
||||
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "of").parameterTypes("int[]"),
|
||||
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "of").parameterTypes("long[]"),
|
||||
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "of").parameterTypes("double[]"));
|
||||
private static final CallMatcher ARRAYS_STREAM = staticCall(JAVA_UTIL_ARRAYS, "stream").parameterCount(1);
|
||||
private static final CallMatcher COLLECTION_STREAM = instanceCall(JAVA_UTIL_COLLECTION, "stream").parameterCount(0);
|
||||
|
||||
private static final CallMapper<UnaryOperator<Step>> INTERMEDIATE_STEP_MAPPER = new CallMapper<UnaryOperator<Step>>()
|
||||
.register(FILTER, (PsiMethodCallExpression call) -> (Step next) -> new FilterStep(call, next))
|
||||
@@ -71,6 +92,12 @@ public class StreamChainInliner implements CallInliner {
|
||||
.register(BOXED, (PsiMethodCallExpression call) -> (Step next) -> new BoxedStep(call, next))
|
||||
.register(STATE_FILTER, (PsiMethodCallExpression call) -> (Step next) -> new StateFilterStep(call, next));
|
||||
|
||||
private static final CallMapper<Step> TERMINAL_STEP_MAPPER = new CallMapper<Step>()
|
||||
.register(FOR_TERMINAL, LambdaTerminalStep::new)
|
||||
.register(MATCH_TERMINAL, MatchTerminalStep::new)
|
||||
.register(SUM_TERMINAL, SumTerminalStep::new)
|
||||
.register(OPTIONAL_TERMINAL, OptionalTerminalStep::new);
|
||||
|
||||
static abstract class Step {
|
||||
final Step myNext;
|
||||
final @NotNull PsiMethodCallExpression myCall;
|
||||
@@ -92,6 +119,16 @@ public class StreamChainInliner implements CallInliner {
|
||||
}
|
||||
|
||||
abstract void iteration(CFGBuilder builder);
|
||||
|
||||
void pushResult(CFGBuilder builder) {
|
||||
if (myNext != null) {
|
||||
myNext.pushResult(builder);
|
||||
}
|
||||
else {
|
||||
builder.push(builder.getFactory()
|
||||
.createTypeValue(myCall.getType(), DfaPsiUtil.getElementNullability(myCall.getType(), myCall.resolveMethod())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class UnknownTerminalStep extends Step {
|
||||
@@ -114,6 +151,31 @@ public class StreamChainInliner implements CallInliner {
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class TerminalStep extends Step {
|
||||
PsiVariable myResult;
|
||||
|
||||
TerminalStep(@NotNull PsiMethodCallExpression call, PsiExpression function) {
|
||||
super(call, null, function);
|
||||
}
|
||||
|
||||
@Override
|
||||
void before(CFGBuilder builder) {
|
||||
myResult = builder.createTempVariable(myCall.getType());
|
||||
builder.pushVariable(myResult)
|
||||
.chain(this::pushInitialValue)
|
||||
.assign()
|
||||
.pop()
|
||||
.chain(super::before);
|
||||
}
|
||||
|
||||
protected abstract void pushInitialValue(CFGBuilder builder);
|
||||
|
||||
@Override
|
||||
void pushResult(CFGBuilder builder) {
|
||||
builder.push(builder.getFactory().getVarFactory().createVariableValue(myResult, false));
|
||||
}
|
||||
}
|
||||
|
||||
static class LambdaTerminalStep extends Step {
|
||||
LambdaTerminalStep(@NotNull PsiMethodCallExpression call) {
|
||||
super(call, null, call.getArgumentList().getExpressions()[0]);
|
||||
@@ -125,6 +187,74 @@ public class StreamChainInliner implements CallInliner {
|
||||
}
|
||||
}
|
||||
|
||||
static class SumTerminalStep extends TerminalStep {
|
||||
SumTerminalStep(@NotNull PsiMethodCallExpression call) {
|
||||
super(call, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pushInitialValue(CFGBuilder builder) {
|
||||
PsiType type = myCall.getType();
|
||||
Object value = null;
|
||||
if (PsiType.INT.equals(type)) {
|
||||
value = 0;
|
||||
}
|
||||
else if (PsiType.LONG.equals(type)) {
|
||||
value = 0L;
|
||||
}
|
||||
else if (PsiType.DOUBLE.equals(type)) {
|
||||
value = 0.0;
|
||||
}
|
||||
builder.push(builder.getFactory().getConstFactory().createFromValue(value, type, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
void iteration(CFGBuilder builder) {
|
||||
builder.pushVariable(myResult).pushUnknown().assign().splice(2);
|
||||
}
|
||||
}
|
||||
|
||||
static class OptionalTerminalStep extends TerminalStep {
|
||||
OptionalTerminalStep(@NotNull PsiMethodCallExpression call) {
|
||||
super(call, ArrayUtil.getFirstElement(call.getArgumentList().getExpressions()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pushInitialValue(CFGBuilder builder) {
|
||||
builder.push(builder.getFactory().getOptionalFactory().getOptional(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
void iteration(CFGBuilder builder) {
|
||||
if (myFunction != null) {
|
||||
builder.pushUnknown().invokeFunction(2, myFunction);
|
||||
}
|
||||
builder.pushVariable(myResult).push(builder.getFactory().getOptionalFactory().getOptional(true)).assign().splice(2);
|
||||
}
|
||||
}
|
||||
|
||||
static class MatchTerminalStep extends TerminalStep {
|
||||
MatchTerminalStep(@NotNull PsiMethodCallExpression call) {
|
||||
super(call, call.getArgumentList().getExpressions()[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pushInitialValue(CFGBuilder builder) {
|
||||
builder.push(builder.getFactory().getBoolean(!"anyMatch".equals(myCall.getMethodExpression().getReferenceName())));
|
||||
}
|
||||
|
||||
@Override
|
||||
void iteration(CFGBuilder builder) {
|
||||
builder.invokeFunction(1, myFunction)
|
||||
.ifConditionIs(!"allMatch".equals(myCall.getMethodExpression().getReferenceName()))
|
||||
.pushVariable(myResult)
|
||||
.push(builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName())))
|
||||
.assign()
|
||||
.pop()
|
||||
.endIf();
|
||||
}
|
||||
}
|
||||
|
||||
static class FilterStep extends Step {
|
||||
FilterStep(@NotNull PsiMethodCallExpression call, Step next) {
|
||||
super(call, next, call.getArgumentList().getExpressions()[0]);
|
||||
@@ -178,9 +308,22 @@ public class StreamChainInliner implements CallInliner {
|
||||
streamSource = body;
|
||||
chain = next;
|
||||
if (body instanceof PsiMethodCallExpression) {
|
||||
chain = buildChain((PsiMethodCallExpression)body, next);
|
||||
if (chain != next) {
|
||||
Step filteredNext = new Step(call, next, null) {
|
||||
@Override
|
||||
void before(CFGBuilder builder) {
|
||||
// skip following steps: their before behavior is added before the main loop
|
||||
}
|
||||
|
||||
@Override
|
||||
void iteration(CFGBuilder builder) {
|
||||
myNext.iteration(builder);
|
||||
}
|
||||
};
|
||||
chain = buildChain((PsiMethodCallExpression)body, filteredNext);
|
||||
if (chain != filteredNext) {
|
||||
streamSource = chain.myCall.getMethodExpression().getQualifierExpression();
|
||||
} else {
|
||||
streamSource = body;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,15 +443,10 @@ public class StreamChainInliner implements CallInliner {
|
||||
PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
|
||||
Step terminalStep = createTerminalStep(call);
|
||||
Step firstStep = buildChain(qualifierCall, terminalStep);
|
||||
if (firstStep == terminalStep) {
|
||||
// Do not handle specially case when only terminal operation is known: at least one intermediate op should be known as well
|
||||
return false;
|
||||
}
|
||||
PsiExpression originalQualifier = firstStep.myCall.getMethodExpression().getQualifierExpression();
|
||||
if (originalQualifier == null) return false;
|
||||
buildStreamCFG(builder, firstStep, originalQualifier);
|
||||
builder.push(
|
||||
builder.getFactory().createTypeValue(call.getType(), DfaPsiUtil.getElementNullability(call.getType(), call.resolveMethod())));
|
||||
firstStep.pushResult(builder);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -326,19 +464,92 @@ public class StreamChainInliner implements CallInliner {
|
||||
.assign()
|
||||
.chain(firstStep::iteration)
|
||||
.endWhileUnknown();
|
||||
} else {
|
||||
builder
|
||||
.pushExpression(originalQualifier)
|
||||
.checkNotNull(firstStep.myCall, NullabilityProblem.callNPE)
|
||||
.pop()
|
||||
.chain(firstStep::before)
|
||||
.doWhile()
|
||||
.pushVariable(builder.createTempVariable(inType))
|
||||
.push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
|
||||
.assign()
|
||||
.chain(firstStep::iteration)
|
||||
.endWhileUnknown();
|
||||
return;
|
||||
}
|
||||
boolean empty = STREAM_EMPTY.test(sourceCall);
|
||||
boolean array = ARRAYS_STREAM.test(sourceCall);
|
||||
boolean single = STREAM_OF.test(sourceCall);
|
||||
if (STREAM_OF_ARRAY.test(sourceCall)) {
|
||||
PsiExpression[] args = sourceCall.getArgumentList().getExpressions();
|
||||
if(args.length == 0) {
|
||||
empty = true;
|
||||
} else if(args.length == 1) {
|
||||
if(MethodCallUtils.isVarArgCall(sourceCall)) {
|
||||
single = true;
|
||||
} else {
|
||||
array = true;
|
||||
}
|
||||
} else {
|
||||
builder
|
||||
.pushExpression(originalQualifier)
|
||||
.pop()
|
||||
.chain(firstStep::before)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (empty) {
|
||||
builder.chain(firstStep::before); // skip loop at all
|
||||
return;
|
||||
}
|
||||
if (single) {
|
||||
// exactly single element
|
||||
builder
|
||||
.pushExpression(sourceCall.getArgumentList().getExpressions()[0])
|
||||
.chain(firstStep::before)
|
||||
.chain(firstStep::iteration);
|
||||
return;
|
||||
}
|
||||
if (array) {
|
||||
PsiExpression qualifierExpression = sourceCall.getArgumentList().getExpressions()[0];
|
||||
DfaValue qualifierValue = builder.getFactory().createValue(qualifierExpression);
|
||||
if (qualifierValue != null) {
|
||||
builder.pushExpression(qualifierExpression)
|
||||
.chain(firstStep::before)
|
||||
.checkNotNull(qualifierExpression, NullabilityProblem.passingNullableToNotNullParameter)
|
||||
.pop()
|
||||
.push(SpecialField.ARRAY_LENGTH.createValue(builder.getFactory(), qualifierValue))
|
||||
.push(builder.getFactory().getConstFactory().createFromValue(0, PsiType.INT, null))
|
||||
.ifCondition(JavaTokenType.GT)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType))
|
||||
.endIf();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (COLLECTION_STREAM.test(sourceCall)) {
|
||||
PsiExpression qualifierExpression = sourceCall.getMethodExpression().getQualifierExpression();
|
||||
DfaValue qualifierValue = builder.getFactory().createValue(qualifierExpression);
|
||||
if (qualifierValue != null) {
|
||||
builder.pushExpression(qualifierExpression)
|
||||
.chain(firstStep::before)
|
||||
.checkNotNull(sourceCall, NullabilityProblem.callNPE)
|
||||
.pop()
|
||||
.push(SpecialField.COLLECTION_SIZE.createValue(builder.getFactory(), qualifierValue))
|
||||
.push(builder.getFactory().getConstFactory().createFromValue(0, PsiType.INT, null))
|
||||
.ifCondition(JavaTokenType.GT)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType))
|
||||
.endIf();
|
||||
return;
|
||||
}
|
||||
}
|
||||
builder
|
||||
.pushExpression(originalQualifier)
|
||||
.checkNotNull(firstStep.myCall, NullabilityProblem.callNPE)
|
||||
.pop()
|
||||
.chain(firstStep::before)
|
||||
.pushUnknown()
|
||||
.ifConditionIs(true)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType))
|
||||
.endIf();
|
||||
}
|
||||
|
||||
private static void makeMainLoop(CFGBuilder builder, Step firstStep, PsiType inType) {
|
||||
builder.doWhile()
|
||||
.pushVariable(builder.createTempVariable(inType))
|
||||
.push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
|
||||
.assign()
|
||||
.chain(firstStep::iteration)
|
||||
.endWhileUnknown();
|
||||
}
|
||||
|
||||
static Step buildChain(PsiMethodCallExpression qualifierCall, Step terminalStep) {
|
||||
@@ -361,9 +572,7 @@ public class StreamChainInliner implements CallInliner {
|
||||
}
|
||||
|
||||
private static Step createTerminalStep(PsiMethodCallExpression call) {
|
||||
if (LAMBDA_TERMINAL.test(call)) {
|
||||
return new LambdaTerminalStep(call);
|
||||
}
|
||||
return new UnknownTerminalStep(call);
|
||||
Step step = TERMINAL_STEP_MAPPER.mapFirst(call);
|
||||
return step == null ? new UnknownTerminalStep(call) : step;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,23 @@ public class StreamInlining {
|
||||
list.stream().map(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>).forEach(System.out::println);
|
||||
list.stream().flatMap(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>).forEach(System.out::println);
|
||||
list.stream().filter(x -> x != null).forEach(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
|
||||
List<String> l = null;
|
||||
l.<warning descr="Method invocation 'stream' may produce 'java.lang.NullPointerException'">stream</warning>().count();
|
||||
int[] arr = null;
|
||||
Arrays.stream(<warning descr="Argument 'arr' might be null">arr</warning>).count();
|
||||
Stream<String> stream = null;
|
||||
stream.<warning descr="Method invocation 'filter' may produce 'java.lang.NullPointerException'">filter</warning>(x -> x != null).forEach(System.out::println);
|
||||
}
|
||||
|
||||
void testMethodRef(List<String> list, int[] data) {
|
||||
if(list.stream().map(String::new).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)) {
|
||||
if(<warning descr="Condition 'list.stream().map(String::new).anyMatch(x -> x == null)' is always 'false'">list.stream().map(String::new).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)</warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
if(Arrays.stream(data).mapToObj(int[]::new).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)) {
|
||||
if(<warning descr="Condition 'Arrays.stream(data).mapToObj(int[]::new).anyMatch(x -> x == null)' is always 'false'">Arrays.stream(data).mapToObj(int[]::new).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)</warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
list.stream().filter(Objects::isNull).map(<warning descr="Method reference invocation 'String::trim' may produce 'java.lang.NullPointerException'">String::trim</warning>).forEach(System.out::println);
|
||||
if(list.stream().filter(Objects::nonNull).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)) {
|
||||
if(<warning descr="Condition 'list.stream().filter(Objects::nonNull).anyMatch(x -> x == null)' is always 'false'">list.stream().filter(Objects::nonNull).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)</warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
list.stream().map(Integer::valueOf).filter(<warning descr="Method reference result is always 'true'">Objects::nonNull</warning>).forEach(System.out::println);
|
||||
@@ -77,7 +81,8 @@ public class StreamInlining {
|
||||
is.boxed().filter(x -> <warning descr="Condition 'x != null' is always 'true'">x != null</warning>).forEach(s -> System.out.println(s));
|
||||
}
|
||||
|
||||
boolean flatMap(List<String> list) {
|
||||
boolean flatMap(List<String> list, List<List<String>> ll) {
|
||||
System.out.println(ll.stream().flatMap(l -> l.stream()).count());
|
||||
return list.stream().map(s -> s.isEmpty() ? null : s)
|
||||
.flatMap(s -> Stream.of(s, s.<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>()).filter(r -> r != null))
|
||||
.anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>);
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
|
||||
public class StreamKnownSource {
|
||||
void testFlatMap(List<String> list) {
|
||||
long count = list.stream().<String>flatMap(s -> Stream.empty()).count();
|
||||
if(<warning descr="Condition 'count > 0' is always 'false'">count > 0</warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
}
|
||||
|
||||
void testEmptyStream(List<String> list) {
|
||||
long result = Stream.empty().count();
|
||||
|
||||
if (<warning descr="Condition 'result > 0' is always 'false'">result > 0</warning>) {
|
||||
System.out.println("Never");
|
||||
}
|
||||
|
||||
int sum = list.stream().filter(Objects::isNull).filter(<warning descr="Method reference result is always 'false'">Objects::nonNull</warning>).mapToInt(String::length).sum();
|
||||
if (<warning descr="Condition 'sum == 0' is always 'true'">sum == 0</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
|
||||
Optional<String> any = Stream.<String>empty().map(String::trim).findAny();
|
||||
if (<warning descr="Condition 'any.isPresent()' is always 'false'">any.isPresent()</warning>) {
|
||||
System.out.println("Never");
|
||||
}
|
||||
Optional<String> any2 = Stream.of("x").map(String::trim).findAny();
|
||||
if (<warning descr="Condition 'any2.isPresent()' is always 'true'">any2.isPresent()</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
Optional<String> any3 = list.stream().map(String::trim).findAny();
|
||||
if (any3.isPresent()) {
|
||||
System.out.println("Possible");
|
||||
}
|
||||
Optional<String> any4 = Stream.of("x").limit(list.size()).map(String::trim).findAny();
|
||||
if (any4.isPresent()) {
|
||||
System.out.println("Probably");
|
||||
}
|
||||
|
||||
boolean emptyAll = Stream.empty().allMatch(Objects::nonNull);
|
||||
if (<warning descr="Condition 'emptyAll' is always 'true'">emptyAll</warning>) {
|
||||
System.out.println("True");
|
||||
}
|
||||
boolean emptyAny = Stream.empty().anyMatch(Objects::nonNull);
|
||||
if (<warning descr="Condition 'emptyAny' is always 'false'">emptyAny</warning>) {
|
||||
System.out.println("False");
|
||||
}
|
||||
boolean emptyNone = Stream.empty().noneMatch(Objects::nonNull);
|
||||
if (<warning descr="Condition 'emptyNone' is always 'true'">emptyNone</warning>) {
|
||||
System.out.println("True");
|
||||
}
|
||||
|
||||
boolean allMatch = Stream.generate(() -> "foo").limit(10).allMatch(<warning descr="Method reference result is always 'false'">"bar"::equals</warning>);
|
||||
if (allMatch) { // currently we're not aware that stream is non-empty (limit arg is not processed), and allMatch could be true for empty list
|
||||
System.out.println("Who knows?");
|
||||
}
|
||||
Optional<String> min = Stream.<String>empty().min(Comparator.comparing(String::length));
|
||||
if(<warning descr="Condition 'min.isPresent()' is always 'false'">min.isPresent()</warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
}
|
||||
|
||||
void testSingleElement(String foo) {
|
||||
Optional<String> notNull = Stream.of(foo).filter(Objects::nonNull).findAny();
|
||||
if(<warning descr="Condition 'notNull.isPresent() && foo == null' is always 'false'">notNull.isPresent() && <warning descr="Condition 'foo == null' is always 'false' when reached">foo == null</warning></warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
OptionalInt max = IntStream.of(1).max();
|
||||
if(<warning descr="Condition 'max.isPresent()' is always 'true'">max.isPresent()</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
OptionalLong opt = LongStream.of(2).reduce(Long::sum);
|
||||
if(<warning descr="Condition 'opt.isPresent()' is always 'true'">opt.isPresent()</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
}
|
||||
|
||||
void testStreamOf(int[] arr) {
|
||||
if(Stream.of().count() > 0) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
if(<warning descr="Condition 'Stream.of(\"foo\", \"bar\").findFirst().isPresent()' is always 'true'">Stream.of("foo", "bar").findFirst().isPresent()</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
if(<warning descr="Condition 'DoubleStream.of(0.0, 1.0, 2.0, 3.0).map(x -> x*2).max().isPresent()' is always 'true'">DoubleStream.of(0.0, 1.0, 2.0, 3.0).map(x -> x*2).max().isPresent()</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
if(<warning descr="Condition 'IntStream.of(arr).filter(x -> x > 0).sum() > 0 && arr.length == 0' is always 'false'">IntStream.of(arr).filter(x -> x > 0).sum() > 0 && <warning descr="Condition 'arr.length == 0' is always 'false' when reached">arr.length == 0</warning></warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
}
|
||||
|
||||
void testListSize(List<String> list, int[] arr) {
|
||||
if (<warning descr="Condition 'arr.length == 0 && Arrays.stream(arr).anyMatch(x -> x > 0)' is always 'false'">arr.length == 0 && <warning descr="Condition 'Arrays.stream(arr).anyMatch(x -> x > 0)' is always 'false' when reached">Arrays.stream(arr).anyMatch(x -> x > 0)</warning></warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
if (<warning descr="Condition 'Arrays.stream(arr).anyMatch(x -> x > 0) && arr.length == 0' is always 'false'">Arrays.stream(arr).anyMatch(x -> x > 0) && <warning descr="Condition 'arr.length == 0' is always 'false' when reached">arr.length == 0</warning></warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
boolean empty = list.isEmpty();
|
||||
String res = list.stream().map(String::trim).findFirst().orElse(null);
|
||||
if(res == null && <warning descr="Condition 'empty' is always 'true' when reached">empty</warning>) {
|
||||
System.out.println("res == null -> empty list");
|
||||
}
|
||||
Optional<String> first = list.stream().filter(Objects::nonNull).findFirst();
|
||||
if (empty) {
|
||||
System.out.println(first.<warning descr="'Optional.get()' without 'isPresent()' check">get</warning>());
|
||||
}
|
||||
}
|
||||
|
||||
// IDEA-176129
|
||||
void foo(List<Long> list) {
|
||||
if (!list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasNoNulls = list.stream().allMatch(Objects::nonNull);
|
||||
|
||||
if(<warning descr="Condition 'hasNoNulls' is always 'true'">hasNoNulls</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
public void testStreamInlining() { doTest(); }
|
||||
public void testStreamKnownSource() { doTest(); }
|
||||
|
||||
public void testMethodVsExpressionTypeAnnotationConflict() {
|
||||
setupCustomAnnotations("withTypeUse", "{ElementType.METHOD, ElementType.TYPE_USE}", myFixture);
|
||||
|
||||
Reference in New Issue
Block a user