blob: 442ee43c4794275d33dc5844d47cef68cbc7a6f6 [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.
*/
/*
* Created by IntelliJ IDEA.
* User: max
* Date: Jan 26, 2002
* Time: 10:48:52 PM
* To change template for new class use
* Code Style | Class Templates options (Tools | IDE Options).
*/
package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.*;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class MethodCallInstruction extends Instruction {
@Nullable private final PsiCallExpression myCall;
@Nullable private final PsiType myType;
@NotNull private final PsiExpression[] myArgs;
private final boolean myShouldFlushFields;
@NotNull private final PsiExpression myContext;
@Nullable private final PsiMethod myTargetMethod;
private final List<MethodContract> myContracts;
private final MethodType myMethodType;
@Nullable private final DfaValue myPrecalculatedReturnValue;
private final boolean myVarArgCall;
private final Map<PsiExpression, Nullness> myArgRequiredNullability;
public enum MethodType {
BOXING, UNBOXING, REGULAR_METHOD_CALL, CAST
}
public MethodCallInstruction(@NotNull PsiExpression context, MethodType methodType, @Nullable PsiType resultType) {
myContext = context;
myContracts = Collections.emptyList();
myMethodType = methodType;
myCall = null;
myArgs = PsiExpression.EMPTY_ARRAY;
myType = resultType;
myShouldFlushFields = false;
myPrecalculatedReturnValue = null;
myTargetMethod = null;
myVarArgCall = false;
myArgRequiredNullability = Collections.emptyMap();
}
public MethodCallInstruction(@NotNull PsiCallExpression call, @Nullable DfaValue precalculatedReturnValue, List<MethodContract> contracts) {
myContext = call;
myContracts = contracts;
myMethodType = MethodType.REGULAR_METHOD_CALL;
myCall = call;
final PsiExpressionList argList = call.getArgumentList();
myArgs = argList != null ? argList.getExpressions() : PsiExpression.EMPTY_ARRAY;
myType = myCall.getType();
JavaResolveResult result = call.resolveMethodGenerics();
myTargetMethod = (PsiMethod)result.getElement();
PsiSubstitutor substitutor = result.getSubstitutor();
if (argList != null && myTargetMethod != null) {
PsiParameter[] parameters = myTargetMethod.getParameterList().getParameters();
myVarArgCall = isVarArgCall(myTargetMethod, substitutor, myArgs, parameters);
myArgRequiredNullability = calcArgRequiredNullability(substitutor, parameters);
} else {
myVarArgCall = false;
myArgRequiredNullability = Collections.emptyMap();
}
myShouldFlushFields = !(call instanceof PsiNewExpression && myType != null && myType.getArrayDimensions() > 0) && !isPureCall();
myPrecalculatedReturnValue = precalculatedReturnValue;
}
private Map<PsiExpression, Nullness> calcArgRequiredNullability(PsiSubstitutor substitutor, PsiParameter[] parameters) {
int checkedCount = Math.min(myArgs.length, parameters.length) - (myVarArgCall ? 1 : 0);
Map<PsiExpression, Nullness> map = ContainerUtil.newHashMap();
for (int i = 0; i < checkedCount; i++) {
map.put(myArgs[i], DfaPsiUtil.getElementNullability(substitutor.substitute(parameters[i].getType()), parameters[i]));
}
return map;
}
private static boolean isVarArgCall(PsiMethod method, PsiSubstitutor substitutor, PsiExpression[] args, PsiParameter[] parameters) {
if (!method.isVarArgs()) {
return false;
}
int argCount = args.length;
int paramCount = parameters.length;
if (argCount > paramCount) {
return true;
}
if (paramCount > 0 && argCount == paramCount) {
PsiType lastArgType = args[argCount - 1].getType();
if (lastArgType != null && !substitutor.substitute(parameters[paramCount - 1].getType()).isAssignableFrom(lastArgType)) {
return true;
}
}
return false;
}
private boolean isPureCall() {
if (myTargetMethod == null) return false;
PsiAnnotation anno = ControlFlowAnalyzer.findContractAnnotation(myTargetMethod);
if (anno != null && Boolean.TRUE.equals(AnnotationUtil.getBooleanAttributeValue(anno, "pure"))) return true;
return PropertyUtil.isSimplePropertyGetter(myTargetMethod);
}
@Nullable
public PsiType getResultType() {
return myType;
}
@NotNull
public PsiExpression[] getArgs() {
return myArgs;
}
public MethodType getMethodType() {
return myMethodType;
}
public boolean shouldFlushFields() {
return myShouldFlushFields;
}
@Nullable
public PsiMethod getTargetMethod() {
return myTargetMethod;
}
public boolean isVarArgCall() {
return myVarArgCall;
}
@Nullable
public Nullness getArgRequiredNullability(@NotNull PsiExpression arg) {
return myArgRequiredNullability.get(arg);
}
public List<MethodContract> getContracts() {
return myContracts;
}
@Override
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
return visitor.visitMethodCall(this, runner, stateBefore);
}
@Nullable
public PsiCallExpression getCallExpression() {
return myCall;
}
@NotNull
public PsiExpression getContext() {
return myContext;
}
@Nullable
public DfaValue getPrecalculatedReturnValue() {
return myPrecalculatedReturnValue;
}
public String toString() {
return myMethodType == MethodType.UNBOXING
? "UNBOX"
: myMethodType == MethodType.BOXING
? "BOX" :
"CALL_METHOD: " + (myCall == null ? "null" : myCall.getText());
}
}