blob: 3f0caf47679e6851366e270be1755b86888b8cc3 [file] [log] [blame]
/*
* 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<EqClass> myEqClasses;
private final Stack<DfaValue> myStack;
private final TLongHashSet myDistinctClasses;
private final LinkedHashMap<DfaVariableValue,DfaVariableState> myVariableStates;
private final Map<DfaVariableValue,DfaVariableState> myDefaultVariableStates;
private final LinkedHashSet<DfaVariableValue> 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<DfaValue>();
}
protected DfaMemoryStateImpl(DfaMemoryStateImpl toCopy) {
myFactory = toCopy.myFactory;
myEphemeral = toCopy.myEphemeral;
myDefaultVariableStates = toCopy.myDefaultVariableStates; // shared between all states
myStack = new Stack<DfaValue>(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<UnorderedPair<EqClass>> myCachedDistinctClassPairs;
LinkedHashSet<UnorderedPair<EqClass>> getDistinctClassPairs() {
if (myCachedDistinctClassPairs != null) return myCachedDistinctClassPairs;
LinkedHashSet<UnorderedPair<EqClass>> result = ContainerUtil.newLinkedHashSet();
for (long encodedPair : myDistinctClasses.toArray()) {
result.add(new UnorderedPair<EqClass>(myEqClasses.get(low(encodedPair)), myEqClasses.get(high(encodedPair))));
}
return myCachedDistinctClassPairs = result;
}
private LinkedHashSet<EqClass> myCachedNonTrivialEqClasses;
LinkedHashSet<EqClass> getNonTrivialEqClasses() {
if (myCachedNonTrivialEqClasses != null) return myCachedNonTrivialEqClasses;
LinkedHashSet<EqClass> 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<String> distincts = new ArrayList<String>();
for (UnorderedPair<EqClass> 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<DfaVariableValue, DfaVariableState> entry : myVariableStates.entrySet()) {
result.append("[").append(entry.getKey()).append("->").append(entry.getValue()).append("] ");
}
}
if (!myUnknownVariables.isEmpty()) {
result.append("\n unknowns: ").append(new HashSet<DfaVariableValue>(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<DfaValue> 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<DfaVariableValue> vars = ContainerUtil.newTroveSet();
Set<DfaVariableValue> 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 (dfaLeft == dfaRight) {
if (dfaLeft instanceof DfaVariableValue && ((DfaVariableValue)dfaLeft).containsCalls()) {
return true;
}
return !isNegated;
}
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<DfaVariableValue, DfaVariableState> getVariableStates() {
return myVariableStates;
}
protected DfaVariableState createVariableState(final DfaVariableValue var) {
return new DfaVariableState(var);
}
@Override
public void flushFields() {
Set<DfaVariableValue> 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<EqClass> pair : getDistinctClassPairs()) {
if (pair.first == eqClass && pair.second.findConstant(true) != null ||
pair.second == eqClass && pair.first.findConstant(true) != null) {
return true;
}
}
return false;
}
Set<DfaVariableValue> 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<DfaVariableValue> 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();
}
}