mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-20 02:15:59 +07:00
IDEA-169503 Integral range tracking for variables inside dataflow analysis
This commit is contained in:
+16
-3
@@ -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,
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+99
-6
@@ -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();
|
||||
|
||||
+62
-20
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+73
-47
@@ -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();
|
||||
|
||||
+12
-7
@@ -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
|
||||
|
||||
+638
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -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;
|
||||
}
|
||||
|
||||
+70
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-1
@@ -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;
|
||||
}
|
||||
|
||||
+8
-1
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+245
@@ -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());
|
||||
}
|
||||
}
|
||||
+11
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user