diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java index b569bf697bfb..9f32e37ac42b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java @@ -25,6 +25,7 @@ import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiUtil; import com.intellij.util.ObjectUtils; +import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,13 +42,18 @@ import java.util.function.Consumer; @SuppressWarnings("UnusedReturnValue") public class CFGBuilder { private final ControlFlowAnalyzer myAnalyzer; - private final Deque myBranches = new ArrayDeque<>(); + private final Deque myBranches = new ArrayDeque<>(); private final Map myMethodRefQualifiers = new HashMap<>(); CFGBuilder(ControlFlowAnalyzer analyzer) { myAnalyzer = analyzer; } + private CFGBuilder add(Instruction instruction) { + myAnalyzer.addInstruction(instruction); + return this; + } + /** * Generate instructions to push unknown DfaValue on stack. *

@@ -72,8 +78,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder pushNull() { - myAnalyzer.addInstruction(new PushInstruction(getFactory().getConstFactory().getNull(), null)); - return this; + return add(new PushInstruction(getFactory().getConstFactory().getNull(), null)); } /** @@ -102,8 +107,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder pushForWrite(DfaVariableValue variable) { - myAnalyzer.addInstruction(new PushInstruction(variable, null, true)); - return this; + return add(new PushInstruction(variable, null, true)); } /** @@ -117,8 +121,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder push(DfaValue value) { - myAnalyzer.addInstruction(new PushInstruction(value, null)); - return this; + return add(new PushInstruction(value, null)); } /** @@ -131,8 +134,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder pop() { - myAnalyzer.addInstruction(new PopInstruction()); - return this; + return add(new PopInstruction()); } /** @@ -145,8 +147,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder dup() { - myAnalyzer.addInstruction(new DupInstruction()); - return this; + return add(new DupInstruction()); } /** @@ -161,8 +162,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder splice(int count, int... replacement) { - myAnalyzer.addInstruction(new SpliceInstruction(count, replacement)); - return this; + return add(new SpliceInstruction(count, replacement)); } /** @@ -175,8 +175,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder swap() { - myAnalyzer.addInstruction(new SwapInstruction()); - return this; + return add(new SwapInstruction()); } /** @@ -189,8 +188,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder objectOf() { - myAnalyzer.addInstruction(new ObjectOfInstruction()); - return this; + return add(new ObjectOfInstruction()); } /** @@ -204,8 +202,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder isInstance(PsiMethodCallExpression anchor) { - myAnalyzer.addInstruction(new InstanceofInstruction(anchor)); - return this; + return add(new InstanceofInstruction(anchor)); } /** @@ -239,8 +236,7 @@ public class CFGBuilder { * @return this builder */ private CFGBuilder compare(IElementType relation) { - myAnalyzer.addInstruction(new BinopInstruction(relation, null, PsiType.BOOLEAN)); - return this; + return add(new BinopInstruction(relation, null, PsiType.BOOLEAN)); } /** @@ -250,7 +246,7 @@ public class CFGBuilder { *

* Stack after: ... *

- * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside. + * The conditional block must end with {@link #end()} and may contain one {@link #elseBranch()} inside. * Nested conditional blocks are acceptable. * * @param value a value condition must have to visit conditional block @@ -258,9 +254,8 @@ public class CFGBuilder { */ public CFGBuilder ifConditionIs(boolean value) { ConditionalGotoInstruction gotoInstruction = new ConditionalGotoInstruction(null, value, null); - myBranches.add(gotoInstruction); - myAnalyzer.addInstruction(gotoInstruction); - return this; + myBranches.add(() -> gotoInstruction.setOffset(myAnalyzer.getInstructionCount())); + return add(gotoInstruction); } /** @@ -271,7 +266,7 @@ public class CFGBuilder { *

* Stack after: ... *

- * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside. + * The conditional block must end with {@link #end()} and may contain one {@link #elseBranch()} inside. * Nested conditional blocks are acceptable. * * @param relation a relation to use to compare two stack values. Conditional block will be executed if "val1 relation val2" is true. @@ -288,7 +283,7 @@ public class CFGBuilder { *

* Stack after: ... *

- * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside. + * The conditional block must end with {@link #end()} and may contain one {@link #elseBranch()} inside. * Nested conditional blocks are acceptable. * * @return this builder @@ -304,7 +299,7 @@ public class CFGBuilder { *

* Stack after: ... *

- * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside. + * The conditional block must end with {@link #end()} and may contain one {@link #elseBranch()} inside. * Nested conditional blocks are acceptable. * * @return this builder @@ -314,13 +309,12 @@ public class CFGBuilder { } /** - * Generate instructions to finish a conditional block started with {@link #ifCondition(IElementType)}, {@link #ifConditionIs(boolean)}, - * {@link #ifNull()} or {@link #ifNotNull()}. Stack is unchanged. + * Generate instructions to finish a conditional block or a loop. Stack is unchanged. * * @return this builder */ - public CFGBuilder endIf() { - myBranches.removeLast().setOffset(myAnalyzer.getInstructionCount()); + public CFGBuilder end() { + myBranches.removeLast().run(); return this; } @@ -333,33 +327,21 @@ public class CFGBuilder { */ public CFGBuilder elseBranch() { GotoInstruction gotoInstruction = new GotoInstruction(null); - myAnalyzer.addInstruction(gotoInstruction); - endIf(); - myBranches.add(gotoInstruction); + add(gotoInstruction).end(); + myBranches.add(() -> gotoInstruction.setOffset(myAnalyzer.getInstructionCount())); return this; } /** - * Generate instructions to start a loop. Stack is unchanged. Loop must be terminated via {@link #endWhileUnknown()}. + * Generate instructions to start a loop. Stack is unchanged. Loop must be terminated via {@link #end()}. * Nested loops are acceptable. * * @return this builder */ - public CFGBuilder doWhile() { + public CFGBuilder doWhileUnknown() { ConditionalGotoInstruction jump = new ConditionalGotoInstruction(null, false, null); jump.setOffset(myAnalyzer.getInstructionCount()); - myBranches.add(jump); - return this; - } - - /** - * Generate instructions to end a loop started via {@link #doWhile()} by unknown condition. Stack is unchanged. - * - * @return this builder - */ - public CFGBuilder endWhileUnknown() { - pushUnknown(); - myAnalyzer.addInstruction((ConditionalGotoInstruction)myBranches.removeLast()); + myBranches.add(() -> pushUnknown().add(jump)); return this; } @@ -404,8 +386,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder flushFields() { - myAnalyzer.addInstruction(new FlushFieldsInstruction()); - return this; + return add(new FlushFieldsInstruction()); } /** @@ -417,8 +398,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder checkNotNull(T expression, NullabilityProblemKind kind) { - myAnalyzer.addInstruction(new CheckNotNullInstruction(kind.problem(expression))); - return this; + return add(new CheckNotNullInstruction(kind.problem(expression))); } /** @@ -432,8 +412,7 @@ public class CFGBuilder { * @return this builder */ public CFGBuilder assign() { - myAnalyzer.addInstruction(new AssignInstruction(null, null)); - return this; + return add(new AssignInstruction(null, null)); } /** @@ -596,7 +575,7 @@ public class CFGBuilder { if (qualifier == null) return false; PsiType type = qualifier.getOperand().getType(); push(getFactory().createTypeValue(type, Nullness.NOT_NULL)); - myAnalyzer.addInstruction(new InstanceofInstruction(methodRef, null, type)); + add(new InstanceofInstruction(methodRef, null, type)); return true; } @@ -649,6 +628,45 @@ public class CFGBuilder { return this; } + public CFGBuilder loopOver(PsiExpression[] expressions, DfaVariableValue targetVariable) { + DfaValueFactory factory = getFactory(); + if (expressions.length > ControlFlowAnalyzer.MAX_UNROLL_SIZE) { + DfaValue loopElement = null; + for (PsiExpression expression : expressions) { + pushExpression(expression); + DfaValue expressionValue = factory.createValue(expression); + if (expressionValue == null) { + expressionValue = factory.createTypeValue(expression.getType(), NullnessUtil.getExpressionNullness(expression)); + } + loopElement = loopElement == null ? expressionValue : loopElement.union(expressionValue); + pop(); + } + ConditionalGotoInstruction condGoto = new ConditionalGotoInstruction(null, false, null); + condGoto.setOffset(myAnalyzer.getInstructionCount()); + myBranches.add(() -> pushUnknown().add(condGoto)); + pushForWrite(targetVariable).push(loopElement).assign(); + } else { + push(factory.getConstFactory().getSentinel()); + for (PsiExpression expression : expressions) { + pushExpression(expression); + } + // Revert order + add(new SpliceInstruction(expressions.length, IntStreamEx.ofIndices(expressions).toArray())); + GotoInstruction gotoInstruction = new GotoInstruction(null); + gotoInstruction.setOffset(myAnalyzer.getInstructionCount()); + dup().push(factory.getConstFactory().getSentinel()).compare(JavaTokenType.EQEQ); + ConditionalGotoInstruction condGoto = new ConditionalGotoInstruction(null, false, null); + add(condGoto); + assignTo(targetVariable); + myBranches.add(() -> { + add(gotoInstruction); + condGoto.setOffset(myAnalyzer.getInstructionCount()); + pop(); + }); + } + return this; + } + /** * Create a synthetic variable (not declared in the original code) to be used within this control flow. * diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index af5f6d9c683f..a8cf28d2fb77 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -51,7 +51,7 @@ import static com.intellij.psi.CommonClassNames.*; public class ControlFlowAnalyzer extends JavaElementVisitor { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer"); public static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName(); - private static final int MAX_UNROLL_SIZE = 3; + static final int MAX_UNROLL_SIZE = 3; private final PsiElement myCodeFragment; private final boolean myIgnoreAssertions; private final boolean myInlining; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index cbb6a72eea9c..231fa632e906 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -892,10 +892,13 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return true; } - final boolean containsCalls = dfaLeft instanceof DfaVariableValue && ((DfaVariableValue)dfaLeft).containsCalls(); - if (dfaLeft == dfaRight) { - return containsCalls || !isNegated; + return !isNegated || (dfaLeft instanceof DfaVariableValue && ((DfaVariableValue)dfaLeft).containsCalls()); + } + + DfaConstValue sentinel = getFactory().getConstFactory().getSentinel(); + if (dfaLeft == sentinel || dfaRight == sentinel) { + return isNegated; } if (isNull(dfaLeft) && isNotNull(dfaRight) || isNull(dfaRight) && isNotNull(dfaLeft)) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/ComparatorModel.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/ComparatorModel.java index 792b74f39894..bc8e2314d5e4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/ComparatorModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/ComparatorModel.java @@ -103,7 +103,7 @@ abstract class ComparatorModel { @Override void invoke(CFGBuilder builder) { - builder.dup().ifNotNull().chain(myDownstream::invoke).elseBranch().pop().endIf(); + builder.dup().ifNotNull().chain(myDownstream::invoke).elseBranch().pop().end(); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/MapUpdateInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/MapUpdateInliner.java index cabbbd910434..ae28a78f5461 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/MapUpdateInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/MapUpdateInliner.java @@ -74,7 +74,7 @@ public class MapUpdateInliner implements CallInliner { .push(builder.getFactory().createTypeValue(type, Nullness.NOT_NULL)) .swap() .invokeFunction(2, function) - .endIf() + .end() .flushFields(); return true; } @@ -92,7 +92,7 @@ public class MapUpdateInliner implements CallInliner { .elseBranch() .pop() .push(builder.getFactory().createTypeValue(type, Nullness.NOT_NULL)) - .endIf(); + .end(); } private static void inlineComputeIfPresent(@NotNull CFGBuilder builder, @@ -110,7 +110,7 @@ public class MapUpdateInliner implements CallInliner { .elseBranch() .pop() .pushNull() - .endIf(); + .end(); } private static void inlineCompute(@NotNull CFGBuilder builder, diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java index 7063ebd9723b..711599bd2ab5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java @@ -80,7 +80,7 @@ public class OptionalChainInliner implements CallInliner { .splice(2, 0, 1, 1) // stack: .. elseValue, optValue, optValue .ifNotNull() .swap() // stack: .. optValue, elseValue - .endIf() + .end() .pop(); }) .register(OPTIONAL_OR_NULL, (builder, call) -> { @@ -94,7 +94,7 @@ public class OptionalChainInliner implements CallInliner { .ifNull() .pop() .invokeFunction(0, fn) - .endIf(); + .end(); }) .register(OPTIONAL_IF_PRESENT, (builder, call) -> { PsiExpression fn = call.getArgumentList().getExpressions()[0]; @@ -106,7 +106,7 @@ public class OptionalChainInliner implements CallInliner { .elseBranch() .pop() .pushUnknown() - .endIf(); + .end(); }); private static final CallMapper> INTERMEDIATE_MAPPER = @@ -122,19 +122,19 @@ public class OptionalChainInliner implements CallInliner { .ifConditionIs(false) .pop() .pushNull() - .endIf() - .endIf()) + .end() + .end()) .register(OPTIONAL_FLAT_MAP, (builder, function) -> builder .dup() .ifNotNull() .chain(b -> invokeAndUnwrapOptional(b, 1, function)) - .endIf()) + .end()) .register(OPTIONAL_OR, (builder, function) -> builder .dup() .ifNull() .pop() .chain(b -> invokeAndUnwrapOptional(b, 0, function)) - .endIf()) + .end()) .register(GUAVA_TO_JAVA, (builder, stub) -> {/* no op */}); @Override @@ -154,7 +154,7 @@ public class OptionalChainInliner implements CallInliner { .push(factFactory.getFactValue(DfaFactType.OPTIONAL_PRESENCE, true)) .elseBranch() .push(factFactory.getFactValue(DfaFactType.OPTIONAL_PRESENCE, false)) - .endIf(); + .end(); return true; } if (OPTIONAL_EMPTY.test(call)) { @@ -200,7 +200,7 @@ public class OptionalChainInliner implements CallInliner { .push(builder.getFactory().createTypeValue(optionalElementType, Nullness.NOT_NULL)) .elseBranch() .pushNull() - .endIf() + .end() .assignTo(builder.createTempVariable(optionalElementType)); return true; } @@ -251,7 +251,7 @@ public class OptionalChainInliner implements CallInliner { .dup() .ifNotNull() .invokeFunction(1, function, resultNullness) - .endIf(); + .end(); } private static void inlineOf(CFGBuilder builder, PsiType optionalElementType, PsiMethodCallExpression qualifierCall) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java index 4ae1e2780032..a967ceedc650 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java @@ -310,12 +310,12 @@ public class StreamChainInliner implements CallInliner { @Override void iteration(CFGBuilder builder) { builder.invokeFunction(1, myFunction) - .ifConditionIs(!"allMatch".equals(myCall.getMethodExpression().getReferenceName())) - .pushForWrite(myResult) - .push(builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName()))) - .assign() - .pop() - .endIf(); + .ifConditionIs(!"allMatch".equals(myCall.getMethodExpression().getReferenceName())) + .pushForWrite(myResult) + .push(builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName()))) + .assign() + .pop() + .end(); } } @@ -333,7 +333,7 @@ public class StreamChainInliner implements CallInliner { .chain(myNext::iteration) .elseBranch() .pop() - .endIf(); + .end(); } } @@ -415,13 +415,13 @@ public class StreamChainInliner implements CallInliner { } else { PsiType outType = StreamApiUtil.getStreamElementType(myCall.getType()); builder.pop() - .pushUnknown() - .ifConditionIs(true) - .doWhile() - .push(builder.getFactory().createTypeValue(outType, Nullness.UNKNOWN)) - .chain(myNext::iteration) - .endWhileUnknown() - .endIf(); + .pushUnknown() + .ifConditionIs(true) + .doWhileUnknown() + .push(builder.getFactory().createTypeValue(outType, Nullness.UNKNOWN)) + .chain(myNext::iteration) + .end() + .end(); } } } @@ -467,7 +467,7 @@ public class StreamChainInliner implements CallInliner { .chain(myNext::iteration) .elseBranch() .pop() - .endIf(); + .end(); } } @@ -595,7 +595,7 @@ public class StreamChainInliner implements CallInliner { .ifConditionIs(true) .push(builder.getFactory().getFactValue(DfaFactType.CAN_BE_NULL, false)) .invokeFunction(2, myMerger) - .endIf(); + .end(); } // Actual addition of Map element is unnecessary for current analysis builder.pop(); @@ -620,10 +620,10 @@ public class StreamChainInliner implements CallInliner { PsiExpression originalQualifier = firstStep.myCall.getMethodExpression().getQualifierExpression(); if (originalQualifier == null) return false; builder.pushUnknown() - .ifConditionIs(true) - .chain(b -> buildStreamCFG(b, firstStep, originalQualifier)) - .endIf() - .push(builder.getFactory().createTypeValue(call.getType(), Nullness.NOT_NULL)); + .ifConditionIs(true) + .chain(b -> buildStreamCFG(b, firstStep, originalQualifier)) + .end() + .push(builder.getFactory().createTypeValue(call.getType(), Nullness.NOT_NULL)); return true; } @@ -646,12 +646,11 @@ public class StreamChainInliner implements CallInliner { builder .evaluateFunction(fn) .chain(firstStep::before) - .doWhile() + .doWhileUnknown() .pushForWrite(builder.createTempVariable(inType)) .invokeFunction(0, fn) .assign() - .chain(firstStep::iteration) - .endWhileUnknown(); + .chain(firstStep::iteration).end(); return; } boolean empty = STREAM_EMPTY.test(sourceCall); @@ -669,10 +668,9 @@ public class StreamChainInliner implements CallInliner { } } else { builder - .pushExpression(originalQualifier) - .pop() .chain(firstStep::before) - .chain(b -> makeMainLoop(b, firstStep, inType)); + .loopOver(args, builder.createTempVariable(inType)) + .chain(firstStep::iteration).end(); return; } } @@ -693,14 +691,14 @@ public class StreamChainInliner implements CallInliner { DfaValue qualifierValue = builder.getFactory().createValue(qualifierExpression); if (qualifierValue != null) { builder.pushExpression(qualifierExpression) - .chain(firstStep::before) - .checkNotNull(qualifierExpression, NullabilityProblemKind.passingNullableToNotNullParameter) - .pop() - .push(SpecialField.ARRAY_LENGTH.createValue(builder.getFactory(), qualifierValue)) - .push(builder.getFactory().getInt(0)) - .ifCondition(JavaTokenType.GT) - .chain(b -> makeMainLoop(b, firstStep, inType)) - .endIf(); + .chain(firstStep::before) + .checkNotNull(qualifierExpression, NullabilityProblemKind.passingNullableToNotNullParameter) + .pop() + .push(SpecialField.ARRAY_LENGTH.createValue(builder.getFactory(), qualifierValue)) + .push(builder.getFactory().getInt(0)) + .ifCondition(JavaTokenType.GT) + .chain(b -> makeMainLoop(b, firstStep, inType)) + .end(); return; } } @@ -709,14 +707,14 @@ public class StreamChainInliner implements CallInliner { DfaValue qualifierValue = builder.getFactory().createValue(qualifierExpression); if (qualifierValue != null) { builder.pushExpression(qualifierExpression) - .chain(firstStep::before) - .checkNotNull(sourceCall, NullabilityProblemKind.callNPE) - .pop() - .push(SpecialField.COLLECTION_SIZE.createValue(builder.getFactory(), qualifierValue)) - .push(builder.getFactory().getInt(0)) - .ifCondition(JavaTokenType.GT) - .chain(b -> makeMainLoop(b, firstStep, inType)) - .endIf(); + .chain(firstStep::before) + .checkNotNull(sourceCall, NullabilityProblemKind.callNPE) + .pop() + .push(SpecialField.COLLECTION_SIZE.createValue(builder.getFactory(), qualifierValue)) + .push(builder.getFactory().getInt(0)) + .ifCondition(JavaTokenType.GT) + .chain(b -> makeMainLoop(b, firstStep, inType)) + .end(); return; } } @@ -728,16 +726,15 @@ public class StreamChainInliner implements CallInliner { .pushUnknown() .ifConditionIs(true) .chain(b -> makeMainLoop(b, firstStep, inType)) - .endIf(); + .end(); } private static void makeMainLoop(CFGBuilder builder, Step firstStep, PsiType inType) { - builder.doWhile() - .pushForWrite(builder.createTempVariable(inType)) - .push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType))) - .assign() - .chain(firstStep::iteration) - .endWhileUnknown(); + builder.doWhileUnknown() + .pushForWrite(builder.createTempVariable(inType)) + .push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType))) + .assign() + .chain(firstStep::iteration).end(); } static Step buildChain(PsiMethodCallExpression qualifierCall, Step terminalStep) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaConstValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaConstValue.java index ec79fc9224a5..67f34ab2e7ae 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaConstValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaConstValue.java @@ -21,6 +21,7 @@ import com.intellij.psi.*; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.psiutils.ExpressionUtils; import org.jetbrains.annotations.NonNls; @@ -31,11 +32,13 @@ import java.util.Map; public class DfaConstValue extends DfaValue { private static final Throwable ourThrowable = new Throwable(); + private static final Object SENTINEL = ObjectUtils.sentinel("SENTINEL"); public static class Factory { private final DfaConstValue dfaNull; private final DfaConstValue dfaFalse; private final DfaConstValue dfaTrue; private final DfaConstValue dfaFail; + private final DfaConstValue dfaSentinel; private final DfaValueFactory myFactory; private final Map myValues = ContainerUtil.newHashMap(); @@ -45,6 +48,7 @@ public class DfaConstValue extends DfaValue { dfaFalse = new DfaConstValue(Boolean.FALSE, PsiType.BOOLEAN, factory, null); dfaTrue = new DfaConstValue(Boolean.TRUE, PsiType.BOOLEAN, factory, null); dfaFail = new DfaConstValue(ourThrowable, PsiType.VOID, factory, null); + dfaSentinel = new DfaConstValue(SENTINEL, PsiType.VOID, factory, null); } @Nullable @@ -139,6 +143,14 @@ public class DfaConstValue extends DfaValue { public DfaConstValue getNull() { return dfaNull; } + + /** + * Sentinel value is special value used internally by dataflow. It cannot be stored to any variable, and equals to itself only + * @return sentinel value + */ + public DfaConstValue getSentinel() { + return dfaSentinel; + } } private final Object myValue; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java index b42eb55dea90..288441edbf67 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java @@ -42,6 +42,7 @@ public abstract class DfaValue { */ public DfaValue union(DfaValue other) { if(this == other) return this; + if(this == DfaUnknownValue.getInstance() || other == DfaUnknownValue.getInstance()) return DfaUnknownValue.getInstance(); return myFactory.getFactFactory().createValue(DfaFactMap.fromDfaValue(this).union(DfaFactMap.fromDfaValue(other))); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StreamKnownSource.java b/java/java-tests/testData/inspection/dataFlow/fixture/StreamKnownSource.java index 8c1234f4b6e6..61f823b85af5 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/StreamKnownSource.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StreamKnownSource.java @@ -125,4 +125,22 @@ public class StreamKnownSource { System.out.println("Always"); } } + + void testLongVararg() { + IntStream.of(1, 2, 3, 4, 5).anyMatch(x -> x > 6); + Stream.of("foo", "bar", "baz", "qux").filter(Objects::nonNull).forEach(System.out::println); + } + + void testShortVararg(String a, String b) { + if(Stream.of(a, b).allMatch(Objects::isNull)) { + if(a != null) { + System.out.println("Impossible"); + } + if(b != null) { + System.out.println("Impossible"); + } + } + IntStream.of(1, 2).anyMatch(x -> x > 6); + Stream.of("foo", "bar").filter(Objects::nonNull).forEach(System.out::println); + } } diff --git a/java/java-tests/testData/inspection/optionalGet/OptionalGet.java b/java/java-tests/testData/inspection/optionalGet/OptionalGet.java index 4d0d14d1236f..e9e50e4cc5b6 100644 --- a/java/java-tests/testData/inspection/optionalGet/OptionalGet.java +++ b/java/java-tests/testData/inspection/optionalGet/OptionalGet.java @@ -350,6 +350,14 @@ class OptionalGet { // Non-empty stream: get() is fine return Stream.of("foo", "bar", "baz").map(String::toUpperCase).max(Comparator.naturalOrder()).get(); } + + void testStreamOfUnrolling(Optional optionalOne, Optional optionalTwo, Optional optionalThree) { + if (Stream.of(optionalOne, optionalTwo).allMatch(Optional::isPresent)) { + System.out.println(optionalOne.get()); + System.out.println(optionalTwo.get()); + System.out.println(optionalThree.get()); + } + } } class CtorTest {