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 049dbba485df..e6b2d7c3d6da 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 @@ -1435,8 +1435,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } } - private void throwException(PsiType ref, @Nullable PsiElement anchor) { - throwException(new ExceptionTransfer(myFactory.createDfaType(ref)), anchor); + private void throwException(@Nullable PsiType ref, @Nullable PsiElement anchor) { + if (ref != null) { + throwException(new ExceptionTransfer(myFactory.createDfaType(ref)), anchor); + } } private void throwException(ExceptionTransfer kind, @Nullable PsiElement anchor) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java index ddcebef17e34..a109de53955d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java @@ -29,15 +29,38 @@ public interface DfaMemoryState { @NotNull DfaMemoryState createClosureState(); - DfaValue pop(); - DfaValue peek(); + /** + * Pops single value from the top of the stack and returns it + * @return popped value + * @throws com.intellij.codeInspection.dataFlow.instructions.EmptyStackInstruction if stack is empty + */ + @NotNull DfaValue pop(); + + /** + * Reads a value from the top of the stack without popping it + * @return top of stack value + * @throws com.intellij.codeInspection.dataFlow.instructions.EmptyStackInstruction if stack is empty + */ + @NotNull DfaValue peek(); + + /** + * Pushes given value to the stack + * @param value to push + */ void push(@NotNull DfaValue value); void emptyStack(); void setVarValue(DfaVariableValue var, DfaValue value); - boolean applyInstanceofOrNull(DfaValue operand, DfaPsiType type); + /** + * Ensures that top-of-stack value is either null or belongs to the supplied type + * + * @param type the type to cast to + * @return true if cast is successful; false if top-of-stack value type is incompatible with supplied type + * @throws com.intellij.codeInspection.dataFlow.instructions.EmptyStackInstruction if stack is empty + */ + boolean castTopOfStack(DfaPsiType type); boolean applyCondition(DfaValue dfaCond); 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 244cb4e69c71..551455be4df7 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 @@ -221,12 +221,14 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return result.toString(); } + @NotNull @Override public DfaValue pop() { myCachedHash = null; return myStack.pop(); } + @NotNull @Override public DfaValue peek() { return myStack.peek(); @@ -275,7 +277,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (getVariableState(var).isNotNull()) { DfaConstValue dfaNull = myFactory.getConstFactory().getNull(); - applyCondition(myFactory.getRelationFactory().createRelation(var, RelationType.NE, dfaNull)); + applyRelation(var, dfaNull, true); } } @@ -685,20 +687,31 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } @Override - public boolean applyInstanceofOrNull(DfaValue operand, DfaPsiType type) { - DfaValue left = unwrap(operand); + public boolean castTopOfStack(DfaPsiType type) { + DfaValue value = unwrap(peek()); - if (!(left instanceof DfaVariableValue)) return true; + DfaFactMap facts = null; + if (value instanceof DfaVariableValue) { + DfaVariableValue dfaVar = (DfaVariableValue)value; - DfaVariableValue dfaVar = (DfaVariableValue)left; - - if (isUnknownState(dfaVar) || isNull(dfaVar)) return true; - DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue(type); - if (newState != null) { - setVariableState(dfaVar, newState); - return true; + if (isNull(dfaVar)) return true; + if (isUnknownState(dfaVar)) { + facts = getVariableState(dfaVar).myFactMap; + } else { + DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue(type); + if (newState == null) return false; + setVariableState(dfaVar, newState); + } + } else if (value instanceof DfaFactMapValue) { + facts = ((DfaFactMapValue)value).getFacts(); } - return false; + if (facts != null) { + DfaFactMap newFacts = TypeConstraint.withInstanceOf(facts, type); + if (newFacts == null) return false; + pop(); + push(myFactory.getFactFactory().createValue(newFacts)); + } + return true; } void setFact(DfaValue target, DfaFactType factType, T fact) { @@ -926,9 +939,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return false; } } - if (!isNegated && dfaRight instanceof DfaFactMapValue) { - return applyFacts(dfaLeft, ((DfaFactMapValue)dfaRight).getFacts()); - } return true; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java index 77a002dbdffa..51d3f8a51306 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java @@ -46,10 +46,7 @@ class DfaVariableState { @Nullable DfaVariableState withInstanceofValue(@NotNull DfaPsiType dfaType) { if (dfaType.getPsiType() instanceof PsiPrimitiveType) return this; - TypeConstraint typeConstraint = getTypeConstraint(); - TypeConstraint newTypeConstraint = typeConstraint.withInstanceofValue(dfaType); - if (newTypeConstraint == null) return null; - return withFact(DfaFactType.TYPE_CONSTRAINT, newTypeConstraint); + return withFacts(TypeConstraint.withInstanceOf(myFactMap, dfaType)); } @Nullable diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 489585dbec57..d860aa99f1d8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -311,15 +311,13 @@ public class StandardInstructionVisitor extends InstructionVisitor { @Override public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { final DfaValueFactory factory = runner.getFactory(); - DfaValue dfaExpr = factory.createValue(instruction.getCasted()); - if (dfaExpr != null) { - if (!memState.applyInstanceofOrNull(dfaExpr, factory.createDfaType(instruction.getCastTo()))) { - onInstructionProducesCCE(instruction); - } + if (!memState.castTopOfStack(factory.createDfaType(instruction.getCastTo()))) { + onInstructionProducesCCE(instruction); + return DfaInstructionState.EMPTY_ARRAY; } if (instruction.getCastTo() instanceof PsiPrimitiveType) { - memState.push(runner.getFactory().getBoxedFactory().createUnboxed(memState.pop())); + memState.push(factory.getBoxedFactory().createUnboxed(memState.pop())); } return nextInstruction(instruction, runner, memState); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java index ebba36d2bed4..77933e6aca2f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java @@ -21,7 +21,6 @@ import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiPrimitiveType; import com.intellij.psi.PsiType; import com.intellij.util.containers.ContainerUtil; -import com.siyeh.ig.psiutils.TypeUtils; import one.util.streamex.EntryStream; import one.util.streamex.MoreCollectors; import one.util.streamex.StreamEx; @@ -33,6 +32,8 @@ import java.util.*; /** * Immutable class representing a number of non-primitive type constraints applied to some value. * There are two types of constrains: value is instance of some type and value is not an instance of some type. + * Unlike usual Java semantics, the {@code null} value is considered to be instanceof any type (non-null instanceof can be expressed + * via additional restriction {@link DfaFactType#CAN_BE_NULL} {@code = false}). */ public final class TypeConstraint { /** @@ -101,9 +102,6 @@ public final class TypeConstraint { Set newInstanceof = ContainerUtil.newHashSet(myInstanceofValues); newInstanceof.removeAll(moreGeneric); newInstanceof.add(type); - if (newInstanceof.size() == 1 && TypeUtils.isJavaLangObject(newInstanceof.iterator().next().getPsiType())) { - newInstanceof = Collections.emptySet(); - } return create(newInstanceof, myNotInstanceofValues); } @@ -211,4 +209,12 @@ public final class TypeConstraint { .mapKeyValue((prefix, set) -> StreamEx.of(set).joining(",", prefix, "")) .joining(" "); } + + @Nullable + public static DfaFactMap withInstanceOf(@NotNull DfaFactMap map, @NotNull DfaPsiType type) { + TypeConstraint constraint = map.get(DfaFactType.TYPE_CONSTRAINT); + if (constraint == null) constraint = EMPTY; + constraint = constraint.withInstanceofValue(type); + return constraint == null ? null : map.with(DfaFactType.TYPE_CONSTRAINT, constraint); + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/TypeCastInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/TypeCastInstruction.java index 4a43ba7653b6..efd48577e09d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/TypeCastInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/TypeCastInstruction.java @@ -16,13 +16,13 @@ package com.intellij.codeInspection.dataFlow.instructions; +import com.intellij.codeInspection.dataFlow.DataFlowRunner; +import com.intellij.codeInspection.dataFlow.DfaInstructionState; +import com.intellij.codeInspection.dataFlow.DfaMemoryState; +import com.intellij.codeInspection.dataFlow.InstructionVisitor; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiType; import com.intellij.psi.PsiTypeCastExpression; -import com.intellij.codeInspection.dataFlow.DfaInstructionState; -import com.intellij.codeInspection.dataFlow.DataFlowRunner; -import com.intellij.codeInspection.dataFlow.DfaMemoryState; -import com.intellij.codeInspection.dataFlow.InstructionVisitor; public class TypeCastInstruction extends Instruction { private final PsiTypeCastExpression myCastExpression; @@ -51,4 +51,9 @@ public class TypeCastInstruction extends Instruction { public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) { return visitor.visitTypeCast(this, runner, stateBefore); } + + @Override + public String toString() { + return "CAST_TO "+myCastTo.getCanonicalText(); + } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/CastInstanceOf.java b/java/java-tests/testData/inspection/dataFlow/fixture/CastInstanceOf.java new file mode 100644 index 000000000000..561e0dd76e22 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/CastInstanceOf.java @@ -0,0 +1,24 @@ +import org.jetbrains.annotations.Nullable; +import java.util.*; + +class Test { + Object getXyz() { + return "foo"; + } + + void test() { + String x = (String)getXyz(); + if(x instanceof String) { + System.out.println("yes!"); + } + } + + void testUnknown() { + while(getXyz() != null) { + String x = (String)getXyz(); + if(x instanceof String) { + System.out.println("yes!"); + } + } + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/CovariantReturn.java b/java/java-tests/testData/inspection/dataFlow/fixture/CovariantReturn.java index 0d6dcaa045bb..f2c315be200f 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/CovariantReturn.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/CovariantReturn.java @@ -52,4 +52,19 @@ class CovariantReturn { boolean testTypeGeneric(BaseGeneric base) { return base instanceof DerivedGenericImpl && base.get(null) instanceof Integer; } + + interface Super { + Object get(); + } + + interface Sub extends Super { + String get(); + } + + void testCast(Super s) { + if(s instanceof Sub) { + Integer i = (Integer)s.get(); + System.out.println(i); + } + } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NullableField.java b/java/java-tests/testData/inspection/dataFlow/fixture/NullableField.java index aaaa074fa4fa..111913841672 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/NullableField.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NullableField.java @@ -1,8 +1,22 @@ import org.jetbrains.annotations.Nullable; +import java.util.*; class DDD { @Nullable String field; + + @Nullable List list; + + void test2() { + if(list == null) list = new ArrayList<>(); + unknown(); + System.out.println(list.size()); + } + + void unknown() { + System.out.println(); + } + int test() { return new DDD().field.hashCode(); } 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 61270d7f0835..cedf2b972de1 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -549,4 +549,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { public void testTryWithResourceExpressions() { doTest(); } public void testArrayStoreProblems() { doTest(); } + public void testCastInstanceOf() { doTest(); } }