/* * Copyright 2000-2009 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Created by IntelliJ IDEA. * User: max * Date: Jan 28, 2002 * Time: 9:39:36 PM * To change template for new class use * Code Style | Class Templates options (Tools | IDE Options). */ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInspection.dataFlow.value.*; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.UnorderedPair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.JavaTokenType; import com.intellij.psi.PsiPrimitiveType; import com.intellij.psi.PsiType; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Stack; import gnu.trove.TLongArrayList; import gnu.trove.TLongHashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; public class DfaMemoryStateImpl implements DfaMemoryState { private final DfaValueFactory myFactory; private final List myEqClasses; private final Stack myStack; private final TLongHashSet myDistinctClasses; private final LinkedHashMap myVariableStates; private final Map myDefaultVariableStates; private final LinkedHashSet myUnknownVariables; private boolean myEphemeral; public DfaMemoryStateImpl(final DfaValueFactory factory) { myFactory = factory; myDefaultVariableStates = ContainerUtil.newTroveMap(); myEqClasses = ContainerUtil.newArrayList(); myUnknownVariables = ContainerUtil.newLinkedHashSet(); myVariableStates = ContainerUtil.newLinkedHashMap(); myDistinctClasses = new TLongHashSet(); myStack = new Stack(); } protected DfaMemoryStateImpl(DfaMemoryStateImpl toCopy) { myFactory = toCopy.myFactory; myEphemeral = toCopy.myEphemeral; myDefaultVariableStates = toCopy.myDefaultVariableStates; // shared between all states myStack = new Stack(toCopy.myStack); myDistinctClasses = new TLongHashSet(toCopy.myDistinctClasses.toArray()); myUnknownVariables = ContainerUtil.newLinkedHashSet(toCopy.myUnknownVariables); myEqClasses = ContainerUtil.newArrayList(toCopy.myEqClasses); myVariableStates = ContainerUtil.newLinkedHashMap(toCopy.myVariableStates); myCachedDistinctClassPairs = toCopy.myCachedDistinctClassPairs; myCachedNonTrivialEqClasses = toCopy.myCachedNonTrivialEqClasses; } public DfaValueFactory getFactory() { return myFactory; } @Override public DfaMemoryStateImpl createCopy() { return new DfaMemoryStateImpl(this); } public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof DfaMemoryStateImpl)) return false; DfaMemoryStateImpl that = (DfaMemoryStateImpl)obj; return equalsSuperficially(that) && equalsByUnknownVariables(that) && equalsByRelations(that) && equalsByVariableStates(that); } boolean equalsByUnknownVariables(DfaMemoryStateImpl that) { return myUnknownVariables.equals(that.myUnknownVariables); } Object getSuperficialKey() { return Pair.create(myEphemeral, myStack); } private boolean equalsSuperficially(DfaMemoryStateImpl other) { return myEphemeral == other.myEphemeral && myStack.equals(other.myStack); } boolean equalsByRelations(DfaMemoryStateImpl that) { return getNonTrivialEqClasses().equals(that.getNonTrivialEqClasses()) && getDistinctClassPairs().equals(that.getDistinctClassPairs()); } boolean equalsByVariableStates(DfaMemoryStateImpl that) { return myVariableStates.equals(that.myVariableStates); } private LinkedHashSet> myCachedDistinctClassPairs; LinkedHashSet> getDistinctClassPairs() { if (myCachedDistinctClassPairs != null) return myCachedDistinctClassPairs; LinkedHashSet> result = ContainerUtil.newLinkedHashSet(); for (long encodedPair : myDistinctClasses.toArray()) { result.add(new UnorderedPair(myEqClasses.get(low(encodedPair)), myEqClasses.get(high(encodedPair)))); } return myCachedDistinctClassPairs = result; } private LinkedHashSet myCachedNonTrivialEqClasses; LinkedHashSet getNonTrivialEqClasses() { if (myCachedNonTrivialEqClasses != null) return myCachedNonTrivialEqClasses; LinkedHashSet result = ContainerUtil.newLinkedHashSet(); for (EqClass eqClass : myEqClasses) { if (eqClass != null && eqClass.size() > 1) { result.add(eqClass); } } return myCachedNonTrivialEqClasses = result; } public int hashCode() { return getPartialHashCode(true, true); } int getPartialHashCode(boolean unknowns, boolean varStates) { int hash = (getNonTrivialEqClasses().hashCode() * 31 + getDistinctClassPairs().hashCode()) * 31 + myStack.hashCode(); if (varStates) { hash = hash * 31 + myVariableStates.hashCode(); } if (unknowns) { hash = hash * 31 + myUnknownVariables.hashCode(); } return hash; } @SuppressWarnings({"HardCodedStringLiteral"}) public String toString() { StringBuilder result = new StringBuilder(); result.append('<'); if (myEphemeral) { result.append("ephemeral, "); } for (EqClass set : getNonTrivialEqClasses()) { result.append(set); } if (!myDistinctClasses.isEmpty()) { result.append("\n distincts: "); List distincts = new ArrayList(); for (UnorderedPair pair : getDistinctClassPairs()) { distincts.add("{" + pair.first + ", " + pair.second + "}"); } Collections.sort(distincts); result.append(StringUtil.join(distincts, " ")); } if (!myStack.isEmpty()) { result.append("\n stack: ").append(StringUtil.join(myStack, ",")); } if (!myVariableStates.isEmpty()) { result.append("\n vars: "); for (Map.Entry entry : myVariableStates.entrySet()) { result.append("[").append(entry.getKey()).append("->").append(entry.getValue()).append("] "); } } if (!myUnknownVariables.isEmpty()) { result.append("\n unknowns: ").append(new HashSet(myUnknownVariables)); } result.append('>'); return result.toString(); } @Override public DfaValue pop() { return myStack.pop(); } @Override public DfaValue peek() { return myStack.peek(); } @Override public void push(@NotNull DfaValue value) { myStack.push(value); } @Override public void emptyStack() { myStack.clear(); } @Override public void setVarValue(DfaVariableValue var, DfaValue value) { if (var == value) return; flushVariable(var); if (value instanceof DfaUnknownValue) { setVariableState(var, getVariableState(var).withNullable(false)); return; } setVariableState(var, getVariableState(var).withValue(value)); if (value instanceof DfaTypeValue) { setVariableState(var, getVariableState(var).withNullable(((DfaTypeValue)value).isNullable())); DfaRelationValue dfaInstanceof = myFactory.getRelationFactory().createRelation(var, value, JavaTokenType.INSTANCEOF_KEYWORD, false); if (((DfaTypeValue)value).isNotNull()) { applyCondition(dfaInstanceof); } else { applyInstanceofOrNull(dfaInstanceof); } } else { DfaRelationValue dfaEqual = myFactory.getRelationFactory().createRelation(var, value, JavaTokenType.EQEQ, false); if (dfaEqual == null) return; applyCondition(dfaEqual); if (value instanceof DfaVariableValue) { setVariableState(var, getVariableState((DfaVariableValue)value)); } else if (value instanceof DfaBoxedValue) { setVariableState(var, getVariableState(var).withNullable(false)); applyCondition(compareToNull(var, true)); } } if (getVariableState(var).isNotNull()) { applyCondition(compareToNull(var, true)); } } @Nullable("for boxed values which can't be compared by ==") private Integer getOrCreateEqClassIndex(DfaValue dfaValue) { int i = getEqClassIndex(dfaValue); if (i != -1) return i; if (!canBeReused(dfaValue) && !(((DfaBoxedValue)dfaValue).getWrappedValue() instanceof DfaConstValue)) { return null; } EqClass aClass = new EqClass(myFactory); aClass.add(dfaValue.getID()); int freeIndex = myEqClasses.indexOf(null); if (freeIndex >= 0) { myEqClasses.set(freeIndex, aClass); return freeIndex; } myEqClasses.add(aClass); return myEqClasses.size() - 1; } @NotNull List getEquivalentValues(@NotNull DfaValue dfaValue) { int index = getEqClassIndex(dfaValue); EqClass set = index == -1 ? null : myEqClasses.get(index); if (set == null) { return Collections.emptyList(); } return set.getMemberValues(); } private boolean canBeNaN(@NotNull DfaValue dfaValue) { for (DfaValue eq : getEquivalentValues(dfaValue)) { if (eq instanceof DfaBoxedValue) { eq = ((DfaBoxedValue)eq).getWrappedValue(); } if (eq instanceof DfaConstValue && !isNaN(eq)) { return false; } } return dfaValue instanceof DfaVariableValue && TypeConversionUtil.isFloatOrDoubleType(((DfaVariableValue)dfaValue).getVariableType()); } private boolean isEffectivelyNaN(@NotNull DfaValue dfaValue) { for (DfaValue eqClass : getEquivalentValues(dfaValue)) { if (isNaN(eqClass)) return true; } return false; } private int getEqClassIndex(@NotNull DfaValue dfaValue) { for (int i = 0; i < myEqClasses.size(); i++) { EqClass aClass = myEqClasses.get(i); if (aClass != null && aClass.contains(dfaValue.getID())) { if (!canBeReused(dfaValue) && aClass.size() > 1) return -1; return i; } } return -1; } private boolean canBeReused(final DfaValue dfaValue) { if (dfaValue instanceof DfaBoxedValue) { DfaValue valueToWrap = ((DfaBoxedValue)dfaValue).getWrappedValue(); if (valueToWrap instanceof DfaConstValue) { return cacheable((DfaConstValue)valueToWrap); } if (valueToWrap instanceof DfaVariableValue) { if (PsiType.BOOLEAN.equals(((DfaVariableValue)valueToWrap).getVariableType())) return true; for (DfaValue value : getEquivalentValues(valueToWrap)) { if (value instanceof DfaConstValue && cacheable((DfaConstValue)value)) return true; } } return false; } return true; } private static boolean cacheable(DfaConstValue dfaConstValue) { Object value = dfaConstValue.getValue(); return box(value) == box(value); } @SuppressWarnings({"UnnecessaryBoxing"}) private static Object box(final Object value) { Object newBoxedValue; if (value instanceof Integer) { newBoxedValue = Integer.valueOf(((Integer)value).intValue()); } else if (value instanceof Byte) { newBoxedValue = Byte.valueOf(((Byte)value).byteValue()); } else if (value instanceof Short) { newBoxedValue = Short.valueOf(((Short)value).shortValue()); } else if (value instanceof Long) { newBoxedValue = Long.valueOf(((Long)value).longValue()); } else if (value instanceof Boolean) { newBoxedValue = Boolean.valueOf(((Boolean)value).booleanValue()); } else if (value instanceof Character) { newBoxedValue = Character.valueOf(((Character)value).charValue()); } else { return new Object(); } return newBoxedValue; } private boolean uniteClasses(int c1Index, int c2Index) { EqClass c1 = myEqClasses.get(c1Index); EqClass c2 = myEqClasses.get(c2Index); Set vars = ContainerUtil.newTroveSet(); Set negatedVars = ContainerUtil.newTroveSet(); int[] cs = new int[c1.size() + c2.size()]; c1.set(0, cs, 0, c1.size()); c2.set(0, cs, c1.size(), c2.size()); int nConst = 0; for (int c : cs) { DfaValue dfaValue = unwrap(myFactory.getValue(c)); if (dfaValue instanceof DfaConstValue) nConst++; if (dfaValue instanceof DfaVariableValue) { DfaVariableValue variableValue = (DfaVariableValue)dfaValue; if (variableValue.isNegated()) { negatedVars.add(variableValue.createNegated()); } else { vars.add(variableValue); } } if (nConst > 1) return false; } if (ContainerUtil.intersects(vars, negatedVars)) return false; TLongArrayList c2Pairs = new TLongArrayList(); long[] distincts = myDistinctClasses.toArray(); for (long distinct : distincts) { int pc1 = low(distinct); int pc2 = high(distinct); boolean addedToC1 = false; if (pc1 == c1Index || pc2 == c1Index) { addedToC1 = true; } if (pc1 == c2Index || pc2 == c2Index) { if (addedToC1) return false; c2Pairs.add(distinct); } } myEqClasses.set(c1Index, c1 = new EqClass(c1)); for (int i = 0; i < c2.size(); i++) { int c = c2.get(i); c1.add(c); } for (int i = 0; i < c2Pairs.size(); i++) { long c = c2Pairs.get(i); myDistinctClasses.remove(c); myDistinctClasses.add(createPair(c1Index, low(c) == c2Index ? high(c) : low(c))); } myEqClasses.set(c2Index, null); return true; } private static int low(long l) { return (int)l; } private static int high(long l) { return (int)((l & 0xFFFFFFFF00000000L) >> 32); } private static long createPair(int i1, int i2) { if (i1 < i2) { long l = i1; l <<= 32; l += i2; return l; } else { long l = i2; l <<= 32; l += i1; return l; } } private void makeClassesDistinct(int c1Index, int c2Index) { myDistinctClasses.add(createPair(c1Index, c2Index)); } @Override public boolean isNull(DfaValue dfaValue) { if (dfaValue instanceof DfaTypeValue && ((DfaTypeValue)dfaValue).isNotNull()) return false; if (dfaValue instanceof DfaConstValue) return ((DfaConstValue)dfaValue).getValue() == null; if (dfaValue instanceof DfaVariableValue) { int c1Index = getEqClassIndex(dfaValue); return c1Index >= 0 && c1Index == getEqClassIndex(myFactory.getConstFactory().getNull()); } return false; } @Override public boolean isNotNull(DfaValue dfaVar) { if (dfaVar instanceof DfaVariableValue && getVariableState((DfaVariableValue)dfaVar).isNotNull()) { return true; } if (dfaVar instanceof DfaConstValue && ((DfaConstValue)dfaVar).getValue() != null) { return true; } if (dfaVar instanceof DfaTypeValue && ((DfaTypeValue)dfaVar).isNotNull()) { return true; } DfaConstValue dfaNull = myFactory.getConstFactory().getNull(); int c1Index = getEqClassIndex(dfaVar); int c2Index = getEqClassIndex(dfaNull); if (c1Index < 0 || c2Index < 0) { return false; } long[] pairs = myDistinctClasses.toArray(); for (long pair : pairs) { if (c1Index == low(pair) && c2Index == high(pair) || c1Index == high(pair) && c2Index == low(pair)) { return true; } } return false; } @Override @Nullable public DfaConstValue getConstantValue(DfaVariableValue value) { int index = getEqClassIndex(value); EqClass ec = index == -1 ? null : myEqClasses.get(index); return ec == null ? null : (DfaConstValue)unwrap(ec.findConstant(true)); } @Override public void markEphemeral() { myEphemeral = true; } @Override public boolean isEphemeral() { return myEphemeral; } @Override public boolean applyInstanceofOrNull(DfaRelationValue dfaCond) { DfaValue left = unwrap(dfaCond.getLeftOperand()); if (!(left instanceof DfaVariableValue)) return true; DfaVariableValue dfaVar = (DfaVariableValue)left; DfaTypeValue dfaType = (DfaTypeValue)dfaCond.getRightOperand(); if (isUnknownState(dfaVar) || isNull(dfaVar)) return true; DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue(dfaType); if (newState != null) { setVariableState(dfaVar, newState); return true; } return false; } static DfaValue unwrap(DfaValue value) { if (value instanceof DfaBoxedValue) { return ((DfaBoxedValue)value).getWrappedValue(); } else if (value instanceof DfaUnboxedValue) { return ((DfaUnboxedValue)value).getVariable(); } return value; } @Override public boolean applyCondition(DfaValue dfaCond) { if (dfaCond instanceof DfaUnknownValue) return true; if (dfaCond instanceof DfaUnboxedValue) { DfaVariableValue dfaVar = ((DfaUnboxedValue)dfaCond).getVariable(); boolean isNegated = dfaVar.isNegated(); DfaVariableValue dfaNormalVar = isNegated ? dfaVar.createNegated() : dfaVar; final DfaValue boxedTrue = myFactory.getBoxedFactory().createBoxed(myFactory.getConstFactory().getTrue()); return applyRelationCondition(myFactory.getRelationFactory().createRelation(dfaNormalVar, boxedTrue, JavaTokenType.EQEQ, isNegated)); } if (dfaCond instanceof DfaVariableValue) { DfaVariableValue dfaVar = (DfaVariableValue)dfaCond; boolean isNegated = dfaVar.isNegated(); DfaVariableValue dfaNormalVar = isNegated ? dfaVar.createNegated() : dfaVar; DfaConstValue dfaTrue = myFactory.getConstFactory().getTrue(); return applyRelationCondition(myFactory.getRelationFactory().createRelation(dfaNormalVar, dfaTrue, JavaTokenType.EQEQ, isNegated)); } if (dfaCond instanceof DfaConstValue) { return dfaCond == myFactory.getConstFactory().getTrue() || dfaCond != myFactory.getConstFactory().getFalse(); } if (!(dfaCond instanceof DfaRelationValue)) return true; return applyRelationCondition((DfaRelationValue)dfaCond); } private boolean applyRelationCondition(DfaRelationValue dfaRelation) { DfaValue dfaLeft = dfaRelation.getLeftOperand(); DfaValue dfaRight = dfaRelation.getRightOperand(); if (dfaLeft instanceof DfaUnknownValue || dfaRight instanceof DfaUnknownValue) return true; boolean isNegated = dfaRelation.isNegated(); if (dfaLeft instanceof DfaTypeValue && ((DfaTypeValue)dfaLeft).isNotNull() && dfaRight == myFactory.getConstFactory().getNull()) { return isNegated; } if (dfaRight instanceof DfaTypeValue) { if (dfaLeft instanceof DfaVariableValue) { DfaVariableValue dfaVar = (DfaVariableValue)dfaLeft; if (isUnknownState(dfaVar)) return true; if (!dfaRelation.isInstanceOf()) { if (((DfaTypeValue)dfaRight).isNotNull() && isNull(dfaVar)) { return isNegated; } return true; } if (isNegated) { DfaVariableState newState = getVariableState(dfaVar).withNotInstanceofValue((DfaTypeValue)dfaRight); if (newState != null) { setVariableState(dfaVar, newState); return true; } return applyRelation(dfaVar, myFactory.getConstFactory().getNull(), false); } if (applyRelation(dfaVar, myFactory.getConstFactory().getNull(), true)) { DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue((DfaTypeValue)dfaRight); if (newState != null) { setVariableState(dfaVar, newState); return true; } } return false; } return true; } if (isEffectivelyNaN(dfaLeft) || isEffectivelyNaN(dfaRight)) { applyEquivalenceRelation(dfaRelation, dfaLeft, dfaRight); return isNegated; } if (canBeNaN(dfaLeft) || canBeNaN(dfaRight)) { applyEquivalenceRelation(dfaRelation, dfaLeft, dfaRight); return true; } return applyEquivalenceRelation(dfaRelation, dfaLeft, dfaRight); } private void updateVarStateOnComparison(DfaVariableValue dfaVar, DfaValue value) { if (!isUnknownState(dfaVar)) { if (value instanceof DfaConstValue && ((DfaConstValue)value).getValue() == null) { setVariableState(dfaVar, getVariableState(dfaVar).withNullability(Nullness.NULLABLE)); } else if (isNotNull(value) && !isNotNull(dfaVar)) { setVariableState(dfaVar, getVariableState(dfaVar).withNullability(Nullness.UNKNOWN)); applyRelation(dfaVar, myFactory.getConstFactory().getNull(), true); } } } private boolean applyEquivalenceRelation(DfaRelationValue dfaRelation, DfaValue dfaLeft, DfaValue dfaRight) { boolean isNegated = dfaRelation.isNonEquality(); if (!isNegated && !dfaRelation.isEquality()) { return true; } if (isNull(dfaLeft) && isNotNull(dfaRight) || isNull(dfaRight) && isNotNull(dfaLeft)) { return isNegated; } if (!isNegated) { if (dfaLeft instanceof DfaVariableValue) { updateVarStateOnComparison((DfaVariableValue)dfaLeft, dfaRight); } if (dfaRight instanceof DfaVariableValue) { updateVarStateOnComparison((DfaVariableValue)dfaRight, dfaLeft); } } if (!applyRelation(dfaLeft, dfaRight, isNegated)) { return false; } if (!checkCompareWithBooleanLiteral(dfaLeft, dfaRight, isNegated)) { return false; } if (dfaLeft instanceof DfaVariableValue) { if (!applyUnboxedRelation((DfaVariableValue)dfaLeft, dfaRight, isNegated)) { return false; } if (!applyBoxedRelation((DfaVariableValue)dfaLeft, dfaRight, isNegated)) { return false; } } return true; } private boolean applyBoxedRelation(DfaVariableValue dfaLeft, DfaValue dfaRight, boolean negated) { if (!TypeConversionUtil.isPrimitiveAndNotNull(dfaLeft.getVariableType())) return true; DfaBoxedValue.Factory boxedFactory = myFactory.getBoxedFactory(); DfaValue boxedLeft = boxedFactory.createBoxed(dfaLeft); DfaValue boxedRight = boxedFactory.createBoxed(dfaRight); return boxedLeft == null || boxedRight == null || applyRelation(boxedLeft, boxedRight, negated); } private boolean applyUnboxedRelation(DfaVariableValue dfaLeft, DfaValue dfaRight, boolean negated) { PsiType type = dfaLeft.getVariableType(); if (!TypeConversionUtil.isPrimitiveWrapper(type)) { return true; } if (negated && !(unwrap(dfaRight) instanceof DfaConstValue)) { // from the fact (wrappers are not the same) does not follow (unboxed values are not equals) return true; } DfaBoxedValue.Factory boxedFactory = myFactory.getBoxedFactory(); return applyRelation(boxedFactory.createUnboxed(dfaLeft), boxedFactory.createUnboxed(dfaRight), negated); } private boolean checkCompareWithBooleanLiteral(DfaValue dfaLeft, DfaValue dfaRight, boolean negated) { if (dfaRight instanceof DfaConstValue) { Object constVal = ((DfaConstValue)dfaRight).getValue(); if (constVal instanceof Boolean) { DfaConstValue negVal = myFactory.getConstFactory().createFromValue(!((Boolean)constVal).booleanValue(), PsiType.BOOLEAN, null); if (!applyRelation(dfaLeft, negVal, !negated)) { return false; } } } return true; } static boolean isNaN(final DfaValue dfa) { if (dfa instanceof DfaConstValue) { Object value = ((DfaConstValue)dfa).getValue(); if (value instanceof Double && ((Double)value).isNaN()) return true; if (value instanceof Float && ((Float)value).isNaN()) return true; } else if (dfa instanceof DfaBoxedValue){ return isNaN(((DfaBoxedValue)dfa).getWrappedValue()); } return false; } private boolean applyRelation(@NotNull final DfaValue dfaLeft, @NotNull final DfaValue dfaRight, boolean isNegated) { if (isUnknownState(dfaLeft) || isUnknownState(dfaRight)) { return true; } // DfaConstValue || DfaVariableValue Integer c1Index = getOrCreateEqClassIndex(dfaLeft); Integer c2Index = getOrCreateEqClassIndex(dfaRight); if (c1Index == null || c2Index == null) { return true; } if (!isNegated) { //Equals if (c1Index.equals(c2Index)) return true; if (!uniteClasses(c1Index, c2Index)) return false; for (long encodedPair : myDistinctClasses.toArray()) { EqClass c1 = myEqClasses.get(low(encodedPair)); EqClass c2 = myEqClasses.get(high(encodedPair)); if (c1.findConstant(false) != null && c2.findConstant(false) != null) { myDistinctClasses.remove(encodedPair); } } myCachedDistinctClassPairs = null; myCachedNonTrivialEqClasses = null; } else { // Not Equals if (c1Index.equals(c2Index)) return false; makeClassesDistinct(c1Index, c2Index); myCachedDistinctClassPairs = null; } return true; } private boolean isUnknownState(DfaValue val) { val = unwrap(val); if (val instanceof DfaVariableValue) { if (myUnknownVariables.contains(val)) return true; DfaVariableValue negatedValue = ((DfaVariableValue)val).getNegatedValue(); if (negatedValue != null && myUnknownVariables.contains(negatedValue)) return true; } return false; } @Override public boolean checkNotNullable(DfaValue value) { if (value == myFactory.getConstFactory().getNull()) return false; if (value instanceof DfaTypeValue && ((DfaTypeValue)value).isNullable()) return false; if (value instanceof DfaVariableValue) { DfaVariableValue varValue = (DfaVariableValue)value; if (varValue.getVariableType() instanceof PsiPrimitiveType) return true; if (isNotNull(varValue)) return true; if (getVariableState(varValue).isNullable()) return false; } return true; } @Nullable private DfaRelationValue compareToNull(DfaValue dfaVar, boolean negated) { DfaConstValue dfaNull = myFactory.getConstFactory().getNull(); return myFactory.getRelationFactory().createRelation(dfaVar, dfaNull, JavaTokenType.EQEQ, negated); } void setVariableState(DfaVariableValue dfaVar, DfaVariableState state) { assert !myUnknownVariables.contains(dfaVar); if (state.equals(myDefaultVariableStates.get(dfaVar))) { myVariableStates.remove(dfaVar); } else { myVariableStates.put(dfaVar, state); } } public DfaVariableState getVariableState(DfaVariableValue dfaVar) { DfaVariableState state = myVariableStates.get(dfaVar); if (state == null) { state = myDefaultVariableStates.get(dfaVar); if (state == null) { state = createVariableState(dfaVar); DfaTypeValue initialType = dfaVar.getTypeValue(); if (initialType != null) { state = state.withInstanceofValue(initialType); assert state != null; } myDefaultVariableStates.put(dfaVar, state); } if (isUnknownState(dfaVar)) { return state.withNullable(false); } } return state; } protected Map getVariableStates() { return myVariableStates; } protected DfaVariableState createVariableState(final DfaVariableValue var) { return new DfaVariableState(var); } @Override public void flushFields() { Set vars = ContainerUtil.newLinkedHashSet(getChangedVariables()); for (EqClass aClass : myEqClasses) { if (aClass != null) { vars.addAll(aClass.getVariables(true)); } } for (DfaVariableValue value : vars) { if (value.isFlushableByCalls()) { doFlush(value, shouldMarkUnknown(value)); } } } private boolean shouldMarkUnknown(DfaVariableValue value) { int eqClassIndex = getEqClassIndex(value); if (eqClassIndex < 0) return false; EqClass eqClass = myEqClasses.get(eqClassIndex); if (eqClass == null) return false; if (eqClass.findConstant(true) != null) return true; for (UnorderedPair pair : getDistinctClassPairs()) { if (pair.first == eqClass && pair.second.findConstant(true) != null || pair.second == eqClass && pair.first.findConstant(true) != null) { return true; } } return false; } Set getChangedVariables() { return myVariableStates.keySet(); } @Override public void flushVariable(@NotNull DfaVariableValue variable) { doFlush(variable, false); flushDependencies(variable); myUnknownVariables.remove(variable); myUnknownVariables.removeAll(myFactory.getVarFactory().getAllQualifiedBy(variable)); } public void flushDependencies(DfaVariableValue variable) { for (DfaVariableValue dependent : myFactory.getVarFactory().getAllQualifiedBy(variable)) { doFlush(dependent, false); } } Set getUnknownVariables() { return myUnknownVariables; } void doFlush(DfaVariableValue varPlain, boolean markUnknown) { DfaVariableValue varNegated = varPlain.getNegatedValue(); final int idPlain = varPlain.getID(); final int idNegated = varNegated == null ? -1 : varNegated.getID(); int size = myEqClasses.size(); int interruptCount = 0; for (int varClassIndex = 0; varClassIndex < size; varClassIndex++) { EqClass varClass = myEqClasses.get(varClassIndex); if (varClass == null) continue; for (int i = 0; i < varClass.size(); i++) { if ((++interruptCount & 0xf) == 0) { ProgressManager.checkCanceled(); } int cl = varClass.get(i); DfaValue value = myFactory.getValue(cl); if (mine(idPlain, value) || idNegated >= 0 && mine(idNegated, value)) { myEqClasses.set(varClassIndex, varClass = new EqClass(varClass)); varClass.remove(i); break; } } if (varClass.isEmpty()) { myEqClasses.set(varClassIndex, null); long[] pairs = myDistinctClasses.toArray(); for (long pair : pairs) { if (low(pair) == varClassIndex || high(pair) == varClassIndex) { myDistinctClasses.remove(pair); } } } else if (varClass.containsConstantsOnly()) { for (long pair : myDistinctClasses.toArray()) { if (low(pair) == varClassIndex && myEqClasses.get(high(pair)).containsConstantsOnly() || high(pair) == varClassIndex && myEqClasses.get(low(pair)).containsConstantsOnly()) { myDistinctClasses.remove(pair); } } } } myVariableStates.remove(varPlain); if (varNegated != null) { myVariableStates.remove(varNegated); } if (markUnknown) { myUnknownVariables.add(varPlain); } myCachedNonTrivialEqClasses = null; myCachedDistinctClassPairs = null; } private static boolean mine(int id, DfaValue value) { return value != null && id == unwrap(value).getID(); } }