blob: 96102b53f5d4257bb5e38ae6a12bd917da848eee [file] [log] [blame]
/*
* Copyright 2000-2009 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;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static com.intellij.psi.JavaTokenType.*;
/**
* @author peter
*/
public class StandardInstructionVisitor extends InstructionVisitor {
private static final Object ANY_VALUE = new Object();
private final Set<BinopInstruction> myReachable = new THashSet<BinopInstruction>();
private final Set<BinopInstruction> myCanBeNullInInstanceof = new THashSet<BinopInstruction>();
private final MultiMap<PushInstruction, Object> myPossibleVariableValues = MultiMap.createSet();
private final Set<PsiElement> myNotToReportReachability = new THashSet<PsiElement>();
private final Set<InstanceofInstruction> myUsefulInstanceofs = new THashSet<InstanceofInstruction>();
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final FactoryMap<MethodCallInstruction, Nullness> myReturnTypeNullability = new FactoryMap<MethodCallInstruction, Nullness>() {
@Override
protected Nullness create(MethodCallInstruction key) {
final PsiCallExpression callExpression = key.getCallExpression();
if (callExpression instanceof PsiNewExpression) {
return Nullness.NOT_NULL;
}
return callExpression != null ? DfaPsiUtil.getElementNullability(key.getResultType(), callExpression.resolveMethod()) : null;
}
};
@Override
public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
DfaValue dfaSource = memState.pop();
DfaValue dfaDest = memState.pop();
if (dfaDest instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue) dfaDest;
DfaValueFactory factory = runner.getFactory();
if (dfaSource instanceof DfaVariableValue && factory.getVarFactory().getAllQualifiedBy(var).contains(dfaSource)) {
Nullness nullability = memState.isNotNull(dfaSource) ? Nullness.NOT_NULL
: ((DfaVariableValue)dfaSource).getInherentNullability();
dfaSource = factory.createTypeValue(((DfaVariableValue)dfaSource).getVariableType(), nullability);
}
if (var.getInherentNullability() == Nullness.NOT_NULL) {
checkNotNullable(memState, dfaSource, NullabilityProblem.assigningToNotNull, instruction.getRExpression());
}
final PsiModifierListOwner psi = var.getPsiVariable();
if (!(psi instanceof PsiField) || !psi.hasModifierProperty(PsiModifier.VOLATILE)) {
memState.setVarValue(var, dfaSource);
}
} else if (dfaDest instanceof DfaTypeValue && ((DfaTypeValue)dfaDest).isNotNull()) {
checkNotNullable(memState, dfaSource, NullabilityProblem.assigningToNotNull, instruction.getRExpression());
}
memState.push(dfaDest);
return nextInstruction(instruction, runner, memState);
}
@Override
public DfaInstructionState[] visitCheckReturnValue(CheckReturnValueInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState) {
final DfaValue retValue = memState.pop();
checkNotNullable(memState, retValue, NullabilityProblem.nullableReturn, instruction.getReturn());
return nextInstruction(instruction, runner, memState);
}
@Override
public DfaInstructionState[] visitFieldReference(FieldReferenceInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
final DfaValue qualifier = memState.pop();
if (!checkNotNullable(memState, qualifier, NullabilityProblem.fieldAccessNPE, instruction.getElementToAssert())) {
forceNotNull(runner, memState, qualifier);
}
return nextInstruction(instruction, runner, memState);
}
@Override
public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
if (instruction.isReferenceRead()) {
DfaValue dfaValue = instruction.getValue();
if (dfaValue instanceof DfaVariableValue) {
DfaConstValue constValue = memState.getConstantValue((DfaVariableValue)dfaValue);
myPossibleVariableValues.putValue(instruction, constValue != null && (constValue.getValue() == null || constValue.getValue() instanceof Boolean) ? constValue : ANY_VALUE);
}
}
return super.visitPush(instruction, runner, memState);
}
public List<Pair<PsiReferenceExpression, DfaConstValue>> getConstantReferenceValues() {
List<Pair<PsiReferenceExpression, DfaConstValue>> result = ContainerUtil.newArrayList();
for (PushInstruction instruction : myPossibleVariableValues.keySet()) {
Collection<Object> values = myPossibleVariableValues.get(instruction);
if (values.size() == 1) {
Object singleValue = values.iterator().next();
if (singleValue != ANY_VALUE) {
result.add(Pair.create((PsiReferenceExpression)instruction.getPlace(), (DfaConstValue)singleValue));
}
}
}
return result;
}
@Override
public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
final DfaValueFactory factory = runner.getFactory();
DfaValue dfaExpr = factory.createValue(instruction.getCasted());
if (dfaExpr != null) {
DfaTypeValue dfaType = (DfaTypeValue)factory.createTypeValue(instruction.getCastTo(), Nullness.UNKNOWN);
DfaRelationValue dfaInstanceof = factory.getRelationFactory().createRelation(dfaExpr, dfaType, INSTANCEOF_KEYWORD, false);
if (dfaInstanceof != null && !memState.applyInstanceofOrNull(dfaInstanceof)) {
onInstructionProducesCCE(instruction);
}
}
if (instruction.getCastTo() instanceof PsiPrimitiveType) {
memState.push(runner.getFactory().getBoxedFactory().createUnboxed(memState.pop()));
}
return nextInstruction(instruction, runner, memState);
}
protected void onInstructionProducesCCE(TypeCastInstruction instruction) {}
@Override
public DfaInstructionState[] visitMethodCall(final MethodCallInstruction instruction, final DataFlowRunner runner, final DfaMemoryState memState) {
DfaValue[] argValues = popCallArguments(instruction, runner, memState);
final DfaValue qualifier = popQualifier(instruction, runner, memState);
List<DfaMemoryState> currentStates = ContainerUtil.newArrayList(memState);
Set<DfaMemoryState> finalStates = ContainerUtil.newLinkedHashSet();
if (argValues != null) {
for (MethodContract contract : instruction.getContracts()) {
currentStates = addContractResults(argValues, contract, currentStates, instruction, runner.getFactory(), finalStates);
}
}
for (DfaMemoryState state : currentStates) {
state.push(getMethodResultValue(instruction, qualifier, runner.getFactory()));
finalStates.add(state);
}
return ContainerUtil.map2Array(finalStates, DfaInstructionState.class, new Function<DfaMemoryState, DfaInstructionState>() {
@Override
public DfaInstructionState fun(DfaMemoryState state) {
if (instruction.shouldFlushFields()) {
state.flushFields();
}
return new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), state);
}
});
}
@Nullable
private DfaValue[] popCallArguments(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
final PsiExpression[] args = instruction.getArgs();
PsiMethod method = instruction.getTargetMethod();
boolean varargCall = instruction.isVarArgCall();
DfaValue[] argValues;
if (method == null || instruction.getContracts().isEmpty()) {
argValues = null;
} else {
int paramCount = method.getParameterList().getParametersCount();
if (paramCount == args.length || method.isVarArgs() && args.length >= paramCount - 1) {
argValues = new DfaValue[paramCount];
if (varargCall) {
argValues[paramCount - 1] = DfaUnknownValue.getInstance();
}
} else {
argValues = null;
}
}
for (int i = 0; i < args.length; i++) {
final DfaValue arg = memState.pop();
int paramIndex = args.length - i - 1;
if (argValues != null && (paramIndex < argValues.length - 1 || !varargCall)) {
argValues[paramIndex] = arg;
}
PsiExpression expr = args[paramIndex];
Nullness requiredNullability = instruction.getArgRequiredNullability(expr);
if (requiredNullability == Nullness.NOT_NULL) {
if (!checkNotNullable(memState, arg, NullabilityProblem.passingNullableToNotNullParameter, expr)) {
forceNotNull(runner, memState, arg);
}
}
else if (requiredNullability == Nullness.UNKNOWN) {
checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, expr);
}
}
return argValues;
}
private DfaValue popQualifier(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
@NotNull final DfaValue qualifier = memState.pop();
boolean unboxing = instruction.getMethodType() == MethodCallInstruction.MethodType.UNBOXING;
NullabilityProblem problem = unboxing ? NullabilityProblem.unboxingNullable : NullabilityProblem.callNPE;
PsiExpression anchor = unboxing ? instruction.getContext() : instruction.getCallExpression();
if (!checkNotNullable(memState, qualifier, problem, anchor)) {
forceNotNull(runner, memState, qualifier);
}
return qualifier;
}
private List<DfaMemoryState> addContractResults(DfaValue[] argValues,
MethodContract contract,
List<DfaMemoryState> states,
MethodCallInstruction instruction,
DfaValueFactory factory,
Set<DfaMemoryState> finalStates) {
DfaConstValue.Factory constFactory = factory.getConstFactory();
List<DfaMemoryState> falseStates = ContainerUtil.newArrayList();
for (int i = 0; i < argValues.length; i++) {
DfaValue argValue = argValues[i];
MethodContract.ValueConstraint constraint = contract.arguments[i];
DfaConstValue expectedValue = constraint.getComparisonValue(factory);
if (expectedValue == null) continue;
boolean invertCondition = constraint.shouldUseNonEqComparison();
DfaValue condition = factory.getRelationFactory().createRelation(argValue, expectedValue, EQEQ, invertCondition);
if (condition == null) {
if (!(argValue instanceof DfaConstValue)) {
for (DfaMemoryState state : states) {
falseStates.add(state.createCopy());
}
continue;
}
condition = constFactory.createFromValue((argValue == expectedValue) != invertCondition, PsiType.BOOLEAN, null);
}
List<DfaMemoryState> nextStates = ContainerUtil.newArrayList();
for (DfaMemoryState state : states) {
boolean unknownVsNull = expectedValue == constFactory.getNull() &&
argValue instanceof DfaVariableValue &&
((DfaMemoryStateImpl)state).getVariableState((DfaVariableValue)argValue).getNullability() == Nullness.UNKNOWN;
DfaMemoryState falseCopy = state.createCopy();
if (state.applyCondition(condition)) {
if (unknownVsNull && !invertCondition) {
state.markEphemeral();
}
nextStates.add(state);
}
if (falseCopy.applyCondition(condition.createNegated())) {
if (unknownVsNull && invertCondition) {
falseCopy.markEphemeral();
}
falseStates.add(falseCopy);
}
}
states = nextStates;
}
for (DfaMemoryState state : states) {
state.push(getDfaContractReturnValue(contract, instruction, factory));
finalStates.add(state);
}
return falseStates;
}
private DfaValue getDfaContractReturnValue(MethodContract contract,
MethodCallInstruction instruction,
DfaValueFactory factory) {
switch (contract.returnValue) {
case NULL_VALUE: return factory.getConstFactory().getNull();
case NOT_NULL_VALUE: return factory.createTypeValue(instruction.getResultType(), Nullness.NOT_NULL);
case TRUE_VALUE: return factory.getConstFactory().getTrue();
case FALSE_VALUE: return factory.getConstFactory().getFalse();
case THROW_EXCEPTION: return factory.getConstFactory().getContractFail();
default: return getMethodResultValue(instruction, null, factory);
}
}
private static void forceNotNull(DataFlowRunner runner, DfaMemoryState memState, DfaValue arg) {
if (arg instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue)arg;
memState.setVarValue(var, runner.getFactory().createTypeValue(var.getVariableType(), Nullness.NOT_NULL));
}
}
@NotNull
private DfaValue getMethodResultValue(MethodCallInstruction instruction, @Nullable DfaValue qualifierValue, DfaValueFactory factory) {
DfaValue precalculated = instruction.getPrecalculatedReturnValue();
if (precalculated != null) {
return precalculated;
}
final PsiType type = instruction.getResultType();
final MethodCallInstruction.MethodType methodType = instruction.getMethodType();
if (methodType == MethodCallInstruction.MethodType.UNBOXING) {
return factory.getBoxedFactory().createUnboxed(qualifierValue);
}
if (methodType == MethodCallInstruction.MethodType.BOXING) {
DfaValue boxed = factory.getBoxedFactory().createBoxed(qualifierValue);
return boxed == null ? factory.createTypeValue(type, Nullness.NOT_NULL) : boxed;
}
if (methodType == MethodCallInstruction.MethodType.CAST) {
assert qualifierValue != null;
if (qualifierValue instanceof DfaConstValue) {
Object casted = TypeConversionUtil.computeCastTo(((DfaConstValue)qualifierValue).getValue(), type);
return factory.getConstFactory().createFromValue(casted, type, ((DfaConstValue)qualifierValue).getConstant());
}
return qualifierValue;
}
if (type != null && (type instanceof PsiClassType || type.getArrayDimensions() > 0)) {
return factory.createTypeValue(type, myReturnTypeNullability.get(instruction));
}
return DfaUnknownValue.getInstance();
}
protected boolean checkNotNullable(DfaMemoryState state,
DfaValue value, NullabilityProblem problem,
PsiElement anchor) {
boolean notNullable = state.checkNotNullable(value);
if (notNullable && problem != NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter) {
DfaValueFactory factory = ((DfaMemoryStateImpl)state).getFactory();
state.applyCondition(factory.getRelationFactory().createRelation(value, factory.getConstFactory().getNull(), NE, false));
}
return notNullable;
}
@Override
public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
myReachable.add(instruction);
DfaValue dfaRight = memState.pop();
DfaValue dfaLeft = memState.pop();
final IElementType opSign = instruction.getOperationSign();
if (opSign != null) {
DfaInstructionState[] states = handleConstantComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign);
if (states == null) {
states = handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft);
}
if (states != null) {
return states;
}
if (PLUS == opSign) {
memState.push(instruction.getNonNullStringValue(runner.getFactory()));
}
else {
if (instruction instanceof InstanceofInstruction) {
handleInstanceof((InstanceofInstruction)instruction, dfaRight, dfaLeft);
}
memState.push(DfaUnknownValue.getInstance());
}
}
else {
memState.push(DfaUnknownValue.getInstance());
}
instruction.setTrueReachable(); // Not a branching instruction actually.
instruction.setFalseReachable();
return nextInstruction(instruction, runner, memState);
}
@Nullable
private DfaInstructionState[] handleRelationBinop(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue dfaRight, DfaValue dfaLeft) {
DfaValueFactory factory = runner.getFactory();
final Instruction next = runner.getInstruction(instruction.getIndex() + 1);
DfaRelationValue dfaRelation = factory.getRelationFactory().createRelation(dfaLeft, dfaRight, instruction.getOperationSign(), false);
if (dfaRelation == null) {
return null;
}
myCanBeNullInInstanceof.add(instruction);
ArrayList<DfaInstructionState> states = new ArrayList<DfaInstructionState>();
final DfaMemoryState trueCopy = memState.createCopy();
if (trueCopy.applyCondition(dfaRelation)) {
trueCopy.push(factory.getConstFactory().getTrue());
instruction.setTrueReachable();
states.add(new DfaInstructionState(next, trueCopy));
}
//noinspection UnnecessaryLocalVariable
DfaMemoryState falseCopy = memState;
if (falseCopy.applyCondition(dfaRelation.createNegated())) {
falseCopy.push(factory.getConstFactory().getFalse());
instruction.setFalseReachable();
states.add(new DfaInstructionState(next, falseCopy));
if (instruction instanceof InstanceofInstruction && !falseCopy.isNull(dfaLeft)) {
myUsefulInstanceofs.add((InstanceofInstruction)instruction);
}
}
return states.toArray(new DfaInstructionState[states.size()]);
}
public void skipConstantConditionReporting(@Nullable PsiElement anchor) {
ContainerUtil.addIfNotNull(myNotToReportReachability, anchor);
}
private void handleInstanceof(InstanceofInstruction instruction, DfaValue dfaRight, DfaValue dfaLeft) {
if (dfaLeft instanceof DfaTypeValue && dfaRight instanceof DfaTypeValue) {
if (!((DfaTypeValue)dfaLeft).isNotNull()) {
myCanBeNullInInstanceof.add(instruction);
}
if (((DfaTypeValue)dfaRight).getDfaType().isAssignableFrom(((DfaTypeValue)dfaLeft).getDfaType())) {
return;
}
}
myUsefulInstanceofs.add(instruction);
}
@Nullable
private static DfaInstructionState[] handleConstantComparison(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue dfaRight,
DfaValue dfaLeft, IElementType opSign) {
if (dfaRight instanceof DfaConstValue && dfaLeft instanceof DfaVariableValue) {
Object value = ((DfaConstValue)dfaRight).getValue();
if (value instanceof Number) {
DfaInstructionState[] result = checkComparingWithConstant(instruction, runner, memState, (DfaVariableValue)dfaLeft, opSign,
((Number)value).doubleValue());
if (result != null) {
return result;
}
}
}
if (dfaRight instanceof DfaVariableValue && dfaLeft instanceof DfaConstValue) {
return handleConstantComparison(instruction, runner, memState, dfaLeft, dfaRight, DfaRelationValue.getSymmetricOperation(opSign));
}
if (EQEQ != opSign && NE != opSign) {
return null;
}
if (dfaLeft instanceof DfaConstValue && dfaRight instanceof DfaConstValue ||
dfaLeft == runner.getFactory().getConstFactory().getContractFail() ||
dfaRight == runner.getFactory().getConstFactory().getContractFail()) {
boolean negated = (NE == opSign) ^ (DfaMemoryStateImpl.isNaN(dfaLeft) || DfaMemoryStateImpl.isNaN(dfaRight));
if (dfaLeft == dfaRight ^ negated) {
return alwaysTrue(instruction, runner, memState);
}
return alwaysFalse(instruction, runner, memState);
}
return null;
}
@Nullable
private static DfaInstructionState[] checkComparingWithConstant(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaVariableValue var,
IElementType opSign, double comparedWith) {
DfaConstValue knownConstantValue = memState.getConstantValue(var);
Object knownValue = knownConstantValue == null ? null : knownConstantValue.getValue();
if (knownValue instanceof Number) {
double knownDouble = ((Number)knownValue).doubleValue();
return checkComparisonWithKnownRange(instruction, runner, memState, opSign, comparedWith, knownDouble, knownDouble);
}
PsiType varType = var.getVariableType();
if (!(varType instanceof PsiPrimitiveType)) return null;
if (varType == PsiType.FLOAT || varType == PsiType.DOUBLE) return null;
double minValue = varType == PsiType.BYTE ? Byte.MIN_VALUE :
varType == PsiType.SHORT ? Short.MIN_VALUE :
varType == PsiType.INT ? Integer.MIN_VALUE :
varType == PsiType.CHAR ? Character.MIN_VALUE :
Long.MIN_VALUE;
double maxValue = varType == PsiType.BYTE ? Byte.MAX_VALUE :
varType == PsiType.SHORT ? Short.MAX_VALUE :
varType == PsiType.INT ? Integer.MAX_VALUE :
varType == PsiType.CHAR ? Character.MAX_VALUE :
Long.MAX_VALUE;
return checkComparisonWithKnownRange(instruction, runner, memState, opSign, comparedWith, minValue, maxValue);
}
@Nullable
private static DfaInstructionState[] checkComparisonWithKnownRange(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
IElementType opSign,
double comparedWith,
double rangeMin,
double rangeMax) {
if (comparedWith < rangeMin || comparedWith > rangeMax) {
if (opSign == EQEQ) return alwaysFalse(instruction, runner, memState);
if (opSign == NE) return alwaysTrue(instruction, runner, memState);
}
if (opSign == LT && comparedWith <= rangeMin) return alwaysFalse(instruction, runner, memState);
if (opSign == LT && comparedWith > rangeMax) return alwaysTrue(instruction, runner, memState);
if (opSign == LE && comparedWith >= rangeMax) return alwaysTrue(instruction, runner, memState);
if (opSign == GT && comparedWith >= rangeMax) return alwaysFalse(instruction, runner, memState);
if (opSign == GT && comparedWith < rangeMin) return alwaysTrue(instruction, runner, memState);
if (opSign == GE && comparedWith <= rangeMin) return alwaysTrue(instruction, runner, memState);
return null;
}
private static DfaInstructionState[] alwaysFalse(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.push(runner.getFactory().getConstFactory().getFalse());
instruction.setFalseReachable();
return nextInstruction(instruction, runner, memState);
}
private static DfaInstructionState[] alwaysTrue(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
memState.push(runner.getFactory().getConstFactory().getTrue());
instruction.setTrueReachable();
return nextInstruction(instruction, runner, memState);
}
public boolean isInstanceofRedundant(InstanceofInstruction instruction) {
return !myUsefulInstanceofs.contains(instruction) && !instruction.isConditionConst() && myReachable.contains(instruction);
}
public boolean canBeNull(BinopInstruction instruction) {
return myCanBeNullInInstanceof.contains(instruction);
}
public boolean silenceConstantCondition(@Nullable PsiElement element) {
for (PsiElement skipped : myNotToReportReachability) {
if (PsiTreeUtil.isAncestor(element, skipped, false)) {
return true;
}
}
if (PsiTreeUtil.findChildOfType(element, PsiAssignmentExpression.class) != null) {
return true;
}
return false;
}
}