From df186337c1fd915d5ab4d5124290073307972c5b Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 14 Sep 2022 13:32:10 +0200 Subject: [PATCH] [java-dfa] Do not assume that unknown terminal operation in stream does not escape arguments Fixes IDEA-301057 IDEA reports 'Collection C is always empty' when pass C to custom function `SmartStream.toSet(set)`, but it may not. GitOrigin-RevId: a649ba5bc00de249e7819e4780304ae66517ea55 --- .../java/inliner/StreamChainInliner.java | 5 +- .../fixture/CustomStreamImplementation.java | 237 ++++++++++++++++++ .../DataFlowInspection8Test.java | 1 + 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/CustomStreamImplementation.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/StreamChainInliner.java index 258ce2203cc1..24e8efa71795 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/StreamChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inliner/StreamChainInliner.java @@ -71,6 +71,7 @@ public class StreamChainInliner implements CallInliner { private static final CallMatcher COLLECT_TERMINAL = instanceCall(JAVA_UTIL_STREAM_STREAM, "collect").parameterTypes("java.util.stream.Collector"); private static final CallMatcher TO_LIST_TERMINAL = instanceCall(JAVA_UTIL_STREAM_STREAM, "toList") + .parameterCount(0) .withLanguageLevelAtLeast(LanguageLevel.JDK_16); private static final CallMatcher COLLECT3_TERMINAL = instanceCall(JAVA_UTIL_STREAM_STREAM, "collect").parameterTypes("java.util.function.Supplier", @@ -217,9 +218,11 @@ public class StreamChainInliner implements CallInliner { @Override void before(CFGBuilder builder) { + builder.pushUnknown(); // qualifier for (PsiExpression arg : myCall.getArgumentList().getExpressions()) { - builder.pushExpression(arg).pop(); + builder.pushExpression(arg); } + builder.call(myCall).pop(); super.before(builder); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/CustomStreamImplementation.java b/java/java-tests/testData/inspection/dataFlow/fixture/CustomStreamImplementation.java new file mode 100644 index 000000000000..2aee3629b3f0 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/CustomStreamImplementation.java @@ -0,0 +1,237 @@ +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +class Demo { + void test() { + List list = Arrays.asList(1, 2, 3); + List target = new ArrayList<>(); + from(list).toList(target); + System.out.println(target.isEmpty()); + } + + static SmartStream from(Collection collection) { + return asSmart(collection.stream()); + } + + static SmartStream asSmart(Stream stream) { + if (stream instanceof SmartStream) { + return ((SmartStream) stream); + } else { + return new SmartStream<>(stream); + } + } + + static class SmartStream implements Stream { + private final Stream stream; + + protected SmartStream(Stream stream) { + this.stream = stream; + } + + public > C toList(C destination) { + return stream.collect(Collectors.toCollection(() -> destination)); + } + + @Override + public SmartStream filter(Predicate predicate) { + return asSmart(stream.filter(predicate)); + } + + @Override + public SmartStream map(Function mapper) { + return asSmart(stream.map(mapper)); + } + + @Override + public IntStream mapToInt(ToIntFunction mapper) { + return stream.mapToInt(mapper); + } + + @Override + public LongStream mapToLong(ToLongFunction mapper) { + return stream.mapToLong(mapper); + } + + @Override + public DoubleStream mapToDouble(ToDoubleFunction mapper) { + return stream.mapToDouble(mapper); + } + + @Override + public SmartStream flatMap(Function> mapper) { + return asSmart(stream.flatMap(mapper)); + } + + @Override + public IntStream flatMapToInt(Function mapper) { + return stream.flatMapToInt(mapper); + } + + @Override + public LongStream flatMapToLong(Function mapper) { + return stream.flatMapToLong(mapper); + } + + @Override + public DoubleStream flatMapToDouble(Function mapper) { + return stream.flatMapToDouble(mapper); + } + + @Override + public SmartStream distinct() { + return asSmart(stream.distinct()); + } + + @Override + public SmartStream sorted() { + return asSmart(stream.sorted()); + } + + @Override + public SmartStream sorted(Comparator comparator) { + return asSmart(stream.sorted(comparator)); + } + + @Override + public SmartStream peek(Consumer action) { + return asSmart(stream.peek(action)); + } + + @Override + public SmartStream limit(long maxSize) { + return asSmart(stream.limit(maxSize)); + } + + @Override + public SmartStream skip(long n) { + return asSmart(stream.skip(n)); + } + + @Override + public void forEach(Consumer action) { + stream.forEach(action); + } + + @Override + public void forEachOrdered(Consumer action) { + stream.forEachOrdered(action); + } + + @Override + public Object[] toArray() { + return stream.toArray(); + } + + @SuppressWarnings("SuspiciousToArrayCall") + @Override + public A[] toArray(IntFunction generator) { + return stream.toArray(generator); + } + + @Override + public T reduce(T identity, BinaryOperator accumulator) { + return stream.reduce(identity, accumulator); + } + + @Override + public Optional reduce(BinaryOperator accumulator) { + return stream.reduce(accumulator); + } + + @Override + public U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { + return stream.reduce(identity, accumulator, combiner); + } + + @Override + public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { + return stream.collect(supplier, accumulator, combiner); + } + + @Override + public R collect(Collector collector) { + return stream.collect(collector); + } + + @Override + public Optional min(Comparator comparator) { + return stream.min(comparator); + } + + @Override + public Optional max(Comparator comparator) { + return stream.max(comparator); + } + + @Override + public long count() { + return stream.count(); + } + + @Override + public boolean anyMatch(Predicate predicate) { + return stream.anyMatch(predicate); + } + + @Override + public boolean allMatch(Predicate predicate) { + return stream.allMatch(predicate); + } + + @Override + public boolean noneMatch(Predicate predicate) { + return stream.noneMatch(predicate); + } + + @Override + public Optional findFirst() { + return stream.findFirst(); + } + + @Override + public Optional findAny() { + return stream.findAny(); + } + + @Override + public Iterator iterator() { + return stream.iterator(); + } + + @Override + public Spliterator spliterator() { + return stream.spliterator(); + } + + @Override + public boolean isParallel() { + return stream.isParallel(); + } + + @Override + public SmartStream sequential() { + return asSmart(stream.sequential()); + } + + @Override + public SmartStream parallel() { + return asSmart(stream.parallel()); + } + + @Override + public SmartStream unordered() { + return asSmart(stream.unordered()); + } + + @Override + public SmartStream onClose(Runnable closeHandler) { + return asSmart(stream.onClose(closeHandler)); + } + + @Override + public void close() { + stream.close(); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index cd161f05de2f..04b724f130be 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -375,4 +375,5 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { doTest(); } public void testConstructorMethodReferenceNullability() { doTest(); } + public void testCustomStreamImplementation() { doTest(); } } \ No newline at end of file