mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
OptionalGetWithoutIsPresentInspection: optional state integrated into main dataflow (IDEA-CR-18052)
This commit is contained in:
@@ -19,6 +19,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.util.ThreeState;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -43,8 +44,12 @@ public interface DfaMemoryState {
|
||||
|
||||
boolean applyInstanceofOrNull(@NotNull DfaRelationValue dfaCond);
|
||||
|
||||
void applyIsPresentCheck(boolean present, DfaValue qualifier);
|
||||
|
||||
boolean applyCondition(DfaValue dfaCond);
|
||||
|
||||
ThreeState checkOptional(DfaValue value);
|
||||
|
||||
void flushFields();
|
||||
|
||||
void flushVariable(DfaVariableValue variable);
|
||||
|
||||
+23
-2
@@ -36,6 +36,7 @@ import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.Stack;
|
||||
import gnu.trove.*;
|
||||
@@ -614,6 +615,13 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyIsPresentCheck(boolean present, DfaValue qualifier) {
|
||||
if (qualifier instanceof DfaVariableValue) {
|
||||
setVariableState((DfaVariableValue)qualifier, getVariableState((DfaVariableValue)qualifier).withOptionalPresense(present));
|
||||
}
|
||||
}
|
||||
|
||||
static DfaValue unwrap(DfaValue value) {
|
||||
if (value instanceof DfaBoxedValue) {
|
||||
return ((DfaBoxedValue)value).getWrappedValue();
|
||||
@@ -766,6 +774,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!isNegated && dfaRight instanceof DfaOptionalValue) {
|
||||
applyIsPresentCheck(((DfaOptionalValue)dfaRight).isPresent(), dfaLeft);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -907,13 +918,23 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreeState checkOptional(DfaValue value) {
|
||||
if (value instanceof DfaVariableValue) {
|
||||
DfaVariableValue var = (DfaVariableValue)value;
|
||||
DfaVariableState state = getVariableState(var);
|
||||
return state.getOptionalPresense();
|
||||
}
|
||||
return value instanceof DfaOptionalValue ? ThreeState.fromBoolean(((DfaOptionalValue)value).isPresent()) : ThreeState.UNSURE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DfaRelationValue compareToNull(DfaValue dfaVar, boolean negated) {
|
||||
DfaConstValue dfaNull = myFactory.getConstFactory().getNull();
|
||||
return myFactory.getRelationFactory().createRelation(dfaVar, dfaNull, JavaTokenType.EQEQ, negated);
|
||||
}
|
||||
|
||||
protected void setVariableState(DfaVariableValue dfaVar, DfaVariableState state) {
|
||||
void setVariableState(DfaVariableValue dfaVar, DfaVariableState state) {
|
||||
assert !myUnknownVariables.contains(dfaVar);
|
||||
if (state.equals(myDefaultVariableStates.get(dfaVar))) {
|
||||
myVariableStates.remove(dfaVar);
|
||||
@@ -923,7 +944,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
myCachedHash = null;
|
||||
}
|
||||
|
||||
protected DfaVariableState getVariableState(DfaVariableValue dfaVar) {
|
||||
DfaVariableState getVariableState(DfaVariableValue dfaVar) {
|
||||
DfaVariableState state = myVariableStates.get(dfaVar);
|
||||
|
||||
if (state == null) {
|
||||
|
||||
+36
-14
@@ -30,6 +30,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiPrimitiveType;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -40,23 +41,27 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DfaVariableState {
|
||||
class DfaVariableState {
|
||||
@NotNull final Set<DfaPsiType> myInstanceofValues;
|
||||
@NotNull final Set<DfaPsiType> myNotInstanceofValues;
|
||||
@NotNull final Nullness myNullability;
|
||||
@NotNull final ThreeState myOptionalPresence;
|
||||
private final int myHash;
|
||||
|
||||
protected DfaVariableState(@NotNull DfaVariableValue dfaVar) {
|
||||
this(Collections.<DfaPsiType>emptySet(), Collections.<DfaPsiType>emptySet(), dfaVar.getInherentNullability());
|
||||
DfaVariableState(@NotNull DfaVariableValue dfaVar) {
|
||||
this(Collections.emptySet(), Collections.emptySet(), dfaVar.getInherentNullability(), ThreeState.UNSURE);
|
||||
}
|
||||
|
||||
protected DfaVariableState(@NotNull Set<DfaPsiType> instanceofValues,
|
||||
DfaVariableState(@NotNull Set<DfaPsiType> instanceofValues,
|
||||
@NotNull Set<DfaPsiType> notInstanceofValues,
|
||||
@NotNull Nullness nullability) {
|
||||
@NotNull Nullness nullability,
|
||||
@NotNull ThreeState optionalPresence) {
|
||||
myInstanceofValues = instanceofValues;
|
||||
myNotInstanceofValues = notInstanceofValues;
|
||||
myNullability = nullability;
|
||||
myHash = (myInstanceofValues.hashCode() * 31 + myNotInstanceofValues.hashCode()) * 31 + myNullability.hashCode();
|
||||
myOptionalPresence = optionalPresence;
|
||||
myHash = ((myInstanceofValues.hashCode() * 31 + myNotInstanceofValues.hashCode()) * 31 + myNullability.hashCode()) * 31 +
|
||||
myOptionalPresence.hashCode();
|
||||
}
|
||||
|
||||
public boolean isNullable() {
|
||||
@@ -96,7 +101,7 @@ public class DfaVariableState {
|
||||
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
|
||||
newInstanceof.removeAll(moreGeneric);
|
||||
newInstanceof.add(dfaType.getDfaType());
|
||||
result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability);
|
||||
result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability, myOptionalPresence);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -124,7 +129,7 @@ public class DfaVariableState {
|
||||
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
|
||||
newNotInstanceof.removeAll(moreSpecific);
|
||||
newNotInstanceof.add(dfaType.getDfaType());
|
||||
return createCopy(myInstanceofValues, newNotInstanceof, myNullability);
|
||||
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -132,12 +137,12 @@ public class DfaVariableState {
|
||||
if (myInstanceofValues.contains(type)) {
|
||||
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
|
||||
newInstanceof.remove(type);
|
||||
return createCopy(newInstanceof, myNotInstanceofValues, myNullability);
|
||||
return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myOptionalPresence);
|
||||
}
|
||||
if (myNotInstanceofValues.contains(type)) {
|
||||
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
|
||||
newNotInstanceof.remove(type);
|
||||
return createCopy(myInstanceofValues, newNotInstanceof, myNullability);
|
||||
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -152,13 +157,16 @@ public class DfaVariableState {
|
||||
DfaVariableState aState = (DfaVariableState) obj;
|
||||
return myHash == aState.myHash &&
|
||||
myNullability == aState.myNullability &&
|
||||
myOptionalPresence == aState.myOptionalPresence &&
|
||||
myInstanceofValues.equals(aState.myInstanceofValues) &&
|
||||
myNotInstanceofValues.equals(aState.myNotInstanceofValues);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues, @NotNull Set<DfaPsiType> notInstanceofValues, @NotNull Nullness nullability) {
|
||||
return new DfaVariableState(instanceofValues, notInstanceofValues, nullability);
|
||||
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
|
||||
@NotNull Set<DfaPsiType> notInstanceofValues,
|
||||
@NotNull Nullness nullability, ThreeState optionalPresent) {
|
||||
return new DfaVariableState(instanceofValues, notInstanceofValues, nullability, optionalPresent);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -172,11 +180,15 @@ public class DfaVariableState {
|
||||
if (!myNotInstanceofValues.isEmpty()) {
|
||||
buf.append(" not instanceof ").append(StringUtil.join(myNotInstanceofValues, ","));
|
||||
}
|
||||
|
||||
if (myOptionalPresence != ThreeState.UNSURE) {
|
||||
buf.append(myOptionalPresence == ThreeState.YES ? " Optional with value" : " empty Optional");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Nullness getNullability() {
|
||||
Nullness getNullability() {
|
||||
return myNullability;
|
||||
}
|
||||
|
||||
@@ -186,7 +198,7 @@ public class DfaVariableState {
|
||||
|
||||
@NotNull
|
||||
DfaVariableState withNullability(@NotNull Nullness nullness) {
|
||||
return myNullability == nullness ? this : createCopy(myInstanceofValues, myNotInstanceofValues, nullness);
|
||||
return myNullability == nullness ? this : createCopy(myInstanceofValues, myNotInstanceofValues, nullness, myOptionalPresence);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -194,6 +206,13 @@ public class DfaVariableState {
|
||||
return myNullability != Nullness.NOT_NULL ? withNullability(nullable ? Nullness.NULLABLE : Nullness.UNKNOWN) : this;
|
||||
}
|
||||
|
||||
DfaVariableState withOptionalPresense(final boolean presense) {
|
||||
ThreeState optionalPresent = ThreeState.fromBoolean(presense);
|
||||
return myOptionalPresence != optionalPresent
|
||||
? createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, optionalPresent)
|
||||
: this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DfaVariableState withValue(DfaValue value) {
|
||||
return this;
|
||||
@@ -212,4 +231,7 @@ public class DfaVariableState {
|
||||
return myNotInstanceofValues;
|
||||
}
|
||||
|
||||
public ThreeState getOptionalPresense() {
|
||||
return myOptionalPresence;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -29,6 +29,7 @@ import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiPrimitiveType;
|
||||
import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.ThreeState;
|
||||
import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -128,9 +129,8 @@ class NullParameterConstraintChecker extends DataFlowRunner {
|
||||
protected MyDfaMemoryState(DfaValueFactory factory) {
|
||||
super(factory);
|
||||
for (PsiParameter parameter : myPossiblyViolatedParameters) {
|
||||
setVariableState(getFactory().getVarFactory().createVariableValue(parameter, false), new DfaVariableState(Collections.emptySet(),
|
||||
Collections.emptySet(),
|
||||
Nullness.NULLABLE));
|
||||
setVariableState(getFactory().getVarFactory().createVariableValue(parameter, false),
|
||||
new DfaVariableState(Collections.emptySet(), Collections.emptySet(), Nullness.NULLABLE, ThreeState.UNSURE));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+72
-14
@@ -24,9 +24,12 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.FactoryMap;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -180,24 +183,28 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
DfaValue[] argValues = popCallArguments(instruction, runner, memState);
|
||||
final DfaValue qualifier = popQualifier(instruction, runner, memState);
|
||||
|
||||
LinkedHashSet<DfaMemoryState> currentStates = ContainerUtil.newLinkedHashSet(memState);
|
||||
Set<DfaMemoryState> finalStates = ContainerUtil.newLinkedHashSet();
|
||||
if (argValues != null) {
|
||||
for (MethodContract contract : instruction.getContracts()) {
|
||||
currentStates = addContractResults(argValues, contract, currentStates, instruction, runner.getFactory(), finalStates);
|
||||
if (currentStates.size() + finalStates.size() > DataFlowRunner.MAX_STATES_PER_BRANCH) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Too complex contract on " + instruction.getContext() + ", skipping contract processing");
|
||||
finalStates.addAll(handleOptionalMethods(instruction, runner, memState, qualifier, argValues));
|
||||
|
||||
if (finalStates.isEmpty()) {
|
||||
LinkedHashSet<DfaMemoryState> currentStates = ContainerUtil.newLinkedHashSet(memState);
|
||||
if (argValues != null) {
|
||||
for (MethodContract contract : instruction.getContracts()) {
|
||||
currentStates = addContractResults(argValues, contract, currentStates, instruction, runner.getFactory(), finalStates);
|
||||
if (currentStates.size() + finalStates.size() > DataFlowRunner.MAX_STATES_PER_BRANCH) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Too complex contract on " + instruction.getContext() + ", skipping contract processing");
|
||||
}
|
||||
finalStates.clear();
|
||||
currentStates = ContainerUtil.newLinkedHashSet(memState);
|
||||
break;
|
||||
}
|
||||
finalStates.clear();
|
||||
currentStates = ContainerUtil.newLinkedHashSet(memState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (DfaMemoryState state : currentStates) {
|
||||
state.push(getMethodResultValue(instruction, qualifier, runner.getFactory()));
|
||||
finalStates.add(state);
|
||||
for (DfaMemoryState state : currentStates) {
|
||||
state.push(getMethodResultValue(instruction, qualifier, runner.getFactory()));
|
||||
finalStates.add(state);
|
||||
}
|
||||
}
|
||||
|
||||
DfaInstructionState[] result = new DfaInstructionState[finalStates.size()];
|
||||
@@ -211,6 +218,57 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<DfaMemoryState> handleOptionalMethods(MethodCallInstruction instruction,
|
||||
DataFlowRunner runner,
|
||||
DfaMemoryState memState,
|
||||
DfaValue qualifierValue, @Nullable DfaValue[] argValues) {
|
||||
PsiMethodCallExpression call = ObjectUtils.tryCast(instruction.getCallExpression(), PsiMethodCallExpression.class);
|
||||
if (call == null) return Collections.emptyList();
|
||||
String methodName = call.getMethodExpression().getReferenceName();
|
||||
if ("isPresent".equals(methodName)) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (method != null && TypeUtils.isOptional(method.getContainingClass())) {
|
||||
ThreeState state = memState.checkOptional(qualifierValue);
|
||||
DfaConstValue.Factory constFactory = runner.getFactory().getConstFactory();
|
||||
if (state == ThreeState.UNSURE) {
|
||||
DfaMemoryState falseState = memState.createCopy();
|
||||
memState.push(constFactory.getTrue());
|
||||
memState.applyIsPresentCheck(true, qualifierValue);
|
||||
falseState.push(constFactory.getFalse());
|
||||
falseState.applyIsPresentCheck(false, qualifierValue);
|
||||
return Arrays.asList(memState, falseState);
|
||||
}
|
||||
else {
|
||||
DfaValue result = state == ThreeState.YES ? constFactory.getTrue() : constFactory.getFalse();
|
||||
memState.push(result);
|
||||
return Collections.singletonList(memState);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("of".equals(methodName)) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (method != null && TypeUtils.isOptional(method.getContainingClass())) {
|
||||
memState.push(runner.getFactory().getOptionalFactory().getOptional(true));
|
||||
return Collections.singletonList(memState);
|
||||
}
|
||||
}
|
||||
if (DfaOptionalSupport.resolveOfNullable(call) != null) {
|
||||
if (argValues != null && argValues.length == 1 && memState.isNotNull(argValues[0])) {
|
||||
memState.push(runner.getFactory().getOptionalFactory().getOptional(true));
|
||||
return Collections.singletonList(memState);
|
||||
}
|
||||
}
|
||||
if ("empty".equals(methodName) || "absent".equals(methodName)) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (method != null && TypeUtils.isOptional(method.getContainingClass())) {
|
||||
memState.push(runner.getFactory().getOptionalFactory().getOptional(false));
|
||||
return Collections.singletonList(memState);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DfaValue[] popCallArguments(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
final PsiExpression[] args = instruction.getArgs();
|
||||
|
||||
+16
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
* Copyright 2000-2015 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.
|
||||
@@ -21,6 +21,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.intellij.util.containers.FList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -30,7 +31,7 @@ import java.util.Set;
|
||||
/**
|
||||
* @author Gregory.Shrago
|
||||
*/
|
||||
public class ValuableDataFlowRunner extends DataFlowRunner {
|
||||
class ValuableDataFlowRunner extends DataFlowRunner {
|
||||
@NotNull
|
||||
@Override
|
||||
protected DfaMemoryState createMemoryState() {
|
||||
@@ -64,11 +65,11 @@ public class ValuableDataFlowRunner extends DataFlowRunner {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ValuableDfaVariableState extends DfaVariableState {
|
||||
static class ValuableDfaVariableState extends DfaVariableState {
|
||||
private final DfaValue myValue;
|
||||
@NotNull final FList<PsiExpression> myConcatenation;
|
||||
|
||||
public ValuableDfaVariableState(@NotNull DfaVariableValue psiVariable) {
|
||||
private ValuableDfaVariableState(@NotNull DfaVariableValue psiVariable) {
|
||||
super(psiVariable);
|
||||
myValue = null;
|
||||
myConcatenation = FList.emptyList();
|
||||
@@ -77,28 +78,33 @@ public class ValuableDataFlowRunner extends DataFlowRunner {
|
||||
private ValuableDfaVariableState(Set<DfaPsiType> instanceofValues,
|
||||
Set<DfaPsiType> notInstanceofValues,
|
||||
Nullness nullability, DfaValue value,
|
||||
@NotNull FList<PsiExpression> concatenation) {
|
||||
super(instanceofValues, notInstanceofValues, nullability);
|
||||
@NotNull FList<PsiExpression> concatenation, ThreeState optionalPresence) {
|
||||
super(instanceofValues, notInstanceofValues, nullability, optionalPresence);
|
||||
myValue = value;
|
||||
myConcatenation = concatenation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues, @NotNull Set<DfaPsiType> notInstanceofValues, @NotNull Nullness nullability) {
|
||||
return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation);
|
||||
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
|
||||
@NotNull Set<DfaPsiType> notInstanceofValues,
|
||||
@NotNull Nullness nullability,
|
||||
ThreeState optionalPresence) {
|
||||
return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation, optionalPresence);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DfaVariableState withValue(@Nullable final DfaValue value) {
|
||||
if (value == myValue) return this;
|
||||
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, value, myConcatenation);
|
||||
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, value, myConcatenation,
|
||||
myOptionalPresence);
|
||||
}
|
||||
|
||||
ValuableDfaVariableState withExpression(@NotNull final FList<PsiExpression> concatenation) {
|
||||
if (concatenation == myConcatenation) return this;
|
||||
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation);
|
||||
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation,
|
||||
myOptionalPresence);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class DfaOptionalValue extends DfaValue {
|
||||
final boolean myPresent;
|
||||
|
||||
protected DfaOptionalValue(DfaValueFactory factory, boolean isPresent) {
|
||||
super(factory);
|
||||
myPresent = isPresent;
|
||||
}
|
||||
|
||||
public boolean isPresent() {
|
||||
return myPresent;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return myPresent ? "Optional with value" : "Empty optional";
|
||||
}
|
||||
|
||||
public static class Factory {
|
||||
private final DfaOptionalValue myPresent, myAbsent;
|
||||
|
||||
Factory(DfaValueFactory factory) {
|
||||
myPresent = new DfaOptionalValue(factory, true);
|
||||
myAbsent = new DfaOptionalValue(factory, false);
|
||||
}
|
||||
|
||||
public DfaOptionalValue getOptional(boolean present) {
|
||||
return present ? myPresent : myAbsent;
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -59,6 +59,7 @@ public class DfaValueFactory {
|
||||
myTypeFactory = new DfaTypeValue.Factory(this);
|
||||
myRelationFactory = new DfaRelationValue.Factory(this);
|
||||
myExpressionFactory = new DfaExpressionFactory(this);
|
||||
myOptionalFactory = new DfaOptionalValue.Factory(this);
|
||||
}
|
||||
|
||||
public boolean isHonorFieldInitializers() {
|
||||
@@ -135,6 +136,7 @@ public class DfaValueFactory {
|
||||
private final DfaTypeValue.Factory myTypeFactory;
|
||||
private final DfaRelationValue.Factory myRelationFactory;
|
||||
private final DfaExpressionFactory myExpressionFactory;
|
||||
private final DfaOptionalValue.Factory myOptionalFactory;
|
||||
|
||||
@NotNull
|
||||
public DfaVariableValue.Factory getVarFactory() {
|
||||
@@ -159,4 +161,9 @@ public class DfaValueFactory {
|
||||
public DfaRelationValue.Factory getRelationFactory() {
|
||||
return myRelationFactory;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DfaOptionalValue.Factory getOptionalFactory() {
|
||||
return myOptionalFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import java.util.Optional;
|
||||
|
||||
class Test {
|
||||
private void checkIsPresent(boolean b) {
|
||||
Optional<String> test;
|
||||
if (b) {
|
||||
test = Optional.of("x");
|
||||
} else {
|
||||
test = Optional.empty();
|
||||
if(<warning descr="Condition '!test.isPresent()' is always 'true'">!test.isPresent()</warning>) {
|
||||
System.out.println("Always");
|
||||
}
|
||||
}
|
||||
Optional<String> other = test;
|
||||
if(test.isPresent() && <warning descr="Condition 'other.isPresent()' is always 'true' when reached">other.isPresent()</warning>) {
|
||||
System.out.println(test.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
|
||||
public void testNullableForeachVariable() { doTestWithCustomAnnotations(); }
|
||||
public void testGenericParameterNullity() { doTestWithCustomAnnotations(); }
|
||||
public void testOptionalOfNullable() { doTest(); }
|
||||
public void testOptionalIsPresent() { doTest(); }
|
||||
public void testPrimitiveInVoidLambda() { doTest(); }
|
||||
public void testNotNullLambdaParameter() { doTest(); }
|
||||
public void testNotNullOptionalLambdaParameter() { doTest(); }
|
||||
|
||||
+4
-140
@@ -17,9 +17,6 @@ package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.*;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
@@ -92,16 +89,7 @@ public class OptionalGetWithoutIsPresentInspection extends BaseInspection {
|
||||
}
|
||||
|
||||
private void analyze(PsiElement context) {
|
||||
final DataFlowRunner dfaRunner = new StandardDataFlowRunner(false, true, isOnTheFly()) {
|
||||
private final DfaOptionalValue myPresentValue = new DfaOptionalValue(getFactory(), true);
|
||||
private final DfaOptionalValue myAbsentValue = new DfaOptionalValue(getFactory(), false);
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected DfaMemoryState createMemoryState() {
|
||||
return new OptionalMemoryState(getFactory(), myPresentValue, myAbsentValue);
|
||||
}
|
||||
};
|
||||
final DataFlowRunner dfaRunner = new StandardDataFlowRunner(false, true, isOnTheFly());
|
||||
dfaRunner.analyzeMethod(context, new StandardInstructionVisitor() {
|
||||
@Override
|
||||
public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
@@ -109,66 +97,13 @@ public class OptionalGetWithoutIsPresentInspection extends BaseInspection {
|
||||
if (call != null) {
|
||||
String methodName = call.getMethodExpression().getReferenceName();
|
||||
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
|
||||
OptionalMemoryState optionalMemState = (OptionalMemoryState)memState;
|
||||
if (qualifier != null && TypeUtils.isOptional(qualifier.getType())) {
|
||||
if ("isPresent".equals(methodName)) {
|
||||
DfaValue qualifierValue = optionalMemState.peek();
|
||||
ThreeState state = optionalMemState.checkOptional(qualifierValue);
|
||||
if(state == ThreeState.UNSURE) {
|
||||
DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState);
|
||||
if(states.length != 1) return states;
|
||||
OptionalMemoryState trueState = (OptionalMemoryState)states[0].getMemoryState();
|
||||
trueState.pop();
|
||||
OptionalMemoryState falseState = trueState.createCopy();
|
||||
trueState.push(optionalMemState.getBoolean(true));
|
||||
trueState.applyIsPresentCheck(true, qualifierValue);
|
||||
falseState.push(optionalMemState.getBoolean(false));
|
||||
falseState.applyIsPresentCheck(false, qualifierValue);
|
||||
return new DfaInstructionState[] {states[0], new DfaInstructionState(states[0].getInstruction(), falseState)};
|
||||
} else {
|
||||
return replaceResult(instruction, runner, memState, optionalMemState.getBoolean(state.toBoolean()));
|
||||
}
|
||||
}
|
||||
else if (isOptionalGetMethodName(methodName)) {
|
||||
ThreeState state = optionalMemState.checkOptional(memState.peek());
|
||||
seen.merge(call, state, (s1, s2) -> s1 == s2 ? s1 : ThreeState.UNSURE);
|
||||
}
|
||||
}
|
||||
if ("of".equals(methodName)) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (method != null && TypeUtils.isOptional(method.getContainingClass())) {
|
||||
return replaceResult(instruction, runner, memState, optionalMemState.getOptional(true));
|
||||
}
|
||||
}
|
||||
if ("ofNullable".equals(methodName)) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (method != null &&
|
||||
TypeUtils.isOptional(method.getContainingClass()) &&
|
||||
call.getArgumentList().getExpressions().length == 1) {
|
||||
if(memState.isNotNull(memState.peek())) {
|
||||
return replaceResult(instruction, runner, memState, optionalMemState.getOptional(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("empty".equals(methodName)) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (method != null && TypeUtils.isOptional(method.getContainingClass())) {
|
||||
return replaceResult(instruction, runner, memState, optionalMemState.getOptional(false));
|
||||
}
|
||||
if (qualifier != null && TypeUtils.isOptional(qualifier.getType()) && isOptionalGetMethodName(methodName)) {
|
||||
ThreeState state = memState.checkOptional(memState.peek());
|
||||
seen.merge(call, state, (s1, s2) -> s1 == s2 ? s1 : ThreeState.UNSURE);
|
||||
}
|
||||
}
|
||||
return super.visitMethodCall(instruction, runner, memState);
|
||||
}
|
||||
|
||||
private DfaInstructionState[] replaceResult(MethodCallInstruction instruction,
|
||||
DataFlowRunner runner,
|
||||
DfaMemoryState memState,
|
||||
DfaValue result) {
|
||||
DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState);
|
||||
memState.pop();
|
||||
memState.push(result);
|
||||
return states;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,76 +125,5 @@ public class OptionalGetWithoutIsPresentInspection extends BaseInspection {
|
||||
private static boolean isOptionalGetMethodName(String name) {
|
||||
return "get".equals(name) || "getAsDouble".equals(name) || "getAsInt".equals(name) || "getAsLong".equals(name);
|
||||
}
|
||||
|
||||
static class OptionalMemoryState extends DfaMemoryStateImpl {
|
||||
private final DfaOptionalValue myPresentValue;
|
||||
private final DfaOptionalValue myAbsentValue;
|
||||
|
||||
public OptionalMemoryState(DfaValueFactory factory,
|
||||
DfaOptionalValue presentValue,
|
||||
DfaOptionalValue absentValue) {
|
||||
super(factory);
|
||||
myPresentValue = presentValue;
|
||||
myAbsentValue = absentValue;
|
||||
}
|
||||
|
||||
public OptionalMemoryState(OptionalMemoryState state) {
|
||||
super(state);
|
||||
myPresentValue = state.myPresentValue;
|
||||
myAbsentValue = state.myAbsentValue;
|
||||
}
|
||||
|
||||
public DfaValue getBoolean(boolean val) {
|
||||
return val ? getFactory().getConstFactory().getTrue() : getFactory().getConstFactory().getFalse();
|
||||
}
|
||||
|
||||
public DfaValue getOptional(boolean present) {
|
||||
return present ? myPresentValue : myAbsentValue;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected DfaVariableState createVariableState(@NotNull DfaVariableValue var) {
|
||||
return new ValuableDataFlowRunner.ValuableDfaVariableState(var);
|
||||
}
|
||||
|
||||
public ThreeState checkOptional(DfaValue value) {
|
||||
if (value instanceof DfaVariableValue) {
|
||||
DfaVariableValue var = (DfaVariableValue)value;
|
||||
DfaValue newCond = getVariableState(var).getValue();
|
||||
if (newCond == null) {
|
||||
return checkOptional(getVariableState(var.createNegated()).getValue());
|
||||
}
|
||||
return checkOptional(newCond);
|
||||
}
|
||||
return value instanceof DfaOptionalValue ? ThreeState.fromBoolean(((DfaOptionalValue)value).myPresent) : ThreeState.UNSURE;
|
||||
}
|
||||
|
||||
void applyIsPresentCheck(boolean present, DfaValue qualifier) {
|
||||
if (qualifier instanceof DfaVariableValue) {
|
||||
DfaVariableValue optionalVar = (DfaVariableValue)qualifier;
|
||||
setVariableState(optionalVar, getVariableState(optionalVar).withValue(getOptional(present)));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public OptionalMemoryState createCopy() {
|
||||
return new OptionalMemoryState(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class DfaOptionalValue extends DfaValue {
|
||||
final boolean myPresent;
|
||||
|
||||
protected DfaOptionalValue(DfaValueFactory factory, boolean isPresent) {
|
||||
super(factory);
|
||||
myPresent = isPresent;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return myPresent ? "Optional with value" : "Empty optional";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user