/* * Copyright 2000-2013 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.codeInsight.NullableNotNullManager; import com.intellij.codeInspection.dataFlow.instructions.Instruction; import com.intellij.codeInspection.dataFlow.instructions.ReturnInstruction; import com.intellij.openapi.util.Ref; import com.intellij.psi.*; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.NullableFunction; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; public class DfaPsiUtil { public static boolean isFinalField(PsiVariable var) { return var.hasModifierProperty(PsiModifier.FINAL) && !var.hasModifierProperty(PsiModifier.TRANSIENT) && var instanceof PsiField; } static PsiElement getEnclosingCodeBlock(final PsiVariable variable, final PsiElement context) { PsiElement codeBlock; if (variable instanceof PsiParameter) { codeBlock = ((PsiParameter)variable).getDeclarationScope(); if (codeBlock instanceof PsiMethod) { codeBlock = ((PsiMethod)codeBlock).getBody(); } } else if (variable instanceof PsiLocalVariable) { codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); } else { codeBlock = getTopmostBlockInSameClass(context); } while (codeBlock != null) { PsiAnonymousClass anon = PsiTreeUtil.getParentOfType(codeBlock, PsiAnonymousClass.class); if (anon == null) break; codeBlock = PsiTreeUtil.getParentOfType(anon, PsiCodeBlock.class); } return codeBlock; } @NotNull public static Nullness getElementNullability(@Nullable PsiType resultType, @Nullable PsiModifierListOwner owner) { if (owner == null) { return Nullness.UNKNOWN; } if (NullableNotNullManager.isNullable(owner)) { return Nullness.NULLABLE; } if (NullableNotNullManager.isNotNull(owner)) { return Nullness.NOT_NULL; } if (resultType != null) { NullableNotNullManager nnn = NullableNotNullManager.getInstance(owner.getProject()); for (PsiAnnotation annotation : resultType.getAnnotations()) { String qualifiedName = annotation.getQualifiedName(); if (nnn.getNullables().contains(qualifiedName)) { return Nullness.NULLABLE; } if (nnn.getNotNulls().contains(qualifiedName)) { return Nullness.NOT_NULL; } } } return Nullness.UNKNOWN; } public static boolean isInitializedNotNull(PsiField field) { PsiClass containingClass = field.getContainingClass(); if (containingClass == null) return false; PsiMethod[] constructors = containingClass.getConstructors(); if (constructors.length == 0) return false; for (PsiMethod method : constructors) { if (!getNotNullInitializedFields(method, containingClass).contains(field)) { return false; } } return true; } private static Set getNotNullInitializedFields(final PsiMethod constructor, final PsiClass containingClass) { final PsiCodeBlock body = constructor.getBody(); if (body == null) return Collections.emptySet(); return CachedValuesManager.getCachedValue(constructor, new CachedValueProvider>() { @Nullable @Override public Result> compute() { final PsiCodeBlock body = constructor.getBody(); final Map map = ContainerUtil.newHashMap(); final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(body) { boolean shouldCheck; @Override protected void prepareAnalysis(@NotNull PsiElement psiBlock, Iterable initialStates) { super.prepareAnalysis(psiBlock, initialStates); shouldCheck = psiBlock == body; } @Override protected DfaInstructionState[] acceptInstruction(InstructionVisitor visitor, DfaInstructionState instructionState) { if (shouldCheck) { Instruction instruction = instructionState.getInstruction(); if (instruction instanceof ReturnInstruction && !((ReturnInstruction)instruction).isViaException()) { for (PsiField field : containingClass.getFields()) { if (!instructionState.getMemoryState().isNotNull(getFactory().getVarFactory().createVariableValue(field, false))) { map.put(field, false); } else if (!map.containsKey(field)) { map.put(field, true); } } } } return super.acceptInstruction(visitor, instructionState); } }; final RunnerResult rc = dfaRunner.analyzeMethod(body, new StandardInstructionVisitor()); Set notNullFields = ContainerUtil.newHashSet(); if (rc == RunnerResult.OK) { for (PsiField field : map.keySet()) { if (map.get(field)) { notNullFields.add(field); } } } return Result.create(notNullFields, constructor, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); } }); } public static List findAllConstructorInitializers(PsiField field) { final List result = ContainerUtil.createLockFreeCopyOnWriteList(); ContainerUtil.addIfNotNull(result, field.getInitializer()); final PsiClass containingClass = field.getContainingClass(); if (containingClass != null && !(containingClass instanceof PsiCompiledElement)) { result.addAll(getAllConstructorFieldInitializers(containingClass).get(field)); } return result; } private static MultiMap getAllConstructorFieldInitializers(final PsiClass psiClass) { if (psiClass instanceof PsiCompiledElement) { return MultiMap.EMPTY; } return CachedValuesManager.getCachedValue(psiClass, new CachedValueProvider>() { @Nullable @Override public Result> compute() { final Set fieldNames = ContainerUtil.newHashSet(); for (PsiField field : psiClass.getFields()) { ContainerUtil.addIfNotNull(fieldNames, field.getName()); } final MultiMap result = new MultiMap(); JavaRecursiveElementWalkingVisitor visitor = new JavaRecursiveElementWalkingVisitor() { @Override public void visitAssignmentExpression(PsiAssignmentExpression assignment) { super.visitAssignmentExpression(assignment); PsiExpression lExpression = assignment.getLExpression(); PsiExpression rExpression = assignment.getRExpression(); if (rExpression != null && lExpression instanceof PsiReferenceExpression && fieldNames.contains(((PsiReferenceExpression)lExpression).getReferenceName())) { PsiElement target = ((PsiReferenceExpression)lExpression).resolve(); if (target instanceof PsiField && ((PsiField)target).getContainingClass() == psiClass) { result.putValue((PsiField)target, rExpression); } } } }; for (PsiMethod constructor : psiClass.getConstructors()) { constructor.accept(visitor); } return Result.create(result, psiClass); } }); } @Nullable public static PsiCodeBlock getTopmostBlockInSameClass(@NotNull PsiElement position) { PsiCodeBlock block = PsiTreeUtil.getParentOfType(position, PsiCodeBlock.class, false, PsiMember.class, PsiFile.class); if (block == null) { return null; } PsiCodeBlock lastBlock = block; while (true) { block = PsiTreeUtil.getParentOfType(block, PsiCodeBlock.class, true, PsiMember.class, PsiFile.class); if (block == null) { return lastBlock; } lastBlock = block; } } @NotNull public static Collection getVariableAssignmentsInFile(@NotNull PsiVariable psiVariable, final boolean literalsOnly, final PsiElement place) { final Ref modificationRef = Ref.create(Boolean.FALSE); final PsiCodeBlock codeBlock = place == null? null : getTopmostBlockInSameClass(place); final int placeOffset = codeBlock != null? place.getTextRange().getStartOffset() : 0; List list = ContainerUtil.mapNotNull( ReferencesSearch.search(psiVariable, new LocalSearchScope(new PsiElement[] {psiVariable.getContainingFile()}, null, true)).findAll(), new NullableFunction() { @Override public PsiExpression fun(final PsiReference psiReference) { if (modificationRef.get()) return null; final PsiElement parent = psiReference.getElement().getParent(); if (parent instanceof PsiAssignmentExpression) { final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent; final IElementType operation = assignmentExpression.getOperationTokenType(); if (assignmentExpression.getLExpression() == psiReference) { if (JavaTokenType.EQ.equals(operation)) { final PsiExpression rValue = assignmentExpression.getRExpression(); if (!literalsOnly || allOperandsAreLiterals(rValue)) { // if there's a codeBlock omit the values assigned later if (codeBlock != null && PsiTreeUtil.isAncestor(codeBlock, parent, true) && placeOffset < parent.getTextRange().getStartOffset()) { return null; } return rValue; } else { modificationRef.set(Boolean.TRUE); } } else if (JavaTokenType.PLUSEQ.equals(operation)) { modificationRef.set(Boolean.TRUE); } } } return null; } }); if (modificationRef.get()) return Collections.emptyList(); PsiExpression initializer = psiVariable.getInitializer(); if (initializer != null && (!literalsOnly || allOperandsAreLiterals(initializer))) { list = ContainerUtil.concat(list, Collections.singletonList(initializer)); } return list; } public static boolean allOperandsAreLiterals(@Nullable final PsiExpression expression) { if (expression == null) return false; if (expression instanceof PsiLiteralExpression) return true; if (expression instanceof PsiPolyadicExpression) { Stack stack = new Stack(); stack.add(expression); while (!stack.isEmpty()) { PsiExpression psiExpression = stack.pop(); if (psiExpression instanceof PsiPolyadicExpression) { PsiPolyadicExpression binaryExpression = (PsiPolyadicExpression)psiExpression; for (PsiExpression op : binaryExpression.getOperands()) { stack.push(op); } } else if (!(psiExpression instanceof PsiLiteralExpression)) { return false; } } return true; } return false; } }