mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-dfa] DfaMemoryState.updateDfType is introduced to properly update the aliases
Fixes IDEA-304297 Incorrect empty collection inspection with ad hoc collector GitOrigin-RevId: 496e7aef00680696561508ad33670f833bab15ee
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9c8febb22b
commit
1137e3ab2a
+4
-8
@@ -16,6 +16,7 @@ import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Utility class to help interpreting the Java DFA
|
||||
@@ -35,15 +36,10 @@ public class JavaDfaHelpers {
|
||||
return value;
|
||||
}
|
||||
DfaVariableValue var = (DfaVariableValue)value;
|
||||
DfType dfType = state.getDfType(var);
|
||||
if (dfType instanceof DfReferenceType) {
|
||||
state.setDfType(var, ((DfReferenceType)dfType).dropLocality());
|
||||
}
|
||||
UnaryOperator<@NotNull DfType> updater = dfType -> dfType instanceof DfReferenceType refType ? refType.dropLocality() : dfType;
|
||||
state.updateDfType(var, updater);
|
||||
for (DfaVariableValue v : new ArrayList<>(var.getDependentVariables())) {
|
||||
dfType = state.getDfType(v);
|
||||
if (dfType instanceof DfReferenceType) {
|
||||
state.setDfType(v, ((DfReferenceType)dfType).dropLocality());
|
||||
}
|
||||
state.updateDfType(v, updater);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
+7
-11
@@ -442,11 +442,9 @@ public class MethodCallInstruction extends ExpressionPushingInstruction {
|
||||
@NotNull DfaMemoryState memState,
|
||||
DfaValue @Nullable [] argValues) {
|
||||
DfaValue value = memState.pop();
|
||||
if (getContext() instanceof PsiMethodReferenceExpression) {
|
||||
PsiMethodReferenceExpression context = (PsiMethodReferenceExpression)getContext();
|
||||
if (MethodReferenceInstruction.isQualifierDereferenced(context)) {
|
||||
value = CheckNotNullInstruction.dereference(interpreter, memState, value, NullabilityProblemKind.callMethodRefNPE.problem(context, null));
|
||||
}
|
||||
if (getContext() instanceof PsiMethodReferenceExpression context && MethodReferenceInstruction.isQualifierDereferenced(context)) {
|
||||
value = CheckNotNullInstruction.dereference(
|
||||
interpreter, memState, value, NullabilityProblemKind.callMethodRefNPE.problem(context, null));
|
||||
}
|
||||
DfType dfType = memState.getDfType(value);
|
||||
if (getMutationSignature().mutatesThis() && !Mutability.fromDfType(dfType).canBeModified()) {
|
||||
@@ -455,9 +453,8 @@ public class MethodCallInstruction extends ExpressionPushingInstruction {
|
||||
// So let's conservatively skip the warning here. Such contract is still useful because it assures that nothing else is mutated.
|
||||
if (method != null && JavaMethodContractUtil.hasExplicitContractAnnotation(method)) {
|
||||
interpreter.getListener().onCondition(new MutabilityProblem(getContext(), true), value, ThreeState.YES, memState);
|
||||
if (dfType instanceof DfReferenceType) {
|
||||
memState.setDfType(value, ((DfReferenceType)dfType).dropMutability().meet(Mutability.MUTABLE.asDfType()));
|
||||
}
|
||||
memState.updateDfType(
|
||||
value, type -> type instanceof DfReferenceType refType ? refType.dropMutability().meet(Mutability.MUTABLE.asDfType()) : type);
|
||||
}
|
||||
}
|
||||
TypeConstraint constraint = TypeConstraint.fromDfType(dfType);
|
||||
@@ -509,9 +506,8 @@ public class MethodCallInstruction extends ExpressionPushingInstruction {
|
||||
!memState.getDfType(SpecialField.ARRAY_LENGTH.createValue(interpreter.getFactory(), arg)).equals(intValue(0))) {
|
||||
PsiElement anchor = getArgumentAnchor(paramIndex);
|
||||
interpreter.getListener().onCondition(new MutabilityProblem(anchor, false), arg, ThreeState.YES, memState);
|
||||
if (dfType instanceof DfReferenceType) {
|
||||
memState.setDfType(arg, ((DfReferenceType)dfType).dropMutability().meet(Mutability.MUTABLE.asDfType()));
|
||||
}
|
||||
memState.updateDfType(
|
||||
arg, type -> type instanceof DfReferenceType refType ? refType.dropMutability().meet(Mutability.MUTABLE.asDfType()) : type);
|
||||
}
|
||||
}
|
||||
if (argValues != null) {
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import foo.NotNull;
|
||||
import foo.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
class Clazz {
|
||||
// IDEA-304297
|
||||
void customCollectToHashMap(List<String> list2) {
|
||||
Collection<String> list = Arrays.asList("test");
|
||||
Map<String, Integer> aux = list.stream().collect(() -> new HashMap<>(), (a, b) -> a.put(b, b.length()), Map::putAll);
|
||||
if (<warning descr="Condition 'aux.isEmpty()' is always 'false'">aux.isEmpty()</warning>) {}
|
||||
Map<String, Integer> aux2 = list2.stream().collect(() -> new HashMap<>(), (a, b) -> a.put(b, b.length()), Map::putAll);
|
||||
if (aux.isEmpty()) {}
|
||||
}
|
||||
|
||||
public void passNull(Collection<?> c) {
|
||||
c.stream().collect(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>,
|
||||
<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>,
|
||||
|
||||
+18
@@ -11,6 +11,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Represents a memory state of abstract interpreter.
|
||||
@@ -132,12 +133,29 @@ public interface DfaMemoryState {
|
||||
* Forcibly sets the supplied dfType to given value if given value state can be memoized.
|
||||
* This is necessary to override some knowledge about the variable state. In most of the cases
|
||||
* {@link #meetDfType(DfaValue, DfType)} should be used as it narrows existing type.
|
||||
* <p>
|
||||
* Use of this method is in general discouraged, as it doesn't update the type of known aliases,
|
||||
* which may cause subtle bugs. Consider using {@link #updateDfType(DfaValue, UnaryOperator)} instead.
|
||||
* </p>
|
||||
*
|
||||
* @param value value to update.
|
||||
* @param dfType type to assign to value. Note that type might be adjusted, e.g. to be compatible with value declared PsiType.
|
||||
*/
|
||||
void setDfType(@NotNull DfaValue value, @NotNull DfType dfType);
|
||||
|
||||
/**
|
||||
* Forcibly updates the dfType for given value if given value state can be memoized.
|
||||
* Known aliases are updated as well. This is necessary to override some knowledge about the variable state.
|
||||
* This may happen if contradiction is found and reported, but you want to continue the analysis, or
|
||||
* if you need to adjust mutable property like object locality. In most of the cases {@link #meetDfType(DfaValue, DfType)}
|
||||
* should be used as it narrows existing type.
|
||||
*
|
||||
* @param value value to update.
|
||||
* @param updater a function that accepts the current dfType and returns the updated one. May be called
|
||||
* several times for every alias.
|
||||
*/
|
||||
void updateDfType(@NotNull DfaValue value, @NotNull UnaryOperator<@NotNull DfType> updater);
|
||||
|
||||
/**
|
||||
* @param value value to get the type of
|
||||
* @return the DfType of the value within this memory state
|
||||
|
||||
+20
-3
@@ -82,11 +82,11 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
copy.flushFields(new QualifierStatusMap(null, true));
|
||||
copy.emptyStack();
|
||||
for (DfaValue value : getFactory().getValues().toArray(new DfaValue[0])) {
|
||||
if (value instanceof DfaVariableValue) {
|
||||
DfType type = copy.getDfType(value);
|
||||
if (value instanceof DfaVariableValue var) {
|
||||
DfType type = copy.getDfType(var);
|
||||
DfType newType = type.correctForClosure();
|
||||
if (newType != type) {
|
||||
copy.setDfType(value, newType);
|
||||
copy.recordVariableType(var, newType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -752,6 +752,23 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDfType(@NotNull DfaValue value, @NotNull UnaryOperator<@NotNull DfType> updater) {
|
||||
if (!(value instanceof DfaVariableValue var)) return;
|
||||
EqClass values = getEqClass(var);
|
||||
Iterable<DfaVariableValue> vars = values == null ? List.of(var) : values;
|
||||
for (@NotNull DfaVariableValue eqVar : vars) {
|
||||
DfType type = getRecordedType(eqVar);
|
||||
if (type == null) {
|
||||
type = eqVar.getInherentType();
|
||||
}
|
||||
DfType newType = updater.apply(type);
|
||||
if (!newType.equals(type)) {
|
||||
recordVariableType(eqVar, newType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean meetDfType(@NotNull DfaValue value, @NotNull DfType dfType) {
|
||||
if (dfType == DfType.TOP) return true;
|
||||
|
||||
Reference in New Issue
Block a user