mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-188301 Map index range to array value in DFA
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.util.keyFMap.KeyFMap;
|
||||
import one.util.streamex.StreamEx;
|
||||
@@ -195,6 +196,21 @@ public final class DfaFactMap {
|
||||
return map.with(factType, factType.calcFromVariable(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives facts which might be known from given DfaValue without knowing the particular memory state
|
||||
*
|
||||
* @param value a value to derive facts from
|
||||
* @return map of facts derived from the value
|
||||
*/
|
||||
@NotNull
|
||||
public static DfaFactMap fromDfaValue(DfaValue value) {
|
||||
return StreamEx.of(DfaFactType.getTypes()).foldLeft(EMPTY, (map, type) -> updateMap(map, type, value));
|
||||
}
|
||||
|
||||
private static <T> DfaFactMap updateMap(DfaFactMap map, DfaFactType<T> factType, DfaValue value) {
|
||||
return map.with(factType, factType.fromDfaValue(value));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FactMapper<R> {
|
||||
<T> R apply(DfaFactType<T> factType, T factValue);
|
||||
|
||||
+1
-6
@@ -1143,12 +1143,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
}
|
||||
value = resolveVariableValue((DfaVariableValue)value);
|
||||
}
|
||||
DfaValue finalValue = value;
|
||||
return StreamEx.of(DfaFactType.getTypes()).foldLeft(DfaFactMap.EMPTY, (map, type) -> updateMap(map, type, finalValue));
|
||||
}
|
||||
|
||||
private static <T> DfaFactMap updateMap(DfaFactMap map, DfaFactType<T> factType, DfaValue value) {
|
||||
return map.with(factType, factType.fromDfaValue(value));
|
||||
return DfaFactMap.fromDfaValue(value);
|
||||
}
|
||||
|
||||
void setVariableState(DfaVariableValue dfaVar, DfaVariableState state) {
|
||||
|
||||
+4
-9
@@ -162,15 +162,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
|
||||
DfaValue result = instruction.getValue();
|
||||
LongRangeSet rangeSet = memState.getValueFact(index, DfaFactType.RANGE);
|
||||
if (rangeSet != null && !rangeSet.isEmpty() && rangeSet.min() == rangeSet.max()) {
|
||||
long longIdx = rangeSet.min();
|
||||
if(longIdx >= 0 && longIdx <= Integer.MAX_VALUE) {
|
||||
int intIdx = (int)longIdx;
|
||||
DfaValue arrayElementValue = runner.getFactory().getExpressionFactory().getArrayElementValue(array, intIdx);
|
||||
if (arrayElementValue != null) {
|
||||
result = arrayElementValue;
|
||||
}
|
||||
}
|
||||
DfaValue arrayElementValue =
|
||||
runner.getFactory().getExpressionFactory().getArrayElementValue(array, rangeSet == null ? LongRangeSet.all() : rangeSet);
|
||||
if (arrayElementValue != DfaUnknownValue.getInstance()) {
|
||||
result = arrayElementValue;
|
||||
}
|
||||
memState.push(result);
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
|
||||
+45
-1
@@ -18,6 +18,7 @@ package com.intellij.codeInspection.dataFlow.value;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.ExpressionUtil;
|
||||
import com.intellij.codeInspection.dataFlow.*;
|
||||
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.Conditions;
|
||||
@@ -31,6 +32,7 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import one.util.streamex.LongStreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -231,6 +233,48 @@ public class DfaExpressionFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private DfaValue getAdvancedExpressionDfaValue(@Nullable PsiExpression expression) {
|
||||
if (expression == null) return DfaUnknownValue.getInstance();
|
||||
DfaValue value = getExpressionDfaValue(expression);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
if (expression instanceof PsiConditionalExpression) {
|
||||
return getAdvancedExpressionDfaValue(((PsiConditionalExpression)expression).getThenExpression()).union(
|
||||
getAdvancedExpressionDfaValue(((PsiConditionalExpression)expression).getElseExpression()));
|
||||
}
|
||||
PsiType type = expression.getType();
|
||||
if (type instanceof PsiPrimitiveType) return DfaUnknownValue.getInstance();
|
||||
return myFactory.createTypeValue(type, NullnessUtil.getExpressionNullness(expression));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DfaValue getArrayElementValue(DfaValue array, LongRangeSet indexSet) {
|
||||
if (!(array instanceof DfaVariableValue)) return DfaUnknownValue.getInstance();
|
||||
if (indexSet.isEmpty()) return DfaUnknownValue.getInstance();
|
||||
long min = indexSet.min();
|
||||
long max = indexSet.max();
|
||||
if (min == max && min >= 0 && min < Integer.MAX_VALUE) {
|
||||
DfaValue value = getArrayElementValue(array, (int)min);
|
||||
return value == null ? DfaUnknownValue.getInstance() : value;
|
||||
}
|
||||
DfaVariableValue arrayDfaVar = (DfaVariableValue)array;
|
||||
PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable();
|
||||
if (!(arrayPsiVar instanceof PsiVariable)) return DfaUnknownValue.getInstance();
|
||||
PsiExpression[] elements = ExpressionUtils.getConstantArrayElements((PsiVariable)arrayPsiVar);
|
||||
if (elements == null || elements.length == 0) return DfaUnknownValue.getInstance();
|
||||
indexSet = indexSet.intersect(LongRangeSet.range(0, elements.length - 1));
|
||||
if (indexSet.isEmpty() || indexSet.max() - indexSet.min() > 100) return DfaUnknownValue.getInstance();
|
||||
return LongStreamEx.of(indexSet.stream())
|
||||
.mapToObj(idx -> getAdvancedExpressionDfaValue(elements[(int)idx]))
|
||||
.prefix(DfaValue::union)
|
||||
.takeWhileInclusive(value -> value != DfaUnknownValue.getInstance())
|
||||
.reduce((a, b) -> b)
|
||||
.orElse(DfaUnknownValue.getInstance());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DfaValue getArrayElementValue(DfaValue array, int index) {
|
||||
if (!(array instanceof DfaVariableValue)) return null;
|
||||
DfaVariableValue arrayDfaVar = (DfaVariableValue)array;
|
||||
@@ -241,7 +285,7 @@ public class DfaExpressionFactory {
|
||||
if (arrayPsiVar instanceof PsiVariable) {
|
||||
PsiExpression constantArrayElement = ExpressionUtils.getConstantArrayElement((PsiVariable)arrayPsiVar, index);
|
||||
if (constantArrayElement != null) {
|
||||
return getExpressionDfaValue(constantArrayElement);
|
||||
return getAdvancedExpressionDfaValue(constantArrayElement);
|
||||
}
|
||||
}
|
||||
PsiVariable indexVariable = getArrayIndexVariable(arrayPsiVar, index);
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.dataFlow.value;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.DfaFactMap;
|
||||
|
||||
public abstract class DfaValue {
|
||||
private final int myID;
|
||||
protected final DfaValueFactory myFactory;
|
||||
@@ -32,6 +34,17 @@ public abstract class DfaValue {
|
||||
return myID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a value which describes a union of this value and other value
|
||||
*
|
||||
* @param other other value to unite with
|
||||
* @return a union value. Any particular runtime value which satisfies this value or other value, satisfies also the returned value.
|
||||
*/
|
||||
public DfaValue union(DfaValue other) {
|
||||
if(this == other) return this;
|
||||
return myFactory.getFactFactory().createValue(DfaFactMap.fromDfaValue(this).union(DfaFactMap.fromDfaValue(other)));
|
||||
}
|
||||
|
||||
public DfaValue createNegated() {
|
||||
return DfaUnknownValue.getInstance();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import java.util.*;
|
||||
class AdvancedArrayAccess {
|
||||
private static final int[] LENGTH = {0, 10, 20, 30};
|
||||
private static final int[] LENGTH2 = {0, 10, 20, 30};
|
||||
private static final String[] ARRAY = new String[] {"xyz".toLowerCase(Locale.ENGLISH)};
|
||||
|
||||
void testInstanceOf() {
|
||||
Object[] arr = {new String("foo"), new Integer(0)};
|
||||
@@ -17,6 +18,24 @@ class AdvancedArrayAccess {
|
||||
}
|
||||
}
|
||||
|
||||
void testAny(int i) {
|
||||
if(<warning descr="Condition 'LENGTH[i] > 30' is always 'false'">LENGTH[i] > 30</warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
if(LENGTH[i] > 20) {
|
||||
System.out.println("Possible");
|
||||
}
|
||||
if(<warning descr="Condition 'i < 3 && LENGTH[i] > 20' is always 'false'">i < 3 && <warning descr="Condition 'LENGTH[i] > 20' is always 'false' when reached">LENGTH[i] > 20</warning></warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
}
|
||||
|
||||
void testCall(int i) {
|
||||
if(<warning descr="Condition 'ARRAY[i] == null' is always 'false'">ARRAY[i] == null</warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
}
|
||||
|
||||
int[] getLengths() {
|
||||
// array is still read-only
|
||||
return LENGTH.clone();
|
||||
|
||||
@@ -17,7 +17,7 @@ public class February31 {
|
||||
if (time.month == 2 && IsLeapYear(time.year)) {
|
||||
return <warning descr="Condition 'time.month <= kDaysInMonth[time.month] + 1' is always 'true'">time.month <= kDaysInMonth[time.month] + 1</warning>;
|
||||
} else {
|
||||
return time.month <= kDaysInMonth[time.month];
|
||||
return <warning descr="Condition 'time.month <= kDaysInMonth[time.month]' is always 'true'">time.month <= kDaysInMonth[time.month]</warning>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-2
@@ -1182,17 +1182,31 @@ public class ExpressionUtils {
|
||||
@Nullable
|
||||
public static PsiExpression getConstantArrayElement(PsiVariable array, int index) {
|
||||
if (index < 0) return null;
|
||||
PsiExpression[] elements = getConstantArrayElements(array);
|
||||
if (elements == null || index >= elements.length) return null;
|
||||
return elements[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of expressions which represent all array elements if array is known to be never modified
|
||||
* after initialization.
|
||||
*
|
||||
* @param array an array variable
|
||||
* @return an array or null if array could be modified after initialization
|
||||
* (empty array means that the initializer is known to be an empty array).
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiExpression[] getConstantArrayElements(PsiVariable array) {
|
||||
PsiExpression initializer = array.getInitializer();
|
||||
if (initializer instanceof PsiNewExpression) initializer = ((PsiNewExpression)initializer).getArrayInitializer();
|
||||
if (!(initializer instanceof PsiArrayInitializerExpression)) return null;
|
||||
PsiExpression[] initializers = ((PsiArrayInitializerExpression)initializer).getInitializers();
|
||||
if (index >= initializers.length) return null;
|
||||
if (array instanceof PsiField && !(array.hasModifierProperty(PsiModifier.PRIVATE) && array.hasModifierProperty(PsiModifier.STATIC))) {
|
||||
return null;
|
||||
}
|
||||
Boolean isConstantArray = CachedValuesManager.<Boolean>getCachedValue(array, () -> CachedValueProvider.Result
|
||||
.create(isConstantArray(array), PsiModificationTracker.MODIFICATION_COUNT));
|
||||
return Boolean.TRUE.equals(isConstantArray) ? initializers[index] : null;
|
||||
return Boolean.TRUE.equals(isConstantArray) ? initializers : null;
|
||||
}
|
||||
|
||||
private static boolean isConstantArray(PsiVariable array) {
|
||||
|
||||
Reference in New Issue
Block a user