IDEA-169503 Integral range tracking for variables inside dataflow analysis

This commit is contained in:
Tagir Valeev
2017-03-13 16:02:48 +07:00
parent 165e5b5c5e
commit 97cdb4cdd5
21 changed files with 1533 additions and 98 deletions
@@ -177,7 +177,7 @@ public class DataFlowRunner {
if (instruction instanceof BranchingInstruction) {
BranchingInstruction branching = (BranchingInstruction)instruction;
Collection<DfaMemoryState> processed = processedStates.get(branching);
if (processed.contains(instructionState.getMemoryState())) {
if (containsState(processed, instructionState)) {
continue;
}
if (processed.size() > MAX_STATES_PER_BRANCH) {
@@ -198,8 +198,8 @@ public class DataFlowRunner {
handleStepOutOfLoop(instruction, nextInstruction, loopNumber, processedStates, incomingStates, states, after, queue);
if (nextInstruction instanceof BranchingInstruction) {
BranchingInstruction branching = (BranchingInstruction)nextInstruction;
if (processedStates.get(branching).contains(state.getMemoryState()) ||
incomingStates.get(branching).contains(state.getMemoryState())) {
if (containsState(processedStates.get(branching), state) ||
containsState(incomingStates.get(branching), state)) {
continue;
}
if (loopNumber[branching.getIndex()] != 0) {
@@ -221,6 +221,19 @@ public class DataFlowRunner {
}
}
private static boolean containsState(Collection<DfaMemoryState> processed,
DfaInstructionState instructionState) {
if (processed.contains(instructionState.getMemoryState())) {
return true;
}
for (DfaMemoryState state : processed) {
if (((DfaMemoryStateImpl)state).isSuperStateOf((DfaMemoryStateImpl)instructionState.getMemoryState())) {
return true;
}
}
return false;
}
private void handleStepOutOfLoop(@NotNull final Instruction prevInstruction,
@NotNull Instruction nextInstruction,
@NotNull final int[] loopNumber,
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -26,7 +26,6 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
import com.intellij.openapi.util.Pair;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
@@ -125,7 +124,8 @@ class StateQueue {
StateMerger merger = new StateMerger();
while (true) {
List<DfaMemoryStateImpl> nextStates = merger.mergeByFacts(group);
List<DfaMemoryStateImpl> nextStates = merger.mergeByRanges(group);
if (nextStates == null) nextStates = merger.mergeByFacts(group);
if (nextStates == null) nextStates = merger.mergeByNullability(group);
if (nextStates == null) nextStates = merger.mergeByUnknowns(group);
if (nextStates == null) break;
@@ -15,6 +15,7 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
@@ -53,6 +54,9 @@ public interface DfaMemoryState {
ThreeState checkOptional(DfaValue value);
@Nullable
LongRangeSet getRange(DfaValue value);
void flushFields();
void flushVariable(DfaVariableValue variable);
@@ -24,6 +24,7 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
@@ -40,6 +41,7 @@ import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import gnu.trove.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -207,12 +209,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
if (!myDistinctClasses.isEmpty()) {
result.append("\n distincts: ");
List<String> distincts = new ArrayList<>();
for (UnorderedPair<EqClass> pair : getDistinctClassPairs()) {
distincts.add("{" + pair.first + ", " + pair.second + "}");
}
Collections.sort(distincts);
result.append(StringUtil.join(distincts, " "));
String distincts =
StreamEx.of(getDistinctClassPairs()).map(pair -> "{" + pair.first + ", " + pair.second + "}").sorted().joining(" ");
result.append(distincts);
}
if (!myStack.isEmpty()) {
@@ -361,6 +360,28 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
myIdToEqClassesIndices.remove(id);
}
/**
* Returns true if current state describes all possible concrete program states described by {@code that} state.
*
* @param that a sub-state candidate
* @return true if current state is a super-state of the supplied state.
*/
public boolean isSuperStateOf(DfaMemoryStateImpl that) {
if (!equalsSuperficially(that) ||
!equalsByUnknownVariables(that) ||
!getNonTrivialEqClasses().equals(that.getNonTrivialEqClasses()) ||
!that.getDistinctClassPairs().containsAll(getDistinctClassPairs())) {
return false;
}
if(myVariableStates.size() != that.myVariableStates.size()) return false;
for (Map.Entry<DfaVariableValue, DfaVariableState> entry : myVariableStates.entrySet()) {
DfaVariableState thisState = entry.getValue();
DfaVariableState thatState = that.myVariableStates.get(entry.getKey());
if(Objects.equals(thisState, thatState)) continue;
if(thatState == null || thisState == null || !thisState.isSuperStateOf(thatState)) return false;
}
return true;
}
private static boolean canBeInRelation(@NotNull DfaValue dfaValue) {
DfaValue unwrapped = unwrap(dfaValue);
@@ -640,6 +661,27 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
}
void setRange(DfaVariableValue target, LongRangeSet range) {
if (!isUnknownState(target)) {
setVariableState(target, getVariableState(target).withRange(range));
}
}
public boolean applyRange(LongRangeSet range, DfaVariableValue target) {
if (!isUnknownState(target) && range != null) {
DfaVariableState state = getVariableState(target);
LongRangeSet oldRange = state.getRange();
if (oldRange == null) {
oldRange = LongRangeSet.fromType(target.getVariableType());
if (oldRange == null) return true;
}
LongRangeSet newRange = oldRange.intersect(range);
if (newRange.isEmpty()) return false;
setVariableState(target, state.withRange(newRange));
}
return true;
}
static DfaValue unwrap(DfaValue value) {
if (value instanceof DfaBoxedValue) {
return ((DfaBoxedValue)value).getWrappedValue();
@@ -683,6 +725,25 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
if (dfaLeft instanceof DfaUnknownValue || dfaRight instanceof DfaUnknownValue) return true;
boolean isNegated = dfaRelation.isNegated();
if (dfaLeft instanceof DfaVariableValue) {
LongRangeSet right = getRange(dfaRight);
if (right != null) {
if (!applyRange(right.fromRelation(dfaRelation.getComparisonOperation()), (DfaVariableValue)dfaLeft)) {
return false;
}
}
}
if (dfaRight instanceof DfaVariableValue) {
LongRangeSet left = getRange(dfaLeft);
if (left != null) {
if (!applyRange(left.fromRelation(DfaRelationValue.getSymmetricOperation(dfaRelation.getComparisonOperation())),
(DfaVariableValue)dfaRight)) {
return false;
}
}
}
if (dfaLeft instanceof DfaTypeValue && ((DfaTypeValue)dfaLeft).isNotNull() && dfaRight == myFactory.getConstFactory().getNull()) {
return isNegated;
}
@@ -946,6 +1007,38 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return value instanceof DfaOptionalValue ? ThreeState.fromBoolean(((DfaOptionalValue)value).isPresent()) : ThreeState.UNSURE;
}
/**
* Returns range of possible values for given DfaValue if possible
*
* @param value value to get the range from
* @return possible range or null if range is not known/non-applicable. Empty range indicates that no exact value is possible
* for given DfaValue (likely impossible code path).
*/
@Nullable
@Override
public LongRangeSet getRange(DfaValue value) {
if (value instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue)value;
DfaVariableState state = getVariableState(var);
LongRangeSet range = state.getRange();
if (range == null) {
DfaConstValue constValue = getConstantValue(var);
if (constValue != null) {
return LongRangeSet.fromConstant(constValue.getValue());
}
return LongRangeSet.fromType(var.getVariableType());
}
return range;
}
if (value instanceof DfaRangeValue) {
return ((DfaRangeValue)value).getValue();
}
if (value instanceof DfaConstValue) {
return LongRangeSet.fromConstant(((DfaConstValue)value).getValue());
}
return null;
}
@Nullable
private DfaRelationValue compareToNull(DfaValue dfaVar, boolean negated) {
DfaConstValue dfaNull = myFactory.getConstFactory().getNull();
@@ -24,44 +24,69 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.*;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.MethodUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
class DfaVariableState {
@NotNull final Set<DfaPsiType> myInstanceofValues;
@NotNull final Set<DfaPsiType> myNotInstanceofValues;
@NotNull final Nullness myNullability;
@NotNull final ThreeState myOptionalPresence;
@Nullable final LongRangeSet myRange;
private final int myHash;
DfaVariableState(@NotNull DfaVariableValue dfaVar) {
this(Collections.emptySet(), Collections.emptySet(), dfaVar.getInherentNullability(), ThreeState.UNSURE);
this(Collections.emptySet(), Collections.emptySet(), dfaVar.getInherentNullability(), ThreeState.UNSURE, getInitialRange(dfaVar));
}
public boolean isSuperStateOf(DfaVariableState that) {
if(!myNotInstanceofValues.equals(that.myNotInstanceofValues)) return false;
if(!myInstanceofValues.equals(that.myNotInstanceofValues)) return false;
if(!myNullability.equals(that.myNullability)) return false;
if(!myOptionalPresence.equals(that.myOptionalPresence)) return false;
if(Objects.equals(myRange, that.myRange)) return true;
return myRange != null && that.myRange != null && myRange.contains(that.myRange);
}
private static LongRangeSet getInitialRange(DfaVariableValue var) {
DfaVariableValue qualifier = var.getQualifier();
if(qualifier != null) {
PsiModifierListOwner owner = var.getPsiVariable();
boolean stringLength = owner instanceof PsiMethod &&
MethodUtils.methodMatches((PsiMethod)owner, CommonClassNames.JAVA_LANG_STRING, PsiType.INT, "length");
boolean arrayLength =
owner instanceof PsiField && "length".equals(((PsiField)owner).getName()) && qualifier.getVariableType() instanceof PsiArrayType;
if(stringLength || arrayLength) {
return LongRangeSet.indexRange();
}
}
return LongRangeSet.fromType(var.getVariableType());
}
DfaVariableState(@NotNull Set<DfaPsiType> instanceofValues,
@NotNull Set<DfaPsiType> notInstanceofValues,
@NotNull Nullness nullability,
@NotNull ThreeState optionalPresence) {
@NotNull Set<DfaPsiType> notInstanceofValues,
@NotNull Nullness nullability,
@NotNull ThreeState optionalPresence,
@Nullable LongRangeSet range) {
myInstanceofValues = instanceofValues;
myNotInstanceofValues = notInstanceofValues;
myNullability = nullability;
myOptionalPresence = optionalPresence;
myHash = ((myInstanceofValues.hashCode() * 31 + myNotInstanceofValues.hashCode()) * 31 + myNullability.hashCode()) * 31 +
myOptionalPresence.hashCode();
myRange = range;
myHash = Objects.hash(myInstanceofValues, myNotInstanceofValues, myNullability, myOptionalPresence, range);
}
public boolean isNullable() {
@@ -101,7 +126,7 @@ class DfaVariableState {
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
newInstanceof.removeAll(moreGeneric);
newInstanceof.add(dfaType.getDfaType());
result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability, myOptionalPresence);
result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability, myOptionalPresence, myRange);
return result;
}
@@ -129,7 +154,7 @@ class DfaVariableState {
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
newNotInstanceof.removeAll(moreSpecific);
newNotInstanceof.add(dfaType.getDfaType());
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence);
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence, myRange);
}
@NotNull
@@ -137,12 +162,12 @@ class DfaVariableState {
if (myInstanceofValues.contains(type)) {
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
newInstanceof.remove(type);
return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myOptionalPresence);
return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myOptionalPresence, myRange);
}
if (myNotInstanceofValues.contains(type)) {
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
newNotInstanceof.remove(type);
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence);
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence, myRange);
}
return this;
}
@@ -159,14 +184,17 @@ class DfaVariableState {
myNullability == aState.myNullability &&
myOptionalPresence == aState.myOptionalPresence &&
myInstanceofValues.equals(aState.myInstanceofValues) &&
myNotInstanceofValues.equals(aState.myNotInstanceofValues);
myNotInstanceofValues.equals(aState.myNotInstanceofValues) &&
Objects.equals(myRange, aState.myRange);
}
@NotNull
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
@NotNull Set<DfaPsiType> notInstanceofValues,
@NotNull Nullness nullability, ThreeState optionalPresent) {
return new DfaVariableState(instanceofValues, notInstanceofValues, nullability, optionalPresent);
@NotNull Nullness nullability,
ThreeState optionalPresent,
LongRangeSet range) {
return new DfaVariableState(instanceofValues, notInstanceofValues, nullability, optionalPresent, range);
}
public String toString() {
@@ -184,6 +212,9 @@ class DfaVariableState {
if (myOptionalPresence != ThreeState.UNSURE) {
buf.append(myOptionalPresence == ThreeState.YES ? " Optional with value" : " empty Optional");
}
if (myRange != null) {
buf.append(" ").append(myRange);
}
return buf.toString();
}
@@ -198,7 +229,7 @@ class DfaVariableState {
@NotNull
DfaVariableState withNullability(@NotNull Nullness nullness) {
return myNullability == nullness ? this : createCopy(myInstanceofValues, myNotInstanceofValues, nullness, myOptionalPresence);
return myNullability == nullness ? this : createCopy(myInstanceofValues, myNotInstanceofValues, nullness, myOptionalPresence, myRange);
}
@NotNull
@@ -209,10 +240,16 @@ class DfaVariableState {
DfaVariableState withOptionalPresense(final boolean presense) {
ThreeState optionalPresent = ThreeState.fromBoolean(presense);
return myOptionalPresence != optionalPresent
? createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, optionalPresent)
? createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, optionalPresent, myRange)
: this;
}
DfaVariableState withRange(@Nullable LongRangeSet range) {
return Objects.equals(range, myRange)
? this
: createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, myOptionalPresence, range);
}
@NotNull
public DfaVariableState withValue(DfaValue value) {
return this;
@@ -234,4 +271,9 @@ class DfaVariableState {
public ThreeState getOptionalPresense() {
return myOptionalPresence;
}
@Nullable
public LongRangeSet getRange() {
return myRange;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -130,7 +130,7 @@ class NullParameterConstraintChecker extends DataFlowRunner {
super(factory);
for (PsiParameter parameter : myPossiblyViolatedParameters) {
setVariableState(getFactory().getVarFactory().createVariableValue(parameter, false),
new DfaVariableState(Collections.emptySet(), Collections.emptySet(), Nullness.NULLABLE, ThreeState.UNSURE));
new DfaVariableState(Collections.emptySet(), Collections.emptySet(), Nullness.NULLABLE, ThreeState.UNSURE, null));
}
}
@@ -16,6 +16,7 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
@@ -29,6 +30,9 @@ import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ComparisonUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
@@ -42,11 +46,18 @@ import static com.intellij.psi.JavaTokenType.*;
* @author peter
*/
public class StandardInstructionVisitor extends InstructionVisitor {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.StandardInstructionVisitor");
private static final Object ANY_VALUE = new Object();
private static final Set<String> OPTIONAL_METHOD_NAMES =
ContainerUtil.set("isPresent", "of", "ofNullable", "fromNullable", "empty", "absent",
"or", "orElseGet", "ifPresent", "map", "flatMap", "filter", "transform");
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.StandardInstructionVisitor");
private static final Object ANY_VALUE = new Object();
private static final CallMapper<LongRangeSet> KNOWN_METHOD_RANGES = new CallMapper<LongRangeSet>()
.register(CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "indexOf", "lastIndexOf"),
LongRangeSet.range(-1, Integer.MAX_VALUE))
.register(CallMatcher.instanceCall("java.time.LocalDateTime", "getHour"), LongRangeSet.range(0, 23))
.register(CallMatcher.instanceCall("java.time.LocalDateTime", "getMinute", "getSecond"), LongRangeSet.range(0, 59));
private final Set<BinopInstruction> myReachable = new THashSet<>();
private final Set<BinopInstruction> myCanBeNullInInstanceof = new THashSet<>();
private final MultiMap<PushInstruction, Object> myPossibleVariableValues = MultiMap.createSet();
@@ -451,6 +462,17 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
return factory.createTypeValue(type, nullability);
}
DfaRangeValue rangeValue = factory.getRangeFactory().create(type);
if (rangeValue != null) {
PsiCall call = instruction.getCallExpression();
if (call instanceof PsiMethodCallExpression) {
LongRangeSet range = KNOWN_METHOD_RANGES.mapFirst((PsiMethodCallExpression)call);
if (range != null) {
return rangeValue.intersect(range);
}
}
return rangeValue;
}
return DfaUnknownValue.getInstance();
}
@@ -476,6 +498,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
final IElementType opSign = instruction.getOperationSign();
if (opSign != null) {
DfaInstructionState[] states = handleConstantComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign);
if (states == null) {
states = handleRangeComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign);
}
if (states == null) {
states = handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft);
}
@@ -557,6 +582,27 @@ public class StandardInstructionVisitor extends InstructionVisitor {
myUsefulInstanceofs.add(instruction);
}
@Nullable
private static DfaInstructionState[] handleRangeComparison(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState state,
DfaValue right,
DfaValue left, IElementType sign) {
LongRangeSet leftRange = state.getRange(left);
if (leftRange == null) return null;
LongRangeSet rightRange = state.getRange(right);
if (rightRange == null) return null;
LongRangeSet constraint = rightRange.fromRelation(sign);
if (constraint != null && !constraint.intersects(leftRange)) {
return alwaysFalse(instruction, runner, state);
}
LongRangeSet revConstraint = rightRange.fromRelation(ComparisonUtils.getNegatedComparisonTokenType(sign));
if (revConstraint != null && !revConstraint.intersects(leftRange)) {
return alwaysTrue(instruction, runner, state);
}
return null;
}
@Nullable
private static DfaInstructionState[] handleConstantComparison(BinopInstruction instruction,
DataFlowRunner runner,
@@ -608,26 +654,11 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaMemoryState memState,
DfaVariableValue var,
IElementType opSign, Number comparedWith) {
Object knownValue = getKnownNumberValue(memState, var);
Number knownValue = getKnownNumberValue(memState, var);
if (knownValue != null) {
return checkComparisonWithKnownValue(instruction, runner, memState, opSign, (Number)knownValue, comparedWith);
return checkComparisonWithKnownValue(instruction, runner, memState, opSign, knownValue, comparedWith);
}
PsiType varType = var.getVariableType();
if (!(varType instanceof PsiPrimitiveType)) return null;
if (PsiType.FLOAT.equals(varType) || PsiType.DOUBLE.equals(varType)) return null;
double minValue = PsiType.BYTE.equals(varType) ? Byte.MIN_VALUE : PsiType.SHORT.equals(varType)
? Short.MIN_VALUE : PsiType.INT.equals(varType)
? Integer.MIN_VALUE : PsiType.CHAR.equals(varType) ? Character.MIN_VALUE :
Long.MIN_VALUE;
double maxValue = PsiType.BYTE.equals(varType) ? Byte.MAX_VALUE : PsiType.SHORT.equals(varType)
? Short.MAX_VALUE : PsiType.INT.equals(varType)
? Integer.MAX_VALUE : PsiType.CHAR.equals(varType) ? Character.MAX_VALUE :
Long.MAX_VALUE;
return checkComparisonWithKnownRange(instruction, runner, memState, opSign, comparedWith, minValue, maxValue);
return null;
}
@Nullable
@@ -642,7 +673,28 @@ public class StandardInstructionVisitor extends InstructionVisitor {
IElementType opSign,
Number leftValue,
Number rightValue) {
return checkComparisonWithKnownRange(instruction, runner, memState, opSign, rightValue, leftValue, leftValue);
int cmp = compare(leftValue, rightValue);
Boolean result = null;
if (cmp < 0 || cmp > 0) {
if(opSign == EQEQ) result = false;
else if (opSign == NE) result = true;
}
if (opSign == LT) {
result = cmp < 0;
}
else if (opSign == GT) {
result = cmp > 0;
}
else if (opSign == LE) {
result = cmp <= 0;
}
else if (opSign == GE) {
result = cmp >= 0;
}
if (result == null) {
return null;
}
return result ? alwaysTrue(instruction, runner, memState) : alwaysFalse(instruction, runner, memState);
}
private static int compare(Number a, Number b) {
@@ -653,32 +705,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return Double.compare(a.doubleValue(), b.doubleValue());
}
@Nullable
private static DfaInstructionState[] checkComparisonWithKnownRange(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
IElementType opSign,
Number comparedWith,
Number rangeMin,
Number rangeMax) {
if (compare(comparedWith, rangeMin) < 0 || compare(comparedWith, rangeMax) > 0) {
if (opSign == EQEQ) return alwaysFalse(instruction, runner, memState);
if (opSign == NE) return alwaysTrue(instruction, runner, memState);
}
if (opSign == LT && compare(comparedWith, rangeMin) <= 0) return alwaysFalse(instruction, runner, memState);
if (opSign == LT && compare(comparedWith, rangeMax) > 0) return alwaysTrue(instruction, runner, memState);
if (opSign == LE && compare(comparedWith, rangeMax) >= 0) return alwaysTrue(instruction, runner, memState);
if (opSign == LE && compare(comparedWith, rangeMin) < 0) return alwaysFalse(instruction, runner, memState);
if (opSign == GT && compare(comparedWith, rangeMax) >= 0) return alwaysFalse(instruction, runner, memState);
if (opSign == GT && compare(comparedWith, rangeMin) < 0) return alwaysTrue(instruction, runner, memState);
if (opSign == GE && compare(comparedWith, rangeMin) <= 0) return alwaysTrue(instruction, runner, memState);
if (opSign == GE && compare(comparedWith, rangeMax) > 0) return alwaysFalse(instruction, runner, memState);
return null;
}
private static DfaInstructionState[] alwaysFalse(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.push(runner.getFactory().getConstFactory().getFalse());
instruction.setFalseReachable();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -15,13 +15,14 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.UnorderedPair;
import com.intellij.psi.JavaTokenType;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,6 +33,7 @@ import java.util.*;
* @author peter
*/
class StateMerger {
public static final int MAX_RANGE_STATES = 100;
private final Map<DfaMemoryStateImpl, Set<Fact>> myFacts = ContainerUtil.newIdentityHashMap();
private final Map<DfaMemoryState, Map<DfaVariableValue, DfaMemoryStateImpl>> myCopyCache = ContainerUtil.newIdentityHashMap();
@@ -203,6 +205,57 @@ class StateMerger {
return replacements.getMergeResult();
}
@Nullable
List<DfaMemoryStateImpl> mergeByRanges(List<DfaMemoryStateImpl> states) {
// If the same variable has different range A and B in different memState and range A contains range B
// then range A is replaced with range B
Map<DfaVariableValue, Map<LongRangeSet, LongRangeSet>> ranges = new LinkedHashMap<>();
for (DfaMemoryStateImpl state : states) {
Map<DfaVariableValue, DfaVariableState> variableStates = state.getVariableStates();
for (Map.Entry<DfaVariableValue, DfaVariableState> entry : variableStates.entrySet()) {
LongRangeSet range = entry.getValue().getRange();
if (range != null) {
ranges.computeIfAbsent(entry.getKey(), k -> new HashMap<>()).put(range, range);
}
}
}
boolean changed = false;
for (Map<LongRangeSet, LongRangeSet> map : ranges.values()) {
for (Map.Entry<LongRangeSet, LongRangeSet> entry : map.entrySet()) {
for(LongRangeSet candidate : map.values()) {
if(!entry.getValue().equals(candidate) && candidate.contains(entry.getValue())) {
entry.setValue(candidate);
changed = true;
}
}
}
}
if(changed) {
changed = false;
for (DfaMemoryStateImpl state : states) {
for (Map.Entry<DfaVariableValue, Map<LongRangeSet, LongRangeSet>> entry : ranges.entrySet()) {
DfaVariableState variableState = state.getVariableState(entry.getKey());
LongRangeSet range = variableState.getRange();
LongRangeSet boundingRange = entry.getValue().get(range);
if (boundingRange != null && !boundingRange.equals(range)) {
state.setRange(entry.getKey(), boundingRange);
changed = true;
}
}
}
if(changed) {
return new ArrayList<>(new LinkedHashSet<>(states));
}
}
if (states.size() <= MAX_RANGE_STATES || ranges.isEmpty()) return null;
// If there are too many states, try to drop range information from some variable
DfaVariableValue lastVar = Collections.max(ranges.keySet(), Comparator.comparing(DfaVariableValue::getID));
for (DfaMemoryStateImpl state : states) {
state.setRange(lastVar, null);
}
return new ArrayList<>(new HashSet<>(states));
}
private static boolean mergeUnknowns(@NotNull Replacements replacements, @NotNull List<DfaMemoryStateImpl> complementary) {
if (complementary.size() < 2) return false;
@@ -220,10 +273,7 @@ class StateMerger {
@NotNull
private DfaMemoryStateImpl copyWithoutVar(@NotNull DfaMemoryStateImpl state, @NotNull DfaVariableValue var) {
Map<DfaVariableValue, DfaMemoryStateImpl> map = myCopyCache.get(state);
if (map == null) {
myCopyCache.put(state, map = ContainerUtil.newIdentityHashMap());
}
Map<DfaVariableValue, DfaMemoryStateImpl> map = myCopyCache.computeIfAbsent(state, k -> ContainerUtil.newIdentityHashMap());
DfaMemoryStateImpl copy = map.get(var);
if (copy == null) {
copy = state.createCopy();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -16,6 +16,7 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
@@ -78,8 +79,10 @@ class ValuableDataFlowRunner extends DataFlowRunner {
private ValuableDfaVariableState(Set<DfaPsiType> instanceofValues,
Set<DfaPsiType> notInstanceofValues,
Nullness nullability, DfaValue value,
@NotNull FList<PsiExpression> concatenation, ThreeState optionalPresence) {
super(instanceofValues, notInstanceofValues, nullability, optionalPresence);
@NotNull FList<PsiExpression> concatenation,
ThreeState optionalPresence,
LongRangeSet range) {
super(instanceofValues, notInstanceofValues, nullability, optionalPresence, range);
myValue = value;
myConcatenation = concatenation;
}
@@ -89,8 +92,10 @@ class ValuableDataFlowRunner extends DataFlowRunner {
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
@NotNull Set<DfaPsiType> notInstanceofValues,
@NotNull Nullness nullability,
ThreeState optionalPresence) {
return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation, optionalPresence);
ThreeState optionalPresence,
LongRangeSet range) {
return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation, optionalPresence,
range);
}
@NotNull
@@ -98,13 +103,13 @@ class ValuableDataFlowRunner extends DataFlowRunner {
public DfaVariableState withValue(@Nullable final DfaValue value) {
if (value == myValue) return this;
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, value, myConcatenation,
myOptionalPresence);
myOptionalPresence, myRange);
}
ValuableDfaVariableState withExpression(@NotNull final FList<PsiExpression> concatenation) {
if (concatenation == myConcatenation) return this;
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation,
myOptionalPresence);
myOptionalPresence, myRange);
}
@Override
@@ -0,0 +1,638 @@
/*
* Copyright 2000-2017 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.
*/
package com.intellij.codeInspection.dataFlow.rangeSet;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.NoSuchElementException;
/**
* An immutable set of long values optimized for small number of ranges.
*
* @author Tagir Valeev
*/
public abstract class LongRangeSet {
LongRangeSet() {}
/**
* Subtracts given set from the current
*
* @param other set to subtract
* @return a new set
*/
public abstract LongRangeSet subtract(LongRangeSet other);
public LongRangeSet gt(long value) {
return subtract(range(Long.MIN_VALUE, value));
}
public LongRangeSet ge(long value) {
return value == Long.MIN_VALUE ? this : subtract(range(Long.MIN_VALUE, value - 1));
}
public LongRangeSet lt(long value) {
return subtract(range(value, Long.MAX_VALUE));
}
public LongRangeSet le(long value) {
return value == Long.MAX_VALUE ? this : subtract(range(value + 1, Long.MAX_VALUE));
}
public LongRangeSet eq(long value) {
return contains(value) ? point(value) : Empty.EMPTY;
}
public LongRangeSet ne(long value) {
return subtract(point(value));
}
/**
* @return true if set is empty
*/
public boolean isEmpty() {
return this == Empty.EMPTY;
}
/**
* Intersects current set with other
*
* @param other other set to intersect with
* @return a new set
*/
public abstract LongRangeSet intersect(LongRangeSet other);
/**
* @return a minimal value contained in the set
* @throws NoSuchElementException if set is empty
*/
public abstract long min();
/**
* @return a maximal value contained in the set
* @throws NoSuchElementException if set is empty
*/
public abstract long max();
/**
* Checks if current set and other set have at least one common element
*
* @param other other set to check whether intersection exists
* @return true if this set intersects other set
*/
public abstract boolean intersects(LongRangeSet other);
/**
* Checks whether current set contains given value
*
* @param value value to find
* @return true if current set contains given value
*/
public abstract boolean contains(long value);
/**
* Checks whether current set contains all the values from other set
*
* @param other a sub-set candidate
* @return true if current set contains all the values from other
*/
public abstract boolean contains(LongRangeSet other);
/**
* Creates a new set which contains all possible values satisfying given predicate regarding the current set.
* <p>
* E.g. if current set is {0..10} and relation is "GT", then result will be {1..Long.MAX_VALUE} (values which can be greater than
* some value from the current set)
*
* @param relation relation to be applied to current set (JavaTokenType.EQEQ/NE/GT/GE/LT/LE)
* @return new set or null if relation is unsupported
*/
public LongRangeSet fromRelation(IElementType relation) {
if (isEmpty()) return null;
if (JavaTokenType.EQEQ.equals(relation)) {
return this;
}
if (JavaTokenType.NE.equals(relation)) {
long min = min();
if (min == max()) return Range.LONG_RANGE.subtract(this);
return Range.LONG_RANGE;
}
if (JavaTokenType.GT.equals(relation)) {
long min = min();
return min == Long.MAX_VALUE ? empty() : range(min + 1, Long.MAX_VALUE);
}
if (JavaTokenType.GE.equals(relation)) {
return range(min(), Long.MAX_VALUE);
}
if (JavaTokenType.LE.equals(relation)) {
return range(Long.MIN_VALUE, max());
}
if (JavaTokenType.LT.equals(relation)) {
long max = max();
return max == Long.MIN_VALUE ? empty() : range(Long.MIN_VALUE, max - 1);
}
return null;
}
/**
* @return an empty set
*/
public static LongRangeSet empty() {
return Empty.EMPTY;
}
/**
* Creates a set containing single given value
*
* @param value a value to be included into the set
* @return a new set
*/
public static LongRangeSet point(long value) {
return new Point(value);
}
/**
* Creates a set containing single value which is equivalent to supplied boxed constant (if its type is supported)
*
* @param val constant to create a set from
* @return new LongRangeSet or null if constant type is unsupported
*/
@Nullable
public static LongRangeSet fromConstant(Object val) {
if (val instanceof Byte || val instanceof Short || val instanceof Integer || val instanceof Long) {
return point(((Number)val).longValue());
}
else if (val instanceof Character) {
return point(((Character)val).charValue());
}
return null;
}
/**
* Creates a new set which contains all the numbers between from (inclusive) and to (inclusive)
*
* @param from lower bound
* @param to upper bound (must be greater or equal to {@code from})
* @return a new LongRangeSet
*/
public static LongRangeSet range(long from, long to) {
return from == to ? new Point(from) : new Range(from, to);
}
abstract long[] asRanges();
static String toString(long from, long to) {
return from == to ? String.valueOf(from) : from + (to - from == 1 ? ", " : "..") + to;
}
/**
* @return LongRangeSet describing possible array or string indices (from 0 to Integer.MAX_VALUE)
*/
public static LongRangeSet indexRange() {
return Range.INDEX_RANGE;
}
/**
* Creates a range for given type (for primitives and boxed: values range)
*
* @param type type to create a range for
* @return a range or null if type is not supported
*/
@Nullable
public static LongRangeSet fromType(PsiType type) {
if (type == null) {
return null;
}
type = PsiPrimitiveType.getOptionallyUnboxedType(type);
if (type != null) {
if (type.equals(PsiType.BYTE)) {
return Range.BYTE_RANGE;
}
if (type.equals(PsiType.CHAR)) {
return Range.CHAR_RANGE;
}
if (type.equals(PsiType.SHORT)) {
return Range.SHORT_RANGE;
}
if (type.equals(PsiType.INT)) {
return Range.INT_RANGE;
}
if (type.equals(PsiType.LONG)) {
return Range.LONG_RANGE;
}
}
return null;
}
static LongRangeSet fromRanges(long[] ranges, int bound) {
if (bound == 0) {
return Empty.EMPTY;
}
else if (bound == 2) {
return range(ranges[0], ranges[1]);
}
else {
return new RangeSet(Arrays.copyOfRange(ranges, 0, bound));
}
}
static final class Empty extends LongRangeSet {
static final LongRangeSet EMPTY = new Empty();
@Override
public LongRangeSet subtract(LongRangeSet other) {
return this;
}
@Override
public LongRangeSet intersect(LongRangeSet other) {
return this;
}
@Override
public long min() {
throw new NoSuchElementException();
}
@Override
public long max() {
throw new NoSuchElementException();
}
@Override
public boolean intersects(LongRangeSet other) {
return false;
}
@Override
public boolean contains(long value) {
return false;
}
@Override
public boolean contains(LongRangeSet other) {
return other.isEmpty();
}
@Override
long[] asRanges() {
return new long[0];
}
@Override
public int hashCode() {
return 2154231;
}
@Override
public boolean equals(Object obj) {
return obj == this;
}
@Override
public String toString() {
return "{}";
}
}
static final class Point extends LongRangeSet {
final long myValue;
Point(long value) {
myValue = value;
}
@Override
public LongRangeSet subtract(LongRangeSet other) {
return other.contains(myValue) ? Empty.EMPTY : this;
}
@Override
public LongRangeSet intersect(LongRangeSet other) {
return other.contains(myValue) ? this : Empty.EMPTY;
}
@Override
public long min() {
return myValue;
}
@Override
public long max() {
return myValue;
}
@Override
public boolean intersects(LongRangeSet other) {
return other.contains(myValue);
}
@Override
public boolean contains(long value) {
return myValue == value;
}
@Override
public boolean contains(LongRangeSet other) {
return other.isEmpty() || equals(other);
}
@Override
long[] asRanges() {
return new long[] {myValue, myValue};
}
@Override
public int hashCode() {
return Long.hashCode(myValue);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
return o != null && o instanceof Point && myValue == ((Point)o).myValue;
}
@Override
public String toString() {
return "{" + myValue + "}";
}
}
static final class Range extends LongRangeSet {
static final Range BYTE_RANGE = new Range(Byte.MIN_VALUE, Byte.MAX_VALUE);
static final Range CHAR_RANGE = new Range(Character.MIN_VALUE, Character.MAX_VALUE);
static final Range SHORT_RANGE = new Range(Short.MIN_VALUE, Short.MAX_VALUE);
static final Range INT_RANGE = new Range(Integer.MIN_VALUE, Integer.MAX_VALUE);
static final Range LONG_RANGE = new Range(Long.MIN_VALUE, Long.MAX_VALUE);
static final Range INDEX_RANGE = new Range(0, Integer.MAX_VALUE);
final long myFrom; // inclusive
final long myTo; // inclusive
Range(long from, long to) {
if (to <= from) { // to == from => must be Point
throw new IllegalArgumentException(to + "<=" + from);
}
myFrom = from;
myTo = to;
}
@Override
public LongRangeSet subtract(LongRangeSet other) {
if (other.isEmpty()) return this;
if (other == this) return Empty.EMPTY;
if (other instanceof Point) {
long value = ((Point)other).myValue;
if (value < myFrom || value > myTo) return this;
if (value == myFrom) return range(myFrom + 1, myTo);
if (value == myTo) return range(myFrom, myTo - 1);
return new RangeSet(new long[]{myFrom, value - 1, value + 1, myTo});
}
if (other instanceof Range) {
long from = ((Range)other).myFrom;
long to = ((Range)other).myTo;
if (to < myFrom || from > myTo) return this;
if (from <= myFrom && to >= myTo) return Empty.EMPTY;
if (from > myFrom && to < myTo) {
return new RangeSet(new long[]{myFrom, from - 1, to + 1, myTo});
}
if (from <= myFrom) {
return range(to + 1, myTo);
}
if (to >= myTo) {
return range(myFrom, from - 1);
}
throw new InternalError("Impossible: " + this + ":" + other);
}
long[] ranges = ((RangeSet)other).myRanges;
LongRangeSet result = this;
for (int i = 0; i < ranges.length; i += 2) {
result = result.subtract(range(ranges[i], ranges[i + 1]));
if (result.isEmpty()) return result;
}
return result;
}
@Override
public LongRangeSet intersect(LongRangeSet other) {
if (other == this) return this;
if (other.isEmpty()) return other;
if (other instanceof Point) {
return other.intersect(this);
}
if (other instanceof Range) {
long from = ((Range)other).myFrom;
long to = ((Range)other).myTo;
if (from <= myFrom && to >= myTo) return this;
if (from >= myFrom && to <= myTo) return other;
if (from < myFrom) {
from = myFrom;
}
if (to > myTo) {
to = myTo;
}
return from <= to ? range(from, to) : Empty.EMPTY;
}
long[] ranges = ((RangeSet)other).myRanges;
long[] result = new long[ranges.length];
int index = 0;
for (int i = 0; i < ranges.length; i += 2) {
long[] res = intersect(range(ranges[i], ranges[i + 1])).asRanges();
System.arraycopy(res, 0, result, index, res.length);
index += res.length;
}
return fromRanges(result, index);
}
@Override
public long min() {
return myFrom;
}
@Override
public long max() {
return myTo;
}
@Override
public boolean intersects(LongRangeSet other) {
if (other.isEmpty()) return false;
if (other instanceof RangeSet) {
return other.intersects(this);
}
return myTo >= other.min() && myFrom <= other.max();
}
@Override
public boolean contains(long value) {
return myFrom <= value && myTo >= value;
}
@Override
public boolean contains(LongRangeSet other) {
return other.isEmpty() || other.min() >= myFrom && other.max() <= myTo;
}
@Override
long[] asRanges() {
return new long[] {myFrom, myTo};
}
@Override
public int hashCode() {
return Long.hashCode(myFrom) * 1337 + Long.hashCode(myTo);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
return o != null && o instanceof Range && myFrom == ((Range)o).myFrom && myTo == ((Range)o).myTo;
}
@Override
public String toString() {
return "{" + toString(myFrom, myTo) + "}";
}
}
static final class RangeSet extends LongRangeSet {
final long[] myRanges;
RangeSet(long[] ranges) {
if (ranges.length < 4 || ranges.length % 2 != 0) {
// 0 ranges = Empty; 1 range = Range
throw new IllegalArgumentException("Bad length: " + ranges.length + " " + Arrays.toString(ranges));
}
for (int i = 0; i < ranges.length; i += 2) {
if (ranges[i + 1] < ranges[i]) {
throw new IllegalArgumentException("Bad sub-range #" + (i / 2) + " " + Arrays.toString(ranges));
}
if (i > 0 && (ranges[i - 1] == Long.MAX_VALUE || 1 + ranges[i - 1] > ranges[i])) {
throw new IllegalArgumentException("Bad sub-ranges #" + (i / 2 - 1) + " and #" + (i / 2) + " " + Arrays.toString(ranges));
}
}
myRanges = ranges;
}
@Override
public LongRangeSet subtract(LongRangeSet other) {
if (other.isEmpty()) return this;
if (other == this) return Empty.EMPTY;
long[] result = new long[myRanges.length + other.asRanges().length];
int index = 0;
for (int i = 0; i < myRanges.length; i += 2) {
LongRangeSet res = range(myRanges[i], myRanges[i + 1]).subtract(other);
long[] ranges = res.asRanges();
System.arraycopy(ranges, 0, result, index, ranges.length);
index += ranges.length;
}
return fromRanges(result, index);
}
@Override
public LongRangeSet intersect(LongRangeSet other) {
if (other == this) return this;
if (other.isEmpty()) return other;
if (other instanceof Point || other instanceof Range) {
return other.intersect(this);
}
return subtract(Range.LONG_RANGE.subtract(other));
}
@Override
public long min() {
return myRanges[0];
}
@Override
public long max() {
return myRanges[myRanges.length - 1];
}
@Override
public boolean intersects(LongRangeSet other) {
if (other.isEmpty()) return false;
if (other instanceof Point) {
return contains(((Point)other).myValue);
}
long[] otherRanges = other.asRanges();
int a = 0, b = 0;
while (true) {
long aFrom = myRanges[a];
long aTo = myRanges[a + 1];
long bFrom = otherRanges[b];
long bTo = otherRanges[b + 1];
if (aFrom <= bTo && bFrom <= aTo) return true;
if (aFrom > bTo) {
b += 2;
if (b >= otherRanges.length) return false;
}
else {
a += 2;
if (a >= myRanges.length) return false;
}
}
}
@Override
public boolean contains(long value) {
for (int i = 0; i < myRanges.length; i += 2) {
if (value >= myRanges[i] && value <= myRanges[i + 1]) {
return true;
}
}
return false;
}
@Override
public boolean contains(LongRangeSet other) {
if (other.isEmpty() || other == this) return true;
return other.subtract(this).isEmpty();
}
@Override
long[] asRanges() {
return myRanges;
}
@Override
public int hashCode() {
return Arrays.hashCode(myRanges);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
return o != null && o instanceof RangeSet && Arrays.equals(myRanges, ((RangeSet)o).myRanges);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
for (int i = 0; i < myRanges.length; i += 2) {
if (i > 0) sb.append(", ");
sb.append(LongRangeSet.toString(myRanges[i], myRanges[i + 1]));
}
sb.append("}");
return sb.toString();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -29,6 +29,7 @@ import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.MethodUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -174,6 +175,9 @@ public class DfaExpressionFactory {
return method;
}
}
if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_LANG_STRING, PsiType.INT, "length")) {
return method;
}
if (AnnotationUtil.findAnnotation(method.getContainingClass(), "javax.annotation.concurrent.Immutable") != null) {
return method;
}
@@ -0,0 +1,70 @@
/*
* Copyright 2000-2017 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.
*/
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* @author Tagir Valeev
*/
public class DfaRangeValue extends DfaValue {
private final LongRangeSet myValue;
DfaRangeValue(DfaValueFactory factory, @NotNull LongRangeSet value) {
super(factory);
myValue = value;
}
public DfaRangeValue intersect(LongRangeSet value) {
return myFactory.getRangeFactory().create(myValue.intersect(value));
}
public LongRangeSet getValue() {
return myValue;
}
public static class Factory {
private Map<LongRangeSet, DfaRangeValue> myValues = new HashMap<>();
private DfaValueFactory myFactory;
Factory(DfaValueFactory factory) {
myFactory = factory;
}
/**
* Any value of given type (if type is supported)
*
* @param type type to create a range-value from
* @return DfaRangeValue representing range of given type
*/
@Nullable
public DfaRangeValue create(PsiType type) {
LongRangeSet domain = LongRangeSet.fromType(type);
return domain == null ? null : create(domain);
}
@NotNull
public DfaRangeValue create(LongRangeSet value) {
return myValues.computeIfAbsent(value, val -> new DfaRangeValue(myFactory, val));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -29,6 +29,7 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@@ -168,6 +169,23 @@ public class DfaRelationValue extends DfaValue {
return myRelation == EQEQ && myIsNegated || myRelation == GT && !myIsNegated || myRelation == GE && myIsNegated;
}
/**
* @return comparison operation (GT, GE, LE, LT, EQEQ, NE) if this relation represents comparison, null otherwise
*/
@Nullable
public IElementType getComparisonOperation() {
if(myRelation == GT) {
return myIsNegated ? LE : GT;
}
if(myRelation == GE) {
return myIsNegated ? LT : GE;
}
if(myRelation == EQEQ) {
return myIsNegated ? NE : EQEQ;
}
return null;
}
public boolean isInstanceOf() {
return myRelation == INSTANCEOF_KEYWORD;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -60,6 +60,7 @@ public class DfaValueFactory {
myRelationFactory = new DfaRelationValue.Factory(this);
myExpressionFactory = new DfaExpressionFactory(this);
myOptionalFactory = new DfaOptionalValue.Factory(this);
myRangeFactory = new DfaRangeValue.Factory(this);
}
public boolean isHonorFieldInitializers() {
@@ -137,6 +138,7 @@ public class DfaValueFactory {
private final DfaRelationValue.Factory myRelationFactory;
private final DfaExpressionFactory myExpressionFactory;
private final DfaOptionalValue.Factory myOptionalFactory;
private final DfaRangeValue.Factory myRangeFactory;
@NotNull
public DfaVariableValue.Factory getVarFactory() {
@@ -166,4 +168,9 @@ public class DfaValueFactory {
public DfaOptionalValue.Factory getOptionalFactory() {
return myOptionalFactory;
}
@NotNull
public DfaRangeValue.Factory getRangeFactory() {
return myRangeFactory;
}
}
@@ -0,0 +1,91 @@
public class LongRangeBasics {
void testSwitch(int i) {
switch (i) {
case 0:
System.out.println("0");
break;
case 1:
System.out.println("1");
return;
case 2:
System.out.println("2");
return;
default:
System.out.println("default");
break;
}
if(i == 0) {
System.out.println("ouch");
}
// i > 0 and i < 3 means (i == 1 || i == 2); in both cases we already returned
if(<warning descr="Condition 'i > 0 && i < 3' is always 'false'">i > 0 && <warning descr="Condition 'i < 3' is always 'false' when reached">i < 3</warning></warning>) {
System.out.println("oops");
}
}
void test(int i) {
if(i > 5) {
if(<warning descr="Condition 'i < 0' is always 'false'">i < 0</warning>) {
System.out.println("Hello");
}
}
}
void test2(char c) {
int i = c;
if(<warning descr="Condition 'i > 0x10000' is always 'false'">i > 0x10000</warning>) {
System.out.println("Hello");
}
}
void test3(String s) {
int i = s.charAt(0);
if(<warning descr="Condition 'i > 0x10000' is always 'false'">i > 0x10000</warning>) {
System.out.println("Hello");
}
}
void test4(String s) {
if(<warning descr="Condition 's.charAt(0) < 0x10000' is always 'true'">s.charAt(0) < 0x10000</warning>) {
System.out.println("Hello");
}
}
void test1(int i, int j) {
if(i > 0 && j > i) {
// j > i which is > 0 means that j >= 2
if(<warning descr="Condition 'j == 1' is always 'false'">j == 1</warning>) {
if(i < 0) {
System.out.println("oops");
}
}
}
}
void testLength(String s) {
if(s.length() < 2) {
if(<warning descr="Condition 's.length() > 4' is always 'false'">s.length() > 4</warning>) {
System.out.println("Never");
}
if(s.length() == 1) {
System.out.println("One");
} else if(<warning descr="Condition 's.length() == 0' is always 'true'">s.length() == 0</warning>) {
System.out.println("Empty");
}
}
if(<warning descr="Condition 's.length() < 0' is always 'false'">s.length() < 0</warning>) {
System.out.println("Never");
}
}
void testArrayLength(int[] arr) {
if (arr.length > 0) {
System.out.println("Ok");
} else if(<warning descr="Condition 'arr.length == 0' is always 'true'">arr.length == 0</warning>) {
System.out.println("Empty");
}
if (<warning descr="Condition 'arr.length < 0' is always 'false'">arr.length < 0</warning>) {
System.out.println("Impossible");
}
}
}
@@ -0,0 +1,18 @@
import java.time.LocalDateTime;
public class LongRangeKnownMethods {
void testIndexOf(String s) {
int idx = s.indexOf("xyz");
if(idx >= 0) {
System.out.println("Found");
} else if(<warning descr="Condition 'idx == -1' is always 'true'">idx == -1</warning>) {
System.out.println("Not found");
}
}
void testLocalDateTime(LocalDateTime ldt) {
if(<warning descr="Condition 'ldt.getHour() == 24' is always 'false'">ldt.getHour() == 24</warning>) System.out.println(1);
if(<warning descr="Condition 'ldt.getMinute() >= 0' is always 'true'">ldt.getMinute() >= 0</warning>) System.out.println(2);
if(<warning descr="Condition 'ldt.getSecond() >= 60' is always 'false'">ldt.getSecond() >= 60</warning>) System.out.println(3);
}
}
@@ -0,0 +1,36 @@
import java.util.Collection;
public class LongRangeLoop {
public static int min(int[] values) {
int min = Integer.MAX_VALUE;
for (int value : values) {
if (value < min) min = value;
}
if (<warning descr="Condition 'min > 0 && min < -10' is always 'false'">min > 0 && <warning descr="Condition 'min < -10' is always 'false' when reached">min < -10</warning></warning>) {
System.out.println("Invalid result");
}
return min;
}
public static void boundedForLoop() {
for(int i=0; i<10; i++) {
if(<warning descr="Condition 'i == 20' is always 'false'">i == 20</warning>) {
System.out.println("Oops");
}
}
}
public void testVariablesAreInDeclarationOrder(String file, Collection<String> vars) throws Exception {
int previousIndex = -1;
for (String each : vars) {
int index = file.indexOf(each);
if(index <= previousIndex) {
throw new AssertionError();
}
previousIndex = index;
if(<warning descr="Condition 'index == -1' is always 'false'">index == -1</warning>) {
System.out.println("Impossible");
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -33,6 +33,7 @@ public class DataFlowInspectionTestSuite {
suite.addTestSuite(DataFlowInspectionAncientTest.class);
suite.addTestSuite(ContractCheckTest.class);
suite.addTestSuite(HardcodedContractsTest.class);
suite.addTestSuite(DataFlowRangeAnalysisTest.class);
suite.addTestSuite(ContractInferenceFromSourceTest.class);
suite.addTestSuite(NullityInferenceFromSourceTestCase.DfaInferenceTest.class);
@@ -0,0 +1,63 @@
/*
* Copyright 2000-2017 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.
*/
package com.intellij.codeInspection;
import com.intellij.JavaTestUtil;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
/**
* @author Tagir Valeev
*/
public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase {
private static final DefaultLightProjectDescriptor PROJECT_DESCRIPTOR = new DefaultLightProjectDescriptor() {
@Override
public Sdk getSdk() {
return PsiTestUtil.addJdkAnnotations(IdeaTestUtil.getMockJdk18());
}
};
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return PROJECT_DESCRIPTOR;
}
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath() + "/inspection/dataFlow/fixture/";
}
public void testLongRangeBasics() { doTest(); }
public void testLongRangeLoop() { doTest(); }
public void testLongRangeKnownMethods() {
myFixture.addClass("package java.time;\n" +
"\n" +
"public interface LocalDateTime {\n" +
" int getHour();\n" +
" int getMinute();\n" +
" int getSecond();\n" +
"}");
doTest();
}
}
@@ -0,0 +1,245 @@
/*
* Copyright 2000-2017 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.
*/
package com.intellij.codeInspection.dataFlow.rangeSet;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiType;
import com.intellij.util.containers.HashMap;
import org.junit.Test;
import java.util.Random;
import static com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet.*;
import static org.junit.Assert.*;
/**
* @author Tagir Valeev
*/
public class LongRangeSetTest {
@Test
public void testToString() {
assertEquals("{}", LongRangeSet.empty().toString());
assertEquals("{10}", point(10).toString());
assertEquals("{10}", range(10, 10).toString());
assertEquals("{10, 11}", range(10, 11).toString());
assertEquals("{10..100}", range(10, 100).toString());
}
@Test
public void testFromType() {
assertNull(LongRangeSet.fromType(PsiType.FLOAT));
assertNull(LongRangeSet.fromType(PsiType.NULL));
assertEquals("{-128..127}", LongRangeSet.fromType(PsiType.BYTE).toString());
assertEquals("{0..65535}", LongRangeSet.fromType(PsiType.CHAR).toString());
assertEquals("{-32768..32767}", LongRangeSet.fromType(PsiType.SHORT).toString());
assertEquals("{-2147483648..2147483647}", LongRangeSet.fromType(PsiType.INT).toString());
assertEquals("{0..2147483647}", LongRangeSet.indexRange().toString());
assertEquals("{-9223372036854775808..9223372036854775807}", LongRangeSet.fromType(PsiType.LONG).toString());
}
@Test
public void testEquals() {
assertEquals(LongRangeSet.empty(), LongRangeSet.empty());
assertEquals(point(10), point(10));
assertNotEquals(point(10), point(11));
assertEquals(point(10), range(10, 10));
assertNotEquals(point(10), range(10, 11));
assertEquals(range(10, 11), range(10, 11));
assertNotEquals(range(10, 11), range(10, 12));
}
@Test
public void testDiff() {
assertEquals(LongRangeSet.empty(), LongRangeSet.empty().subtract(point(10)));
assertEquals(point(10), point(10).subtract(LongRangeSet.empty()));
assertEquals(point(10), point(10).subtract(point(11)));
assertEquals(LongRangeSet.empty(), point(10).subtract(point(10)));
assertEquals(point(10), point(10).subtract(range(15, 20)));
assertEquals(point(10), point(10).subtract(range(-10, -5)));
assertTrue(point(10).subtract(range(10, 20)).isEmpty());
assertTrue(point(10).subtract(range(-10, 20)).isEmpty());
assertTrue(point(10).subtract(range(-10, 10)).isEmpty());
assertEquals("{0..20}", range(0, 20).lt(30).toString());
assertEquals("{0..19}", range(0, 20).lt(20).toString());
assertEquals("{0..18}", range(0, 20).lt(19).toString());
assertEquals("{0}", range(0, 20).lt(1).toString());
assertTrue(range(0, 20).lt(0).isEmpty());
LongRangeSet fullRange = range(Long.MIN_VALUE, Long.MAX_VALUE);
assertEquals("{-9223372036854775808}", fullRange.le(Long.MIN_VALUE).toString());
assertEquals(fullRange, fullRange.le(Long.MAX_VALUE));
assertEquals("{9223372036854775807}", fullRange.ge(Long.MAX_VALUE).toString());
assertEquals(fullRange, fullRange.ge(Long.MIN_VALUE));
assertTrue(fullRange.gt(Long.MAX_VALUE).isEmpty());
assertEquals(LongRangeSet.indexRange(), LongRangeSet.fromType(PsiType.INT).gt(-1));
assertTrue(fullRange.subtract(fullRange).isEmpty());
assertEquals(point(10), fullRange.eq(10));
assertTrue(range(30, 50).eq(10).isEmpty());
}
@Test
public void testSets() {
assertEquals("{0..9, 11..20}", range(0, 20).ne(10).toString());
assertEquals("{0, 20}", range(0, 20).subtract(range(1, 19)).toString());
assertEquals("{0, 1, 19, 20}", range(0, 20).subtract(range(2, 18)).toString());
assertEquals("{0..9, 12..20}", range(0, 20).ne(10).ne(11).toString());
assertEquals("{0..9, 12..14, 16..20}", range(0, 20).ne(10).ne(11).ne(15).toString());
assertEquals("{0, 4..20}", range(0, 20).ne(3).ne(2).ne(1).toString());
assertEquals("{4..20}", range(0, 20).ne(3).ne(2).ne(1).ne(0).toString());
assertEquals("{0..2, 5..15, 19, 20}",
range(0, 20).subtract(range(3, 18).subtract(range(5, 15))).toString());
LongRangeSet first = fromType(PsiType.CHAR).ne(45);
LongRangeSet second = fromType(PsiType.CHAR).ne(32).ne(40).ne(44).ne(45).ne(46).ne(58).ne(59).ne(61);
assertEquals("{0..44, 46..65535}", first.toString());
assertEquals("{0..31, 33..39, 41..43, 47..57, 60, 62..65535}", second.toString());
assertEquals("{32, 40, 44, 46, 58, 59, 61}", first.subtract(second).toString());
}
@Test
public void testHash() {
HashMap<LongRangeSet, String> map = new HashMap<>();
map.put(LongRangeSet.empty(), "empty");
map.put(point(10), "10");
map.put(range(10, 10), "10-10");
map.put(range(10, 11), "10-11");
map.put(range(10, 12), "10-12");
LongRangeSet longNotChar = LongRangeSet.fromType(PsiType.LONG).subtract(LongRangeSet.fromType(PsiType.CHAR));
map.put(longNotChar, "longNotChar");
assertEquals("empty", map.get(LongRangeSet.empty()));
assertEquals("10-10", map.get(point(10)));
assertEquals("10-11", map.get(range(10, 11)));
assertEquals("10-12", map.get(range(10, 12)));
assertNull(map.get(range(11, 11)));
assertEquals("longNotChar", map.get(LongRangeSet.fromType(PsiType.LONG).subtract(LongRangeSet.fromType(PsiType.CHAR))));
}
@Test
public void testIntersects() {
assertFalse(LongRangeSet.empty().intersects(LongRangeSet.fromType(PsiType.LONG)));
assertTrue(point(Long.MIN_VALUE).intersects(LongRangeSet.fromType(PsiType.LONG)));
assertFalse(point(10).intersects(point(11)));
assertTrue(point(10).intersects(point(10)));
assertTrue(range(10, 100).intersects(point(10)));
assertTrue(range(10, 100).intersects(point(100)));
assertFalse(range(10, 100).intersects(point(101)));
assertFalse(range(10, 100).intersects(point(9)));
LongRangeSet range1020 = range(10, 20);
assertTrue(range1020.intersects(range1020));
assertTrue(range1020.intersects(range(10, 30)));
assertTrue(range1020.intersects(range(20, 30)));
assertTrue(range1020.intersects(range(0, 30)));
assertTrue(range1020.intersects(range(0, 10)));
assertTrue(range1020.intersects(range(0, 20)));
assertFalse(range1020.intersects(range(0, 9)));
assertFalse(range1020.intersects(range(21, 30)));
LongRangeSet rangeSet = range1020.subtract(range(12, 13)).subtract(range(17, 18));
assertFalse(rangeSet.intersects(point(12)));
assertFalse(point(12).intersects(rangeSet));
assertFalse(rangeSet.intersects(LongRangeSet.empty()));
assertFalse(rangeSet.intersects(range(12, 13)));
assertFalse(range(12, 13).intersects(rangeSet));
assertFalse(rangeSet.intersects(range(0, 9)));
assertFalse(rangeSet.intersects(range(21, 30)));
assertTrue(rangeSet.intersects(rangeSet));
assertTrue(rangeSet.intersects(range1020));
assertTrue(rangeSet.intersects(point(11)));
LongRangeSet rangeSet2 = range1020.subtract(rangeSet);
assertEquals("{12, 13, 17, 18}", rangeSet2.toString());
assertFalse(rangeSet.intersects(rangeSet2));
}
@Test
public void testIntersect() {
assertEquals("{0..100}", range(0, 100).intersect(range(0, 100)).toString());
assertEquals("{100}", range(0, 100).intersect(range(100, 200)).toString());
assertTrue(range(0, 100).intersect(range(101, 200)).isEmpty());
assertTrue(point(100).intersect(point(200)).isEmpty());
assertFalse(point(100).intersect(range(99, 101)).isEmpty());
LongRangeSet rangeSet = range(-1000, 1000).subtract(range(100, 500)).subtract(range(-500, -100));
assertEquals("{-1000..-501, -99..99, 501..1000}", rangeSet.toString());
assertEquals(point(99), rangeSet.intersect(point(99)));
assertTrue(rangeSet.intersect(point(100)).isEmpty());
assertEquals("{0..99, 501..1000}", rangeSet.intersect(LongRangeSet.indexRange()).toString());
}
@Test
public void testIntersectSubtractRandomized() {
Random r = new Random(1);
LongRangeSet[] data = r.ints(1000, 0, 1000)
.mapToObj(x -> range(x, x + r.nextInt((x % 20) * 100 + 1))).toArray(LongRangeSet[]::new);
for (int i = 0; i < 2000; i++) {
int idx = r.nextInt(data.length);
LongRangeSet left = data[idx];
LongRangeSet right = data[r.nextInt(data.length)];
LongRangeSet lDiff = left.subtract(right);
LongRangeSet rDiff = right.subtract(left);
LongRangeSet intersection = left.intersect(right);
String message = left + " & " + right + " = " + intersection;
assertEquals(message, intersection, right.intersect(left));
if (!intersection.isEmpty()) {
assertTrue(message, intersection.min() >= Math.max(left.min(), right.min()));
assertTrue(message, intersection.max() <= Math.min(left.max(), right.max()));
}
assertEquals(message, intersection, right.subtract(LongRangeSet.fromType(PsiType.LONG).subtract(left)));
assertEquals(message, intersection, left.subtract(LongRangeSet.fromType(PsiType.LONG).subtract(right)));
switch (r.nextInt(3)) {
case 0:
data[idx] = lDiff;
break;
case 1:
data[idx] = rDiff;
break;
case 2:
data[idx] = intersection;
break;
}
}
}
@Test
public void testFromConstant() {
assertEquals("{0}", LongRangeSet.fromConstant(0).toString());
assertEquals("{0}", LongRangeSet.fromConstant(0L).toString());
assertEquals("{1}", LongRangeSet.fromConstant((byte)1).toString());
assertEquals("{97}", LongRangeSet.fromConstant('a').toString());
assertNull(LongRangeSet.fromConstant(null));
assertNull(LongRangeSet.fromConstant(1.0));
}
@Test
public void testFromRelation() {
assertEquals(range(101, Long.MAX_VALUE), range(100, 200).fromRelation(JavaTokenType.GT));
assertEquals(range(100, Long.MAX_VALUE), range(100, 200).fromRelation(JavaTokenType.GE));
assertEquals(range(Long.MIN_VALUE, 199), range(100, 200).fromRelation(JavaTokenType.LT));
assertEquals(range(Long.MIN_VALUE, 200), range(100, 200).fromRelation(JavaTokenType.LE));
assertEquals(range(100, 200), range(100, 200).fromRelation(JavaTokenType.EQEQ));
assertNull(range(100, 200).fromRelation(JavaTokenType.EQ));
assertEquals(fromType(PsiType.LONG), range(100, 200).fromRelation(JavaTokenType.NE));
assertEquals("{-9223372036854775808..99, 101..9223372036854775807}", point(100).fromRelation(JavaTokenType.NE).toString());
}
}
@@ -89,6 +89,17 @@ public class ComparisonUtils {
return s_invertedComparisons.get(tokenType);
}
@Nullable
public static IElementType getNegatedComparisonTokenType(IElementType tokenType) {
if(JavaTokenType.EQEQ.equals(tokenType)) return JavaTokenType.NE;
if(JavaTokenType.NE.equals(tokenType)) return JavaTokenType.EQEQ;
if(JavaTokenType.LT.equals(tokenType)) return JavaTokenType.GE;
if(JavaTokenType.LE.equals(tokenType)) return JavaTokenType.GT;
if(JavaTokenType.GT.equals(tokenType)) return JavaTokenType.LE;
if(JavaTokenType.GE.equals(tokenType)) return JavaTokenType.LT;
return null;
}
@Contract("null, _, _ -> false")
public static boolean isNullComparison(PsiExpression expression, @NotNull PsiVariable variable, boolean equal) {
return variable.equals(ExpressionUtils.getVariableFromNullComparison(expression, equal));