blob: 6bb70010c9ca09bddc0cc6e4874082286ad28f9b [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.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.openapi.util.MultiValuesMap;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FList;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Gregory.Shrago
*/
public class DfaUtil {
@Nullable("null means DFA analysis has failed (too complex to analyze)")
public static Collection<PsiExpression> getCachedVariableValues(@Nullable final PsiVariable variable, @Nullable final PsiElement context) {
if (variable == null || context == null) return Collections.emptyList();
final PsiElement codeBlock = DfaPsiUtil.getEnclosingCodeBlock(variable, context);
if (codeBlock == null) return Collections.emptyList();
final Map<PsiElement, ValuableInstructionVisitor.PlaceResult> value = getCachedPlaceResults(codeBlock);
if (value == null) return null;
ValuableInstructionVisitor.PlaceResult placeResult = value.get(context);
final Collection<FList<PsiExpression>> concatenations = placeResult == null ? null : placeResult.myValues.get(variable);
if (concatenations != null) {
return ContainerUtil.map(concatenations, new Function<FList<PsiExpression>, PsiExpression>() {
@Override
public PsiExpression fun(FList<PsiExpression> expressions) {
return concatenateExpressions(expressions);
}
});
}
return Collections.emptyList();
}
@Nullable("null means DFA analysis has failed (too complex to analyze)")
private static Map<PsiElement, ValuableInstructionVisitor.PlaceResult> getCachedPlaceResults(@NotNull final PsiElement codeBlock) {
return CachedValuesManager.getCachedValue(codeBlock, new CachedValueProvider<Map<PsiElement, ValuableInstructionVisitor.PlaceResult>>() {
@Override
public Result<Map<PsiElement, ValuableInstructionVisitor.PlaceResult>> compute() {
final ValuableInstructionVisitor visitor = new ValuableInstructionVisitor();
RunnerResult runnerResult = new ValuableDataFlowRunner(codeBlock).analyzeMethod(codeBlock, visitor);
return Result.create(runnerResult == RunnerResult.OK ? visitor.myResults : null, codeBlock);
}
});
}
@NotNull
public static Nullness checkNullness(@Nullable final PsiVariable variable, @Nullable final PsiElement context) {
if (variable == null || context == null) return Nullness.UNKNOWN;
final PsiElement codeBlock = DfaPsiUtil.getEnclosingCodeBlock(variable, context);
Map<PsiElement, ValuableInstructionVisitor.PlaceResult> results = codeBlock == null ? null : getCachedPlaceResults(codeBlock);
ValuableInstructionVisitor.PlaceResult placeResult = results == null ? null : results.get(context);
if (placeResult == null) {
return Nullness.UNKNOWN;
}
if (placeResult.myNulls.contains(variable) && !placeResult.myNotNulls.contains(variable)) return Nullness.NULLABLE;
if (placeResult.myNotNulls.contains(variable) && !placeResult.myNulls.contains(variable)) return Nullness.NOT_NULL;
return Nullness.UNKNOWN;
}
@NotNull
public static Collection<? extends PsiElement> getPossibleInitializationElements(final PsiElement qualifierExpression) {
if (qualifierExpression instanceof PsiMethodCallExpression) {
return Collections.singletonList(qualifierExpression);
}
if (qualifierExpression instanceof PsiReferenceExpression) {
final PsiElement targetElement = ((PsiReferenceExpression)qualifierExpression).resolve();
if (!(targetElement instanceof PsiVariable)) {
return Collections.emptyList();
}
final Collection<? extends PsiElement> variableValues = getCachedVariableValues((PsiVariable)targetElement, qualifierExpression);
if (variableValues == null || variableValues.isEmpty()) {
return DfaPsiUtil.getVariableAssignmentsInFile((PsiVariable)targetElement, false, qualifierExpression);
}
return variableValues;
}
if (qualifierExpression instanceof PsiLiteralExpression) {
return Collections.singletonList(qualifierExpression);
}
return Collections.emptyList();
}
@Nullable
static PsiElement getClosureInside(Instruction instruction) {
if (instruction instanceof MethodCallInstruction) {
PsiCallExpression anchor = ((MethodCallInstruction)instruction).getCallExpression();
if (anchor instanceof PsiNewExpression) {
return ((PsiNewExpression)anchor).getAnonymousClass();
}
}
else if (instruction instanceof LambdaInstruction) {
return ((LambdaInstruction)instruction).getLambdaExpression();
}
else if (instruction instanceof EmptyInstruction) {
PsiElement anchor = ((EmptyInstruction)instruction).getAnchor();
if (anchor instanceof PsiClass) {
return anchor;
}
}
return null;
}
private static class ValuableInstructionVisitor extends StandardInstructionVisitor {
final Map<PsiElement, PlaceResult> myResults = ContainerUtil.newHashMap();
static class PlaceResult {
final MultiValuesMap<PsiVariable, FList<PsiExpression>> myValues = new MultiValuesMap<PsiVariable, FList<PsiExpression>>(true);
final Set<PsiVariable> myNulls = new THashSet<PsiVariable>();
final Set<PsiVariable> myNotNulls = new THashSet<PsiVariable>();
}
@Override
public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiExpression place = instruction.getPlace();
if (place != null) {
PlaceResult result = myResults.get(place);
if (result == null) {
myResults.put(place, result = new PlaceResult());
}
final Map<DfaVariableValue,DfaVariableState> map = ((ValuableDataFlowRunner.MyDfaMemoryState)memState).getVariableStates();
for (Map.Entry<DfaVariableValue, DfaVariableState> entry : map.entrySet()) {
ValuableDataFlowRunner.ValuableDfaVariableState state = (ValuableDataFlowRunner.ValuableDfaVariableState)entry.getValue();
DfaVariableValue variableValue = entry.getKey();
final FList<PsiExpression> concatenation = state.myConcatenation;
if (!concatenation.isEmpty() && variableValue.getQualifier() == null) {
PsiModifierListOwner element = variableValue.getPsiVariable();
if (element instanceof PsiVariable) {
result.myValues.put((PsiVariable)element, concatenation);
}
}
}
DfaValue value = instruction.getValue();
if (value instanceof DfaVariableValue && ((DfaVariableValue)value).getQualifier() == null) {
PsiModifierListOwner element = ((DfaVariableValue)value).getPsiVariable();
if (element instanceof PsiVariable) {
if (memState.isNotNull(value)) {
result.myNotNulls.add((PsiVariable)element);
}
if (memState.isNull(value)) {
result.myNulls.add((PsiVariable)element);
}
}
}
}
return super.visitPush(instruction, runner, memState);
}
@Override
public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState _memState) {
final Instruction nextInstruction = runner.getInstruction(instruction.getIndex() + 1);
ValuableDataFlowRunner.MyDfaMemoryState memState = (ValuableDataFlowRunner.MyDfaMemoryState)_memState;
final DfaValue dfaSource = memState.pop();
final DfaValue dfaDest = memState.pop();
if (dfaDest instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue)dfaDest;
final PsiExpression rightValue = instruction.getRExpression();
final PsiElement parent = rightValue == null ? null : rightValue.getParent();
final IElementType type = parent instanceof PsiAssignmentExpression
? ((PsiAssignmentExpression)parent).getOperationTokenType() : JavaTokenType.EQ;
// store current value - to use in case of '+='
final FList<PsiExpression> prevValue = ((ValuableDataFlowRunner.ValuableDfaVariableState)memState.getVariableState(var)).myConcatenation;
memState.setVarValue(var, dfaSource);
// state may have been changed so re-retrieve it
final ValuableDataFlowRunner.ValuableDfaVariableState curState = (ValuableDataFlowRunner.ValuableDfaVariableState)memState.getVariableState(var);
final FList<PsiExpression> curValue = curState.myConcatenation;
final FList<PsiExpression> nextValue;
if (type == JavaTokenType.PLUSEQ && !prevValue.isEmpty()) {
nextValue = prevValue.prepend(rightValue);
}
else {
nextValue = curValue.isEmpty() ? curValue.prepend(rightValue) : curValue;
}
memState.setVariableState(var, curState.withExpression(nextValue));
}
memState.push(dfaDest);
return new DfaInstructionState[]{new DfaInstructionState(nextInstruction, memState)};
}
}
private static PsiExpression concatenateExpressions(FList<PsiExpression> concatenation) {
if (concatenation.size() == 1) {
return concatenation.getHead();
}
String text = StringUtil
.join(ContainerUtil.reverse(new ArrayList<PsiExpression>(concatenation)), new Function<PsiExpression, String>() {
@Override
public String fun(PsiExpression expression) {
return expression.getText();
}
}, "+");
try {
return JavaPsiFacade.getElementFactory(concatenation.getHead().getProject()).createExpressionFromText(text, concatenation.getHead());
}
catch (IncorrectOperationException e) {
return concatenation.getHead();
}
}
}