mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-01 19:02:13 +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) {
|
if (instruction instanceof BranchingInstruction) {
|
||||||
BranchingInstruction branching = (BranchingInstruction)instruction;
|
BranchingInstruction branching = (BranchingInstruction)instruction;
|
||||||
Collection<DfaMemoryState> processed = processedStates.get(branching);
|
Collection<DfaMemoryState> processed = processedStates.get(branching);
|
||||||
if (processed.contains(instructionState.getMemoryState())) {
|
if (containsState(processed, instructionState)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (processed.size() > MAX_STATES_PER_BRANCH) {
|
if (processed.size() > MAX_STATES_PER_BRANCH) {
|
||||||
@@ -198,8 +198,8 @@ public class DataFlowRunner {
|
|||||||
handleStepOutOfLoop(instruction, nextInstruction, loopNumber, processedStates, incomingStates, states, after, queue);
|
handleStepOutOfLoop(instruction, nextInstruction, loopNumber, processedStates, incomingStates, states, after, queue);
|
||||||
if (nextInstruction instanceof BranchingInstruction) {
|
if (nextInstruction instanceof BranchingInstruction) {
|
||||||
BranchingInstruction branching = (BranchingInstruction)nextInstruction;
|
BranchingInstruction branching = (BranchingInstruction)nextInstruction;
|
||||||
if (processedStates.get(branching).contains(state.getMemoryState()) ||
|
if (containsState(processedStates.get(branching), state) ||
|
||||||
incomingStates.get(branching).contains(state.getMemoryState())) {
|
containsState(incomingStates.get(branching), state)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (loopNumber[branching.getIndex()] != 0) {
|
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,
|
private void handleStepOutOfLoop(@NotNull final Instruction prevInstruction,
|
||||||
@NotNull Instruction nextInstruction,
|
@NotNull Instruction nextInstruction,
|
||||||
@NotNull final int[] loopNumber,
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.codeInspection.dataFlow.instructions.Instruction;
|
||||||
import com.intellij.openapi.util.Pair;
|
import com.intellij.openapi.util.Pair;
|
||||||
import com.intellij.util.Function;
|
|
||||||
import com.intellij.util.Processor;
|
import com.intellij.util.Processor;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
import com.intellij.util.containers.MultiMap;
|
import com.intellij.util.containers.MultiMap;
|
||||||
@@ -125,7 +124,8 @@ class StateQueue {
|
|||||||
|
|
||||||
StateMerger merger = new StateMerger();
|
StateMerger merger = new StateMerger();
|
||||||
while (true) {
|
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.mergeByNullability(group);
|
||||||
if (nextStates == null) nextStates = merger.mergeByUnknowns(group);
|
if (nextStates == null) nextStates = merger.mergeByUnknowns(group);
|
||||||
if (nextStates == null) break;
|
if (nextStates == null) break;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.intellij.codeInspection.dataFlow;
|
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.DfaConstValue;
|
||||||
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue;
|
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue;
|
||||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||||
@@ -53,6 +54,9 @@ public interface DfaMemoryState {
|
|||||||
|
|
||||||
ThreeState checkOptional(DfaValue value);
|
ThreeState checkOptional(DfaValue value);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
LongRangeSet getRange(DfaValue value);
|
||||||
|
|
||||||
void flushFields();
|
void flushFields();
|
||||||
|
|
||||||
void flushVariable(DfaVariableValue variable);
|
void flushVariable(DfaVariableValue variable);
|
||||||
|
|||||||
+99
-6
@@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.intellij.codeInspection.dataFlow;
|
package com.intellij.codeInspection.dataFlow;
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||||
import com.intellij.codeInspection.dataFlow.value.*;
|
import com.intellij.codeInspection.dataFlow.value.*;
|
||||||
import com.intellij.openapi.progress.ProgressManager;
|
import com.intellij.openapi.progress.ProgressManager;
|
||||||
import com.intellij.openapi.util.Pair;
|
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.ContainerUtil;
|
||||||
import com.intellij.util.containers.Stack;
|
import com.intellij.util.containers.Stack;
|
||||||
import gnu.trove.*;
|
import gnu.trove.*;
|
||||||
|
import one.util.streamex.StreamEx;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -207,12 +209,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
|||||||
|
|
||||||
if (!myDistinctClasses.isEmpty()) {
|
if (!myDistinctClasses.isEmpty()) {
|
||||||
result.append("\n distincts: ");
|
result.append("\n distincts: ");
|
||||||
List<String> distincts = new ArrayList<>();
|
String distincts =
|
||||||
for (UnorderedPair<EqClass> pair : getDistinctClassPairs()) {
|
StreamEx.of(getDistinctClassPairs()).map(pair -> "{" + pair.first + ", " + pair.second + "}").sorted().joining(" ");
|
||||||
distincts.add("{" + pair.first + ", " + pair.second + "}");
|
result.append(distincts);
|
||||||
}
|
|
||||||
Collections.sort(distincts);
|
|
||||||
result.append(StringUtil.join(distincts, " "));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!myStack.isEmpty()) {
|
if (!myStack.isEmpty()) {
|
||||||
@@ -361,6 +360,28 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
|||||||
myIdToEqClassesIndices.remove(id);
|
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) {
|
private static boolean canBeInRelation(@NotNull DfaValue dfaValue) {
|
||||||
DfaValue unwrapped = unwrap(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) {
|
static DfaValue unwrap(DfaValue value) {
|
||||||
if (value instanceof DfaBoxedValue) {
|
if (value instanceof DfaBoxedValue) {
|
||||||
return ((DfaBoxedValue)value).getWrappedValue();
|
return ((DfaBoxedValue)value).getWrappedValue();
|
||||||
@@ -683,6 +725,25 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
|||||||
if (dfaLeft instanceof DfaUnknownValue || dfaRight instanceof DfaUnknownValue) return true;
|
if (dfaLeft instanceof DfaUnknownValue || dfaRight instanceof DfaUnknownValue) return true;
|
||||||
|
|
||||||
boolean isNegated = dfaRelation.isNegated();
|
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()) {
|
if (dfaLeft instanceof DfaTypeValue && ((DfaTypeValue)dfaLeft).isNotNull() && dfaRight == myFactory.getConstFactory().getNull()) {
|
||||||
return isNegated;
|
return isNegated;
|
||||||
}
|
}
|
||||||
@@ -946,6 +1007,38 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
|||||||
return value instanceof DfaOptionalValue ? ThreeState.fromBoolean(((DfaOptionalValue)value).isPresent()) : ThreeState.UNSURE;
|
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
|
@Nullable
|
||||||
private DfaRelationValue compareToNull(DfaValue dfaVar, boolean negated) {
|
private DfaRelationValue compareToNull(DfaValue dfaVar, boolean negated) {
|
||||||
DfaConstValue dfaNull = myFactory.getConstFactory().getNull();
|
DfaConstValue dfaNull = myFactory.getConstFactory().getNull();
|
||||||
|
|||||||
+62
-20
@@ -24,44 +24,69 @@
|
|||||||
*/
|
*/
|
||||||
package com.intellij.codeInspection.dataFlow;
|
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.DfaPsiType;
|
||||||
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
|
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
|
||||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||||
import com.intellij.openapi.util.text.StringUtil;
|
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.ThreeState;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
|
import com.siyeh.ig.psiutils.MethodUtils;
|
||||||
import org.jetbrains.annotations.NonNls;
|
import org.jetbrains.annotations.NonNls;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
class DfaVariableState {
|
class DfaVariableState {
|
||||||
@NotNull final Set<DfaPsiType> myInstanceofValues;
|
@NotNull final Set<DfaPsiType> myInstanceofValues;
|
||||||
@NotNull final Set<DfaPsiType> myNotInstanceofValues;
|
@NotNull final Set<DfaPsiType> myNotInstanceofValues;
|
||||||
@NotNull final Nullness myNullability;
|
@NotNull final Nullness myNullability;
|
||||||
@NotNull final ThreeState myOptionalPresence;
|
@NotNull final ThreeState myOptionalPresence;
|
||||||
|
@Nullable final LongRangeSet myRange;
|
||||||
private final int myHash;
|
private final int myHash;
|
||||||
|
|
||||||
DfaVariableState(@NotNull DfaVariableValue dfaVar) {
|
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,
|
DfaVariableState(@NotNull Set<DfaPsiType> instanceofValues,
|
||||||
@NotNull Set<DfaPsiType> notInstanceofValues,
|
@NotNull Set<DfaPsiType> notInstanceofValues,
|
||||||
@NotNull Nullness nullability,
|
@NotNull Nullness nullability,
|
||||||
@NotNull ThreeState optionalPresence) {
|
@NotNull ThreeState optionalPresence,
|
||||||
|
@Nullable LongRangeSet range) {
|
||||||
myInstanceofValues = instanceofValues;
|
myInstanceofValues = instanceofValues;
|
||||||
myNotInstanceofValues = notInstanceofValues;
|
myNotInstanceofValues = notInstanceofValues;
|
||||||
myNullability = nullability;
|
myNullability = nullability;
|
||||||
myOptionalPresence = optionalPresence;
|
myOptionalPresence = optionalPresence;
|
||||||
myHash = ((myInstanceofValues.hashCode() * 31 + myNotInstanceofValues.hashCode()) * 31 + myNullability.hashCode()) * 31 +
|
myRange = range;
|
||||||
myOptionalPresence.hashCode();
|
myHash = Objects.hash(myInstanceofValues, myNotInstanceofValues, myNullability, myOptionalPresence, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNullable() {
|
public boolean isNullable() {
|
||||||
@@ -101,7 +126,7 @@ class DfaVariableState {
|
|||||||
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
|
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
|
||||||
newInstanceof.removeAll(moreGeneric);
|
newInstanceof.removeAll(moreGeneric);
|
||||||
newInstanceof.add(dfaType.getDfaType());
|
newInstanceof.add(dfaType.getDfaType());
|
||||||
result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability, myOptionalPresence);
|
result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability, myOptionalPresence, myRange);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +154,7 @@ class DfaVariableState {
|
|||||||
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
|
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
|
||||||
newNotInstanceof.removeAll(moreSpecific);
|
newNotInstanceof.removeAll(moreSpecific);
|
||||||
newNotInstanceof.add(dfaType.getDfaType());
|
newNotInstanceof.add(dfaType.getDfaType());
|
||||||
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence);
|
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence, myRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -137,12 +162,12 @@ class DfaVariableState {
|
|||||||
if (myInstanceofValues.contains(type)) {
|
if (myInstanceofValues.contains(type)) {
|
||||||
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
|
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
|
||||||
newInstanceof.remove(type);
|
newInstanceof.remove(type);
|
||||||
return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myOptionalPresence);
|
return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myOptionalPresence, myRange);
|
||||||
}
|
}
|
||||||
if (myNotInstanceofValues.contains(type)) {
|
if (myNotInstanceofValues.contains(type)) {
|
||||||
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
|
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
|
||||||
newNotInstanceof.remove(type);
|
newNotInstanceof.remove(type);
|
||||||
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence);
|
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence, myRange);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -159,14 +184,17 @@ class DfaVariableState {
|
|||||||
myNullability == aState.myNullability &&
|
myNullability == aState.myNullability &&
|
||||||
myOptionalPresence == aState.myOptionalPresence &&
|
myOptionalPresence == aState.myOptionalPresence &&
|
||||||
myInstanceofValues.equals(aState.myInstanceofValues) &&
|
myInstanceofValues.equals(aState.myInstanceofValues) &&
|
||||||
myNotInstanceofValues.equals(aState.myNotInstanceofValues);
|
myNotInstanceofValues.equals(aState.myNotInstanceofValues) &&
|
||||||
|
Objects.equals(myRange, aState.myRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
|
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
|
||||||
@NotNull Set<DfaPsiType> notInstanceofValues,
|
@NotNull Set<DfaPsiType> notInstanceofValues,
|
||||||
@NotNull Nullness nullability, ThreeState optionalPresent) {
|
@NotNull Nullness nullability,
|
||||||
return new DfaVariableState(instanceofValues, notInstanceofValues, nullability, optionalPresent);
|
ThreeState optionalPresent,
|
||||||
|
LongRangeSet range) {
|
||||||
|
return new DfaVariableState(instanceofValues, notInstanceofValues, nullability, optionalPresent, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
@@ -184,6 +212,9 @@ class DfaVariableState {
|
|||||||
if (myOptionalPresence != ThreeState.UNSURE) {
|
if (myOptionalPresence != ThreeState.UNSURE) {
|
||||||
buf.append(myOptionalPresence == ThreeState.YES ? " Optional with value" : " empty Optional");
|
buf.append(myOptionalPresence == ThreeState.YES ? " Optional with value" : " empty Optional");
|
||||||
}
|
}
|
||||||
|
if (myRange != null) {
|
||||||
|
buf.append(" ").append(myRange);
|
||||||
|
}
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +229,7 @@ class DfaVariableState {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
DfaVariableState withNullability(@NotNull Nullness nullness) {
|
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
|
@NotNull
|
||||||
@@ -209,10 +240,16 @@ class DfaVariableState {
|
|||||||
DfaVariableState withOptionalPresense(final boolean presense) {
|
DfaVariableState withOptionalPresense(final boolean presense) {
|
||||||
ThreeState optionalPresent = ThreeState.fromBoolean(presense);
|
ThreeState optionalPresent = ThreeState.fromBoolean(presense);
|
||||||
return myOptionalPresence != optionalPresent
|
return myOptionalPresence != optionalPresent
|
||||||
? createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, optionalPresent)
|
? createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, optionalPresent, myRange)
|
||||||
: this;
|
: this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DfaVariableState withRange(@Nullable LongRangeSet range) {
|
||||||
|
return Objects.equals(range, myRange)
|
||||||
|
? this
|
||||||
|
: createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, myOptionalPresence, range);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public DfaVariableState withValue(DfaValue value) {
|
public DfaVariableState withValue(DfaValue value) {
|
||||||
return this;
|
return this;
|
||||||
@@ -234,4 +271,9 @@ class DfaVariableState {
|
|||||||
public ThreeState getOptionalPresense() {
|
public ThreeState getOptionalPresense() {
|
||||||
return myOptionalPresence;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -130,7 +130,7 @@ class NullParameterConstraintChecker extends DataFlowRunner {
|
|||||||
super(factory);
|
super(factory);
|
||||||
for (PsiParameter parameter : myPossiblyViolatedParameters) {
|
for (PsiParameter parameter : myPossiblyViolatedParameters) {
|
||||||
setVariableState(getFactory().getVarFactory().createVariableValue(parameter, false),
|
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;
|
package com.intellij.codeInspection.dataFlow;
|
||||||
|
|
||||||
import com.intellij.codeInspection.dataFlow.instructions.*;
|
import com.intellij.codeInspection.dataFlow.instructions.*;
|
||||||
|
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||||
import com.intellij.codeInspection.dataFlow.value.*;
|
import com.intellij.codeInspection.dataFlow.value.*;
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.util.Pair;
|
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.ContainerUtil;
|
||||||
import com.intellij.util.containers.FactoryMap;
|
import com.intellij.util.containers.FactoryMap;
|
||||||
import com.intellij.util.containers.MultiMap;
|
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 com.siyeh.ig.psiutils.TypeUtils;
|
||||||
import gnu.trove.THashSet;
|
import gnu.trove.THashSet;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -42,11 +46,18 @@ import static com.intellij.psi.JavaTokenType.*;
|
|||||||
* @author peter
|
* @author peter
|
||||||
*/
|
*/
|
||||||
public class StandardInstructionVisitor extends InstructionVisitor {
|
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 =
|
private static final Set<String> OPTIONAL_METHOD_NAMES =
|
||||||
ContainerUtil.set("isPresent", "of", "ofNullable", "fromNullable", "empty", "absent",
|
ContainerUtil.set("isPresent", "of", "ofNullable", "fromNullable", "empty", "absent",
|
||||||
"or", "orElseGet", "ifPresent", "map", "flatMap", "filter", "transform");
|
"or", "orElseGet", "ifPresent", "map", "flatMap", "filter", "transform");
|
||||||
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.StandardInstructionVisitor");
|
private static final CallMapper<LongRangeSet> KNOWN_METHOD_RANGES = new CallMapper<LongRangeSet>()
|
||||||
private static final Object ANY_VALUE = new Object();
|
.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> myReachable = new THashSet<>();
|
||||||
private final Set<BinopInstruction> myCanBeNullInInstanceof = new THashSet<>();
|
private final Set<BinopInstruction> myCanBeNullInInstanceof = new THashSet<>();
|
||||||
private final MultiMap<PushInstruction, Object> myPossibleVariableValues = MultiMap.createSet();
|
private final MultiMap<PushInstruction, Object> myPossibleVariableValues = MultiMap.createSet();
|
||||||
@@ -451,6 +462,17 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
|||||||
}
|
}
|
||||||
return factory.createTypeValue(type, nullability);
|
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();
|
return DfaUnknownValue.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,6 +498,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
|||||||
final IElementType opSign = instruction.getOperationSign();
|
final IElementType opSign = instruction.getOperationSign();
|
||||||
if (opSign != null) {
|
if (opSign != null) {
|
||||||
DfaInstructionState[] states = handleConstantComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign);
|
DfaInstructionState[] states = handleConstantComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign);
|
||||||
|
if (states == null) {
|
||||||
|
states = handleRangeComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign);
|
||||||
|
}
|
||||||
if (states == null) {
|
if (states == null) {
|
||||||
states = handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft);
|
states = handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft);
|
||||||
}
|
}
|
||||||
@@ -557,6 +582,27 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
|||||||
myUsefulInstanceofs.add(instruction);
|
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
|
@Nullable
|
||||||
private static DfaInstructionState[] handleConstantComparison(BinopInstruction instruction,
|
private static DfaInstructionState[] handleConstantComparison(BinopInstruction instruction,
|
||||||
DataFlowRunner runner,
|
DataFlowRunner runner,
|
||||||
@@ -608,26 +654,11 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
|||||||
DfaMemoryState memState,
|
DfaMemoryState memState,
|
||||||
DfaVariableValue var,
|
DfaVariableValue var,
|
||||||
IElementType opSign, Number comparedWith) {
|
IElementType opSign, Number comparedWith) {
|
||||||
Object knownValue = getKnownNumberValue(memState, var);
|
Number knownValue = getKnownNumberValue(memState, var);
|
||||||
if (knownValue != null) {
|
if (knownValue != null) {
|
||||||
return checkComparisonWithKnownValue(instruction, runner, memState, opSign, (Number)knownValue, comparedWith);
|
return checkComparisonWithKnownValue(instruction, runner, memState, opSign, knownValue, comparedWith);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -642,7 +673,28 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
|||||||
IElementType opSign,
|
IElementType opSign,
|
||||||
Number leftValue,
|
Number leftValue,
|
||||||
Number rightValue) {
|
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) {
|
private static int compare(Number a, Number b) {
|
||||||
@@ -653,32 +705,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
|||||||
return Double.compare(a.doubleValue(), b.doubleValue());
|
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) {
|
private static DfaInstructionState[] alwaysFalse(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||||
memState.push(runner.getFactory().getConstFactory().getFalse());
|
memState.push(runner.getFactory().getConstFactory().getFalse());
|
||||||
instruction.setFalseReachable();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -15,13 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package com.intellij.codeInspection.dataFlow;
|
package com.intellij.codeInspection.dataFlow;
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||||
import com.intellij.codeInspection.dataFlow.value.*;
|
import com.intellij.codeInspection.dataFlow.value.*;
|
||||||
import com.intellij.openapi.progress.ProgressManager;
|
import com.intellij.openapi.progress.ProgressManager;
|
||||||
import com.intellij.openapi.util.Condition;
|
|
||||||
import com.intellij.openapi.util.UnorderedPair;
|
import com.intellij.openapi.util.UnorderedPair;
|
||||||
import com.intellij.psi.JavaTokenType;
|
import com.intellij.psi.JavaTokenType;
|
||||||
import com.intellij.util.Function;
|
import com.intellij.util.Function;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
|
import com.intellij.util.containers.HashMap;
|
||||||
import com.intellij.util.containers.MultiMap;
|
import com.intellij.util.containers.MultiMap;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -32,6 +33,7 @@ import java.util.*;
|
|||||||
* @author peter
|
* @author peter
|
||||||
*/
|
*/
|
||||||
class StateMerger {
|
class StateMerger {
|
||||||
|
public static final int MAX_RANGE_STATES = 100;
|
||||||
private final Map<DfaMemoryStateImpl, Set<Fact>> myFacts = ContainerUtil.newIdentityHashMap();
|
private final Map<DfaMemoryStateImpl, Set<Fact>> myFacts = ContainerUtil.newIdentityHashMap();
|
||||||
private final Map<DfaMemoryState, Map<DfaVariableValue, DfaMemoryStateImpl>> myCopyCache = ContainerUtil.newIdentityHashMap();
|
private final Map<DfaMemoryState, Map<DfaVariableValue, DfaMemoryStateImpl>> myCopyCache = ContainerUtil.newIdentityHashMap();
|
||||||
|
|
||||||
@@ -203,6 +205,57 @@ class StateMerger {
|
|||||||
return replacements.getMergeResult();
|
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) {
|
private static boolean mergeUnknowns(@NotNull Replacements replacements, @NotNull List<DfaMemoryStateImpl> complementary) {
|
||||||
if (complementary.size() < 2) return false;
|
if (complementary.size() < 2) return false;
|
||||||
|
|
||||||
@@ -220,10 +273,7 @@ class StateMerger {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private DfaMemoryStateImpl copyWithoutVar(@NotNull DfaMemoryStateImpl state, @NotNull DfaVariableValue var) {
|
private DfaMemoryStateImpl copyWithoutVar(@NotNull DfaMemoryStateImpl state, @NotNull DfaVariableValue var) {
|
||||||
Map<DfaVariableValue, DfaMemoryStateImpl> map = myCopyCache.get(state);
|
Map<DfaVariableValue, DfaMemoryStateImpl> map = myCopyCache.computeIfAbsent(state, k -> ContainerUtil.newIdentityHashMap());
|
||||||
if (map == null) {
|
|
||||||
myCopyCache.put(state, map = ContainerUtil.newIdentityHashMap());
|
|
||||||
}
|
|
||||||
DfaMemoryStateImpl copy = map.get(var);
|
DfaMemoryStateImpl copy = map.get(var);
|
||||||
if (copy == null) {
|
if (copy == null) {
|
||||||
copy = state.createCopy();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.intellij.codeInspection.dataFlow;
|
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.DfaPsiType;
|
||||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||||
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
|
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
|
||||||
@@ -78,8 +79,10 @@ class ValuableDataFlowRunner extends DataFlowRunner {
|
|||||||
private ValuableDfaVariableState(Set<DfaPsiType> instanceofValues,
|
private ValuableDfaVariableState(Set<DfaPsiType> instanceofValues,
|
||||||
Set<DfaPsiType> notInstanceofValues,
|
Set<DfaPsiType> notInstanceofValues,
|
||||||
Nullness nullability, DfaValue value,
|
Nullness nullability, DfaValue value,
|
||||||
@NotNull FList<PsiExpression> concatenation, ThreeState optionalPresence) {
|
@NotNull FList<PsiExpression> concatenation,
|
||||||
super(instanceofValues, notInstanceofValues, nullability, optionalPresence);
|
ThreeState optionalPresence,
|
||||||
|
LongRangeSet range) {
|
||||||
|
super(instanceofValues, notInstanceofValues, nullability, optionalPresence, range);
|
||||||
myValue = value;
|
myValue = value;
|
||||||
myConcatenation = concatenation;
|
myConcatenation = concatenation;
|
||||||
}
|
}
|
||||||
@@ -89,8 +92,10 @@ class ValuableDataFlowRunner extends DataFlowRunner {
|
|||||||
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
|
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
|
||||||
@NotNull Set<DfaPsiType> notInstanceofValues,
|
@NotNull Set<DfaPsiType> notInstanceofValues,
|
||||||
@NotNull Nullness nullability,
|
@NotNull Nullness nullability,
|
||||||
ThreeState optionalPresence) {
|
ThreeState optionalPresence,
|
||||||
return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation, optionalPresence);
|
LongRangeSet range) {
|
||||||
|
return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation, optionalPresence,
|
||||||
|
range);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -98,13 +103,13 @@ class ValuableDataFlowRunner extends DataFlowRunner {
|
|||||||
public DfaVariableState withValue(@Nullable final DfaValue value) {
|
public DfaVariableState withValue(@Nullable final DfaValue value) {
|
||||||
if (value == myValue) return this;
|
if (value == myValue) return this;
|
||||||
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, value, myConcatenation,
|
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, value, myConcatenation,
|
||||||
myOptionalPresence);
|
myOptionalPresence, myRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
ValuableDfaVariableState withExpression(@NotNull final FList<PsiExpression> concatenation) {
|
ValuableDfaVariableState withExpression(@NotNull final FList<PsiExpression> concatenation) {
|
||||||
if (concatenation == myConcatenation) return this;
|
if (concatenation == myConcatenation) return this;
|
||||||
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation,
|
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation,
|
||||||
myOptionalPresence);
|
myOptionalPresence, myRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.PsiTreeUtil;
|
||||||
import com.intellij.psi.util.PsiUtil;
|
import com.intellij.psi.util.PsiUtil;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
|
import com.siyeh.ig.psiutils.MethodUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -174,6 +175,9 @@ public class DfaExpressionFactory {
|
|||||||
return method;
|
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) {
|
if (AnnotationUtil.findAnnotation(method.getContainingClass(), "javax.annotation.concurrent.Immutable") != null) {
|
||||||
return method;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 com.intellij.util.containers.HashMap;
|
||||||
import org.jetbrains.annotations.NonNls;
|
import org.jetbrains.annotations.NonNls;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@@ -168,6 +169,23 @@ public class DfaRelationValue extends DfaValue {
|
|||||||
return myRelation == EQEQ && myIsNegated || myRelation == GT && !myIsNegated || myRelation == GE && myIsNegated;
|
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() {
|
public boolean isInstanceOf() {
|
||||||
return myRelation == INSTANCEOF_KEYWORD;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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);
|
myRelationFactory = new DfaRelationValue.Factory(this);
|
||||||
myExpressionFactory = new DfaExpressionFactory(this);
|
myExpressionFactory = new DfaExpressionFactory(this);
|
||||||
myOptionalFactory = new DfaOptionalValue.Factory(this);
|
myOptionalFactory = new DfaOptionalValue.Factory(this);
|
||||||
|
myRangeFactory = new DfaRangeValue.Factory(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHonorFieldInitializers() {
|
public boolean isHonorFieldInitializers() {
|
||||||
@@ -137,6 +138,7 @@ public class DfaValueFactory {
|
|||||||
private final DfaRelationValue.Factory myRelationFactory;
|
private final DfaRelationValue.Factory myRelationFactory;
|
||||||
private final DfaExpressionFactory myExpressionFactory;
|
private final DfaExpressionFactory myExpressionFactory;
|
||||||
private final DfaOptionalValue.Factory myOptionalFactory;
|
private final DfaOptionalValue.Factory myOptionalFactory;
|
||||||
|
private final DfaRangeValue.Factory myRangeFactory;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public DfaVariableValue.Factory getVarFactory() {
|
public DfaVariableValue.Factory getVarFactory() {
|
||||||
@@ -166,4 +168,9 @@ public class DfaValueFactory {
|
|||||||
public DfaOptionalValue.Factory getOptionalFactory() {
|
public DfaOptionalValue.Factory getOptionalFactory() {
|
||||||
return myOptionalFactory;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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(DataFlowInspectionAncientTest.class);
|
||||||
suite.addTestSuite(ContractCheckTest.class);
|
suite.addTestSuite(ContractCheckTest.class);
|
||||||
suite.addTestSuite(HardcodedContractsTest.class);
|
suite.addTestSuite(HardcodedContractsTest.class);
|
||||||
|
suite.addTestSuite(DataFlowRangeAnalysisTest.class);
|
||||||
|
|
||||||
suite.addTestSuite(ContractInferenceFromSourceTest.class);
|
suite.addTestSuite(ContractInferenceFromSourceTest.class);
|
||||||
suite.addTestSuite(NullityInferenceFromSourceTestCase.DfaInferenceTest.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);
|
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")
|
@Contract("null, _, _ -> false")
|
||||||
public static boolean isNullComparison(PsiExpression expression, @NotNull PsiVariable variable, boolean equal) {
|
public static boolean isNullComparison(PsiExpression expression, @NotNull PsiVariable variable, boolean equal) {
|
||||||
return variable.equals(ExpressionUtils.getVariableFromNullComparison(expression, equal));
|
return variable.equals(ExpressionUtils.getVariableFromNullComparison(expression, equal));
|
||||||
|
|||||||
Reference in New Issue
Block a user