DfaTypeValue -> DfaFactMapValue followup fixes; better cast processing

This commit is contained in:
Tagir Valeev
2017-10-20 15:45:54 +07:00
parent 2ee1bec87d
commit 4a9edfb720
11 changed files with 133 additions and 38 deletions
@@ -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) {
@@ -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);
@@ -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;
}
<T> void setFact(DfaValue target, DfaFactType<T> 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;
}
@@ -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
@@ -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);
@@ -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<DfaPsiType> 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);
}
}
@@ -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();
}
}
@@ -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(<warning descr="Condition 'x instanceof String' is redundant and can be replaced with '!= null'">x instanceof String</warning>) {
System.out.println("yes!");
}
}
void testUnknown() {
while(getXyz() != null) {
String x = (String)getXyz();
if(<warning descr="Condition 'x instanceof String' is redundant and can be replaced with '!= null'">x instanceof String</warning>) {
System.out.println("yes!");
}
}
}
}
@@ -52,4 +52,19 @@ class CovariantReturn {
boolean testTypeGeneric(BaseGeneric<?> base) {
return base instanceof DerivedGenericImpl && <warning descr="Condition 'base.get(null) instanceof Integer' is redundant and can be replaced with '!= null'">base.get(null) instanceof Integer</warning>;
}
interface Super {
Object get();
}
interface Sub extends Super {
String get();
}
void testCast(Super s) {
if(s instanceof Sub) {
Integer i = (<warning descr="Casting 's.get()' to 'Integer' may produce 'java.lang.ClassCastException'">Integer</warning>)s.get();
System.out.println(i);
}
}
}
@@ -1,8 +1,22 @@
import org.jetbrains.annotations.Nullable;
import java.util.*;
class DDD {
@Nullable
String field;
@Nullable List<String> 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.<warning descr="Method invocation 'hashCode' may produce 'java.lang.NullPointerException'">hashCode</warning>();
}
@@ -549,4 +549,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testTryWithResourceExpressions() { doTest(); }
public void testArrayStoreProblems() { doTest(); }
public void testCastInstanceOf() { doTest(); }
}