diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaInstructionState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaInstructionState.java index 0c0a16b1be10..4ea1efe0350f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaInstructionState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaInstructionState.java @@ -115,7 +115,14 @@ class StateQueue { } private static List squash(List states) { - return states.stream().filter(left -> states.stream().noneMatch(right -> right != left && right.isSuperStateOf(left))).collect(Collectors.toList()); + // Sometimes a.isSuperStateOf(b) && b.isSuperStateOf(a) does not imply a.equals(b) which is unpleasant hole in the abstraction + // and requires special care here: we leave only one of such states in this case + for (int i = 0; i < states.size(); i++) { + DfaMemoryStateImpl left = states.get(i); + states.subList(i + 1, states.size()).removeIf(right -> left.isSuperStateOf(right) && right.isSuperStateOf(left)); + } + return states.stream().filter(left -> states.stream().noneMatch(right -> right != left && right.isSuperStateOf(left))) + .collect(Collectors.toList()); } static List mergeGroup(List group) { 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 8b83e85e6ee7..25f5ed55f38d 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 @@ -41,6 +41,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Predicate; +import java.util.stream.Collectors; public class DfaMemoryStateImpl implements DfaMemoryState { @@ -1170,7 +1171,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } } for (DfaVariableValue value : vars) { - if (!value.isFlushableByCalls()) continue; + if (value.isNegated() || !value.isFlushableByCalls()) continue; DfaVariableValue qualifier = value.getQualifier(); if (qualifier != null) { if (getValueFact(qualifier, DfaFactType.MUTABILITY) == Mutability.UNMODIFIABLE || @@ -1184,22 +1185,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { private boolean shouldMarkFlushed(@NotNull DfaVariableValue value) { if (value.getInherentNullability() != Nullability.NULLABLE) return false; - if (getVariableState(value).getFact(DfaFactType.NULLABILITY) == DfaNullability.FLUSHED) return true; - int eqClassIndex = getEqClassIndex(value); - if (eqClassIndex < 0) return false; - - EqClass eqClass = myEqClasses.get(eqClassIndex); - if (eqClass == null) return false; - DfaConstValue nullConst = myFactory.getConstFactory().getNull(); - if (eqClass.findConstant(true) == nullConst) return true; - - for (DistinctPairSet.DistinctPair pair : getDistinctClassPairs()) { - EqClass otherClass = pair.getOtherClass(eqClassIndex); - if (otherClass != null && otherClass.findConstant(true) == nullConst) { - return true; - } - } - return false; + return getVariableState(value).getFact(DfaFactType.NULLABILITY) == DfaNullability.FLUSHED || isNull(value) || isNotNull(value); } @NotNull @@ -1231,8 +1217,10 @@ public class DfaMemoryStateImpl implements DfaMemoryState { DfaVariableValue qualifier = variable.getQualifier(); if (psiVariable instanceof PsiField && qualifier != null) { // Flush method results on field write - qualifier.getDependentVariables().stream().filter(DfaVariableValue::containsCalls) - .forEach(val -> doFlush(val, shouldMarkFlushed(val))); + List toFlush = + qualifier.getDependentVariables().stream().filter(DfaVariableValue::containsCalls) + .filter(var -> !var.isNegated()).collect(Collectors.toList()); + toFlush.forEach(val -> doFlush(val, shouldMarkFlushed(val))); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index 44477b146be4..3fbf46eda965 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -89,7 +89,7 @@ public class DfaValueFactory { public DfaValue createExactTypeValue(@Nullable PsiType type) { if (type == null) return DfaUnknownValue.getInstance(); DfaFactMap facts = DfaFactMap.EMPTY.with(DfaFactType.TYPE_CONSTRAINT, TypeConstraint.exact(createDfaType(type))) - .with(DfaFactType.CAN_BE_NULL, false); + .with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL); return getFactFactory().createValue(facts); } diff --git a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java index fdc1ceb74c80..2d59cc4ab23b 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java @@ -1204,7 +1204,9 @@ public class TypeConversionUtil { PsiType[] types = Stream.of(extendsList).map(PsiClassType::resolve).filter(Objects::nonNull) .map(psiClass -> getClassErasedType(psiClass, visited, beforeSubstitutor, typeParameter, true)) .toArray(PsiType[]::new); - return PsiIntersectionType.createIntersection(true, types); + return types.length == 0 ? + PsiType.getJavaLangObject(typeParameter.getManager(), typeParameter.getResolveScope()) : + PsiIntersectionType.createIntersection(true, types); } return getClassErasedType(extendsList[0].resolve(), visited, beforeSubstitutor, typeParameter, false); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/BooleanMergeInLoop.java b/java/java-tests/testData/inspection/dataFlow/fixture/BooleanMergeInLoop.java new file mode 100644 index 000000000000..23bd0ee300cb --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/BooleanMergeInLoop.java @@ -0,0 +1,39 @@ +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; + +class Test { + private static void test(@NotNull IfStmt ifStatement) { + IfStmt currentIf = ifStatement; + boolean flag = false; + while (true) { + if (currentIf.getElseBranch() != null) return; + if(currentIf.getCondition() != null && checkCondition(currentIf.getCondition())) { + flag = true; + } + Stmt sibling = currentIf; + do { + sibling = getNext(sibling); + } + while (sibling != null); + + IfStmt enclosingIf = getEnclosing(currentIf); + if (enclosingIf == null) break; + currentIf = enclosingIf; + } + if(flag) { + System.out.println("not always"); + } + } + + interface Stmt {} + + interface IfStmt extends Stmt { + @Nullable Object getElseBranch(); + @Nullable Object getCondition(); + } + + static native @Nullable Stmt getNext(Stmt cur); + static native IfStmt getEnclosing(Stmt cur); + static native boolean checkCondition(Object condition); + +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NullFlushed.java b/java/java-tests/testData/inspection/dataFlow/fixture/NullFlushed.java new file mode 100644 index 000000000000..33115624ca2f --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NullFlushed.java @@ -0,0 +1,43 @@ +import org.jetbrains.annotations.Nullable; + +class GetUnknownTest { + private void test(Message message, boolean isApplicable) { + if (message == null && field == null) { + return; + } + + if (message != null) { + field = message.getHeader(); + doSomething(); + } + + if (isApplicable) { + // Dubious warning: message.getHeader() is not annotated, but assigned to nullable field; should we consider the result as nullable? + System.out.println(field.hashCode()); + } + } + + interface Message { + Object getHeader(); + } + + @Nullable Object field; + + native void doSomething(); +} +class BooleanTest { + native private Object getSomething(); + private @Nullable Boolean field; + + private boolean test(Object x) { + if (field == null) { + field = x != null; + + if (getSomething() != null) { + field = true; + } + } + + return field.booleanValue(); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index 1b6a62a612fb..77124d9e23fc 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -622,4 +622,6 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { public void testPolyadicEquality() { doTest(); } public void testBoxUnboxArrayElement() { doTest(); } public void testExactInstanceOf() { doTest(); } + public void testNullFlushed() { doTest(); } + public void testBooleanMergeInLoop() { doTest(); } }