| /* |
| * 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.AssignInstruction; |
| import com.intellij.codeInspection.dataFlow.instructions.Instruction; |
| import com.intellij.codeInspection.dataFlow.instructions.PushInstruction; |
| import com.intellij.codeInspection.dataFlow.value.DfaValue; |
| import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; |
| import com.intellij.openapi.util.Key; |
| 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.CachedValue; |
| 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 { |
| private static final Key<CachedValue<MultiValuesMap<PsiVariable, PsiExpression>>> DFA_VARIABLE_INFO_KEY = Key.create("DFA_VARIABLE_INFO_KEY"); |
| |
| private DfaUtil() { |
| } |
| |
| private static final MultiValuesMap<PsiVariable, PsiExpression> TOO_COMPLEX = new MultiValuesMap<PsiVariable, PsiExpression>(); |
| @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(); |
| |
| CachedValue<MultiValuesMap<PsiVariable, PsiExpression>> cachedValue = context.getUserData(DFA_VARIABLE_INFO_KEY); |
| if (cachedValue == null) { |
| final PsiElement codeBlock = DfaPsiUtil.getEnclosingCodeBlock(variable, context); |
| cachedValue = CachedValuesManager.getManager(context.getProject()).createCachedValue(new CachedValueProvider<MultiValuesMap<PsiVariable, PsiExpression>>() { |
| @Override |
| public Result<MultiValuesMap<PsiVariable, PsiExpression>> compute() { |
| final MultiValuesMap<PsiVariable, PsiExpression> result; |
| if (codeBlock == null) { |
| result = null; |
| } |
| else { |
| final ValuableInstructionVisitor visitor = new ValuableInstructionVisitor(context); |
| RunnerResult runnerResult = new ValuableDataFlowRunner(codeBlock).analyzeMethod(codeBlock, visitor); |
| if (runnerResult == RunnerResult.OK) { |
| result = visitor.myValues; |
| } |
| else { |
| result = TOO_COMPLEX; |
| } |
| } |
| return new Result<MultiValuesMap<PsiVariable, PsiExpression>>(result, variable); |
| } |
| }, false); |
| context.putUserData(DFA_VARIABLE_INFO_KEY, cachedValue); |
| } |
| final MultiValuesMap<PsiVariable, PsiExpression> value = cachedValue.getValue(); |
| if (value == TOO_COMPLEX) return null; |
| final Collection<PsiExpression> expressions = value == null ? null : value.get(variable); |
| return expressions == null ? Collections.<PsiExpression>emptyList() : expressions; |
| } |
| |
| @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); |
| if (codeBlock == null) { |
| return Nullness.UNKNOWN; |
| } |
| final ValuableInstructionVisitor visitor = new ValuableInstructionVisitor(context); |
| RunnerResult result = new ValuableDataFlowRunner(codeBlock).analyzeMethod(codeBlock, visitor); |
| if (result != RunnerResult.OK) { |
| return Nullness.UNKNOWN; |
| } |
| if (visitor.myNulls.contains(variable) && !visitor.myNotNulls.contains(variable)) return Nullness.NULLABLE; |
| if (visitor.myNotNulls.contains(variable) && !visitor.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(); |
| } |
| |
| private static class ValuableInstructionVisitor extends StandardInstructionVisitor { |
| final MultiValuesMap<PsiVariable, PsiExpression> myValues = new MultiValuesMap<PsiVariable, PsiExpression>(true); |
| final Set<PsiVariable> myNulls = new THashSet<PsiVariable>(); |
| final Set<PsiVariable> myNotNulls = new THashSet<PsiVariable>(); |
| private final PsiElement myContext; |
| |
| public ValuableInstructionVisitor(@NotNull PsiElement context) { |
| myContext = context; |
| } |
| |
| @Override |
| public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { |
| if (myContext == instruction.getPlace()) { |
| 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) { |
| PsiExpression psiExpression; |
| if (concatenation.size() == 1) { |
| psiExpression = concatenation.getHead(); |
| } else { |
| String text = StringUtil.join(ContainerUtil.reverse(new ArrayList<PsiExpression>(concatenation)), new Function<PsiExpression, String>() { |
| @Override |
| public String fun(PsiExpression expression) { |
| return expression.getText(); |
| } |
| }, "+"); |
| try { |
| psiExpression = JavaPsiFacade.getElementFactory(element.getProject()).createExpressionFromText(text, concatenation.getHead()); |
| } |
| catch (IncorrectOperationException e) { |
| psiExpression = concatenation.getHead(); |
| } |
| } |
| |
| myValues.put((PsiVariable)element, psiExpression); |
| } |
| } |
| } |
| DfaValue value = instruction.getValue(); |
| if (value instanceof DfaVariableValue && ((DfaVariableValue)value).getQualifier() == null) { |
| PsiModifierListOwner element = ((DfaVariableValue)value).getPsiVariable(); |
| if (element instanceof PsiVariable) { |
| if (memState.isNotNull((DfaVariableValue)value)) { |
| myNotNulls.add((PsiVariable)element); |
| } |
| if (memState.isNull(value)) { |
| 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)}; |
| } |
| } |
| } |