| /* |
| * Copyright 2000-2014 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.codeInsight.daemon.impl.analysis; |
| |
| import com.intellij.codeInsight.daemon.JavaErrorMessages; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfo; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfoType; |
| import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; |
| import com.intellij.codeInsight.intention.QuickFixFactory; |
| import com.intellij.openapi.project.IndexNotReadyException; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.controlFlow.*; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.FileTypeUtils; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.util.Processor; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @author cdr |
| * @since Aug 8, 2002 |
| */ |
| public class HighlightControlFlowUtil { |
| private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); |
| |
| private HighlightControlFlowUtil() { } |
| |
| @Nullable |
| public static HighlightInfo checkMissingReturnStatement(PsiCodeBlock body, PsiType returnType) { |
| if (body == null || returnType == null || PsiType.VOID.equals(returnType.getDeepComponentType())) { |
| return null; |
| } |
| |
| // do not compute constant expressions for if() statement condition |
| // see JLS 14.20 Unreachable Statements |
| try { |
| ControlFlow controlFlow = getControlFlowNoConstantEvaluate(body); |
| if (!ControlFlowUtil.returnPresent(controlFlow)) { |
| PsiJavaToken rBrace = body.getRBrace(); |
| PsiElement context = rBrace == null ? body.getLastChild() : rBrace; |
| String message = JavaErrorMessages.message("missing.return.statement"); |
| HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(message).create(); |
| PsiElement parent = body.getParent(); |
| if (parent instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)parent; |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddReturnFix(method)); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.VOID, true)); |
| } |
| return info; |
| } |
| } |
| catch (AnalysisCanceledException ignored) { } |
| |
| return null; |
| } |
| |
| public static ControlFlow getControlFlowNoConstantEvaluate(PsiElement body) throws AnalysisCanceledException { |
| LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); |
| return ControlFlowFactory.getInstance(body.getProject()).getControlFlow(body, policy, false); |
| } |
| |
| private static ControlFlow getControlFlow(PsiElement context) throws AnalysisCanceledException { |
| LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); |
| return ControlFlowFactory.getInstance(context.getProject()).getControlFlow(context, policy); |
| } |
| |
| public static HighlightInfo checkUnreachableStatement(PsiCodeBlock codeBlock) { |
| if (codeBlock == null) return null; |
| // do not compute constant expressions for if() statement condition |
| // see JLS 14.20 Unreachable Statements |
| try { |
| final ControlFlow controlFlow = getControlFlowNoConstantEvaluate(codeBlock); |
| final PsiElement unreachableStatement = ControlFlowUtil.getUnreachableStatement(controlFlow); |
| if (unreachableStatement != null) { |
| String description = JavaErrorMessages.message("unreachable.statement"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(unreachableStatement).descriptionAndTooltip(description).create(); |
| } |
| } |
| catch (AnalysisCanceledException e) { |
| // incomplete code |
| } |
| catch (IndexNotReadyException ignored) { |
| } |
| return null; |
| } |
| |
| private static boolean isFinalFieldInitialized(PsiField field) { |
| if (field.hasInitializer()) return true; |
| final boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC); |
| final PsiClass aClass = field.getContainingClass(); |
| if (aClass != null) { |
| // field might be assigned in the other field initializers |
| if (isFieldInitializedInOtherFieldInitializer(aClass, field, isFieldStatic)) return true; |
| } |
| final PsiClassInitializer[] initializers; |
| if (aClass != null) { |
| initializers = aClass.getInitializers(); |
| } |
| else { |
| return false; |
| } |
| for (PsiClassInitializer initializer : initializers) { |
| if (initializer.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic |
| && variableDefinitelyAssignedIn(field, initializer.getBody())) { |
| return true; |
| } |
| } |
| if (isFieldStatic) { |
| return false; |
| } |
| else { |
| // instance field should be initialized at the end of the each constructor |
| final PsiMethod[] constructors = aClass.getConstructors(); |
| |
| if (constructors.length == 0) return false; |
| nextConstructor: |
| for (PsiMethod constructor : constructors) { |
| PsiCodeBlock ctrBody = constructor.getBody(); |
| if (ctrBody == null) return false; |
| final List<PsiMethod> redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); |
| for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { |
| PsiMethod redirectedConstructor = redirectedConstructors.get(j); |
| final PsiCodeBlock body = redirectedConstructor.getBody(); |
| if (body != null && variableDefinitelyAssignedIn(field, body)) continue nextConstructor; |
| } |
| if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody)) { |
| continue; |
| } |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| private static boolean isFieldInitializedInOtherFieldInitializer(final PsiClass aClass, |
| PsiField field, |
| final boolean fieldStatic) { |
| PsiField[] fields = aClass.getFields(); |
| for (PsiField psiField : fields) { |
| if (psiField != field |
| && psiField.hasModifierProperty(PsiModifier.STATIC) == fieldStatic |
| && variableDefinitelyAssignedIn(field, psiField)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean isRecursivelyCalledConstructor(PsiMethod constructor) { |
| final JavaHighlightUtil.ConstructorVisitorInfo info = new JavaHighlightUtil.ConstructorVisitorInfo(); |
| JavaHighlightUtil.visitConstructorChain(constructor, info); |
| if (info.recursivelyCalledConstructor == null) return false; |
| // our constructor is reached from some other constructor by constructor chain |
| return info.visitedConstructors.indexOf(info.recursivelyCalledConstructor) <= |
| info.visitedConstructors.indexOf(constructor); |
| } |
| |
| public static boolean isAssigned(final PsiParameter parameter) { |
| ParamWriteProcessor processor = new ParamWriteProcessor(); |
| ReferencesSearch.search(parameter, new LocalSearchScope(parameter.getDeclarationScope()), true).forEach(processor); |
| return processor.isWriteRefFound(); |
| } |
| |
| private static class ParamWriteProcessor implements Processor<PsiReference> { |
| private volatile boolean myIsWriteRefFound = false; |
| @Override |
| public boolean process(PsiReference reference) { |
| final PsiElement element = reference.getElement(); |
| if (element instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression)element)) { |
| myIsWriteRefFound = true; |
| return false; |
| } |
| return true; |
| } |
| |
| public boolean isWriteRefFound() { |
| return myIsWriteRefFound; |
| } |
| } |
| |
| /** |
| * see JLS chapter 16 |
| * @return true if variable assigned (maybe more than once) |
| */ |
| private static boolean variableDefinitelyAssignedIn(PsiVariable variable, PsiElement context) { |
| try { |
| ControlFlow controlFlow = getControlFlow(context); |
| return ControlFlowUtil.isVariableDefinitelyAssigned(variable, controlFlow); |
| } |
| catch (AnalysisCanceledException e) { |
| return false; |
| } |
| } |
| |
| private static boolean variableDefinitelyNotAssignedIn(PsiVariable variable, PsiElement context) { |
| try { |
| ControlFlow controlFlow = getControlFlow(context); |
| return ControlFlowUtil.isVariableDefinitelyNotAssigned(variable, controlFlow); |
| } |
| catch (AnalysisCanceledException e) { |
| return false; |
| } |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkFinalFieldInitialized(PsiField field) { |
| if (!field.hasModifierProperty(PsiModifier.FINAL)) return null; |
| if (isFinalFieldInitialized(field)) return null; |
| |
| String description = JavaErrorMessages.message("variable.not.initialized", field.getName()); |
| TextRange range = HighlightNamesUtil.getFieldDeclarationTextRange(field); |
| HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), QUICK_FIX_FACTORY.createCreateConstructorParameterFromFieldFix(field)); |
| QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), QUICK_FIX_FACTORY.createInitializeFinalFieldInConstructorFix(field)); |
| final PsiClass containingClass = field.getContainingClass(); |
| if (containingClass != null && !containingClass.isInterface()) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(field, PsiModifier.FINAL, false, false)); |
| } |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddVariableInitializerFix(field)); |
| return highlightInfo; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkVariableInitializedBeforeUsage(PsiReferenceExpression expression, |
| PsiVariable variable, |
| Map<PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems, @NotNull PsiFile containingFile) { |
| if (variable instanceof ImplicitVariable) return null; |
| if (!PsiUtil.isAccessedForReading(expression)) return null; |
| int startOffset = expression.getTextRange().getStartOffset(); |
| final PsiElement topBlock; |
| if (variable.hasInitializer()) { |
| topBlock = PsiUtil.getVariableCodeBlock(variable, variable); |
| if (topBlock == null) return null; |
| } |
| else { |
| PsiElement scope = variable instanceof PsiField |
| ? variable.getContainingFile() |
| : variable.getParent() != null ? variable.getParent().getParent() : null; |
| if (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchStatement) { |
| scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class); |
| } |
| |
| topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile ? scope : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); |
| if (variable instanceof PsiField) { |
| // non final field already initialized with default value |
| if (!variable.hasModifierProperty(PsiModifier.FINAL)) return null; |
| // final field may be initialized in ctor or class initializer only |
| // if we're inside non-ctr method, skip it |
| if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null |
| && HighlightUtil.findEnclosingFieldInitializer(expression) == null) { |
| return null; |
| } |
| if (topBlock == null) return null; |
| final PsiElement parent = topBlock.getParent(); |
| // access to final fields from inner classes always allowed |
| if (inInnerClass(expression, ((PsiField)variable).getContainingClass(),containingFile)) return null; |
| final PsiCodeBlock block; |
| final PsiClass aClass; |
| if (parent instanceof PsiMethod) { |
| PsiMethod constructor = (PsiMethod)parent; |
| if (!containingFile.getManager().areElementsEquivalent(constructor.getContainingClass(), ((PsiField)variable).getContainingClass())) return null; |
| // static variables already initialized in class initializers |
| if (variable.hasModifierProperty(PsiModifier.STATIC)) return null; |
| // as a last chance, field may be initialized in this() call |
| final List<PsiMethod> redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); |
| for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { |
| PsiMethod redirectedConstructor = redirectedConstructors.get(j); |
| // variable must be initialized before its usage |
| //??? |
| //if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue; |
| if (redirectedConstructor.getBody() != null |
| && variableDefinitelyAssignedIn(variable, redirectedConstructor.getBody())) { |
| return null; |
| } |
| } |
| block = constructor.getBody(); |
| aClass = constructor.getContainingClass(); |
| } |
| else if (parent instanceof PsiClassInitializer) { |
| final PsiClassInitializer classInitializer = (PsiClassInitializer)parent; |
| if (!containingFile.getManager().areElementsEquivalent(classInitializer.getContainingClass(), ((PsiField)variable).getContainingClass())) return null; |
| block = classInitializer.getBody(); |
| aClass = classInitializer.getContainingClass(); |
| } |
| else { |
| // field reference outside code block |
| // check variable initialized before its usage |
| final PsiField field = (PsiField)variable; |
| |
| aClass = field.getContainingClass(); |
| if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, field, field.hasModifierProperty(PsiModifier.STATIC))) { |
| return null; |
| } |
| final PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class); |
| if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) { |
| startOffset = 0; |
| } |
| block = null; |
| // initializers will be checked later |
| final PsiMethod[] constructors = aClass.getConstructors(); |
| for (PsiMethod constructor : constructors) { |
| // variable must be initialized before its usage |
| if (startOffset < constructor.getTextRange().getStartOffset()) continue; |
| if (constructor.getBody() != null |
| && variableDefinitelyAssignedIn(variable, constructor.getBody())) { |
| return null; |
| } |
| // as a last chance, field may be initialized in this() call |
| final List<PsiMethod> redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); |
| for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { |
| PsiMethod redirectedConstructor = redirectedConstructors.get(j); |
| // variable must be initialized before its usage |
| if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue; |
| if (redirectedConstructor.getBody() != null |
| && variableDefinitelyAssignedIn(variable, redirectedConstructor.getBody())) { |
| return null; |
| } |
| } |
| } |
| } |
| |
| if (aClass != null) { |
| // field may be initialized in class initializer |
| final PsiClassInitializer[] initializers = aClass.getInitializers(); |
| for (PsiClassInitializer initializer : initializers) { |
| PsiCodeBlock body = initializer.getBody(); |
| if (body == block) break; |
| // variable referenced in initializer must be initialized in initializer preceding assignment |
| // variable referenced in field initializer or in class initializer |
| boolean shouldCheckInitializerOrder = block == null || block.getParent() instanceof PsiClassInitializer; |
| if (shouldCheckInitializerOrder && startOffset < initializer.getTextRange().getStartOffset()) continue; |
| if (initializer.hasModifierProperty(PsiModifier.STATIC) |
| == variable.hasModifierProperty(PsiModifier.STATIC)) { |
| if (variableDefinitelyAssignedIn(variable, body)) return null; |
| } |
| } |
| } |
| } |
| } |
| if (topBlock == null) return null; |
| Collection<PsiReferenceExpression> codeBlockProblems = uninitializedVarProblems.get(topBlock); |
| if (codeBlockProblems == null) { |
| try { |
| final ControlFlow controlFlow = getControlFlow(topBlock); |
| codeBlockProblems = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow); |
| } |
| catch (AnalysisCanceledException e) { |
| codeBlockProblems = Collections.emptyList(); |
| } |
| catch (IndexNotReadyException e) { |
| codeBlockProblems = Collections.emptyList(); |
| } |
| uninitializedVarProblems.put(topBlock, codeBlockProblems); |
| } |
| if (codeBlockProblems.contains(expression)) { |
| final String name = expression.getElement().getText(); |
| String description = JavaErrorMessages.message("variable.not.initialized", name); |
| HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddVariableInitializerFix(variable)); |
| if (variable instanceof PsiField) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, |
| QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); |
| } |
| return highlightInfo; |
| } |
| |
| return null; |
| } |
| |
| private static boolean inInnerClass(PsiElement element, PsiClass containingClass, @NotNull PsiFile containingFile) { |
| while (element != null) { |
| if (element instanceof PsiClass) return !containingFile.getManager().areElementsEquivalent(element, containingClass); |
| element = element.getParent(); |
| } |
| return false; |
| } |
| |
| public static boolean isReassigned(PsiVariable variable, |
| Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) { |
| if (variable instanceof PsiLocalVariable) { |
| final PsiElement parent = variable.getParent(); |
| if (parent == null) return false; |
| final PsiElement declarationScope = parent.getParent(); |
| Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, declarationScope); |
| return codeBlockProblems.contains(new ControlFlowUtil.VariableInfo(variable, null)); |
| } |
| else if (variable instanceof PsiParameter) { |
| final PsiParameter parameter = (PsiParameter)variable; |
| return isAssigned(parameter); |
| } |
| else { |
| return false; |
| } |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkFinalVariableMightAlreadyHaveBeenAssignedTo(PsiVariable variable, |
| PsiReferenceExpression expression, |
| Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) { |
| if (!PsiUtil.isAccessedForWriting(expression)) return null; |
| |
| final PsiElement scope = variable instanceof PsiField ? variable.getParent() : |
| variable.getParent() == null ? null : variable.getParent().getParent(); |
| PsiElement codeBlock = PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); |
| if (codeBlock == null) return null; |
| Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, codeBlock); |
| |
| boolean alreadyAssigned = false; |
| for (ControlFlowUtil.VariableInfo variableInfo : codeBlockProblems) { |
| if (variableInfo.expression == expression) { |
| alreadyAssigned = true; |
| break; |
| } |
| } |
| |
| if (!alreadyAssigned) { |
| if (!(variable instanceof PsiField)) return null; |
| final PsiField field = (PsiField)variable; |
| final PsiClass aClass = field.getContainingClass(); |
| if (aClass == null) return null; |
| // field can get assigned in other field initializers |
| final PsiField[] fields = aClass.getFields(); |
| boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC); |
| for (PsiField psiField : fields) { |
| PsiExpression initializer = psiField.getInitializer(); |
| if (psiField != field |
| && psiField.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic |
| && initializer != null |
| && initializer != codeBlock |
| && !variableDefinitelyNotAssignedIn(field, initializer)) { |
| alreadyAssigned = true; |
| break; |
| } |
| } |
| |
| if (!alreadyAssigned) { |
| // field can get assigned in class initializers |
| final PsiMember enclosingConstructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); |
| if (enclosingConstructorOrInitializer == null |
| || !aClass.getManager().areElementsEquivalent(enclosingConstructorOrInitializer.getContainingClass(), aClass)) { |
| return null; |
| } |
| final PsiClassInitializer[] initializers = aClass.getInitializers(); |
| for (PsiClassInitializer initializer : initializers) { |
| if (initializer.hasModifierProperty(PsiModifier.STATIC) |
| == field.hasModifierProperty(PsiModifier.STATIC)) { |
| final PsiCodeBlock body = initializer.getBody(); |
| if (body == codeBlock) return null; |
| try { |
| final ControlFlow controlFlow = getControlFlow(body); |
| if (!ControlFlowUtil.isVariableDefinitelyNotAssigned(field, controlFlow)) { |
| alreadyAssigned = true; |
| break; |
| } |
| } |
| catch (AnalysisCanceledException e) { |
| // incomplete code |
| return null; |
| } |
| } |
| } |
| } |
| |
| if (!alreadyAssigned |
| && !field.hasModifierProperty(PsiModifier.STATIC)) { |
| // then check if instance field already assigned in other constructor |
| final PsiMethod ctr = codeBlock.getParent() instanceof PsiMethod ? |
| (PsiMethod)codeBlock.getParent() : null; |
| // assignment to final field in several constructors threatens us only if these are linked (there is this() call in the beginning) |
| final List<PsiMethod> redirectedConstructors = ctr != null && ctr.isConstructor() ? JavaHighlightUtil.getChainedConstructors(ctr) : null; |
| for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { |
| PsiMethod redirectedConstructor = redirectedConstructors.get(j); |
| if (redirectedConstructor.getBody() != null && |
| variableDefinitelyAssignedIn(variable, redirectedConstructor.getBody())) { |
| alreadyAssigned = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (alreadyAssigned) { |
| String description = JavaErrorMessages.message("variable.already.assigned", variable.getName()); |
| final HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, |
| QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeferFinalAssignmentFix(variable, expression)); |
| return highlightInfo; |
| } |
| |
| return null; |
| } |
| |
| @NotNull |
| public static Collection<ControlFlowUtil.VariableInfo> getFinalVariableProblemsInBlock(Map<PsiElement,Collection<ControlFlowUtil.VariableInfo>> finalVarProblems, PsiElement codeBlock) { |
| Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = finalVarProblems.get(codeBlock); |
| if (codeBlockProblems == null) { |
| try { |
| final ControlFlow controlFlow = getControlFlow(codeBlock); |
| codeBlockProblems = ControlFlowUtil.getInitializedTwice(controlFlow); |
| } |
| catch (AnalysisCanceledException e) { |
| codeBlockProblems = Collections.emptyList(); |
| } |
| finalVarProblems.put(codeBlock, codeBlockProblems); |
| } |
| return codeBlockProblems; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkFinalVariableInitializedInLoop(PsiReferenceExpression expression, PsiElement resolved) { |
| if (ControlFlowUtil.isVariableAssignedInLoop(expression, resolved)) { |
| String description = JavaErrorMessages.message("variable.assigned.in.loop", ((PsiVariable)resolved).getName()); |
| final HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, |
| QUICK_FIX_FACTORY.createModifierListFix((PsiVariable)resolved, PsiModifier.FINAL, false, false)); |
| return highlightInfo; |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkCannotWriteToFinal(PsiExpression expression, @NotNull PsiFile containingFile) { |
| PsiReferenceExpression reference = null; |
| boolean readBeforeWrite = false; |
| if (expression instanceof PsiAssignmentExpression) { |
| final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression; |
| final PsiExpression left = PsiUtil.skipParenthesizedExprDown(assignmentExpression.getLExpression()); |
| if (left instanceof PsiReferenceExpression) { |
| reference = (PsiReferenceExpression)left; |
| } |
| readBeforeWrite = assignmentExpression.getOperationTokenType() != JavaTokenType.EQ; |
| } |
| else if (expression instanceof PsiPostfixExpression) { |
| final PsiExpression operand = PsiUtil.skipParenthesizedExprDown(((PsiPostfixExpression)expression).getOperand()); |
| final IElementType sign = ((PsiPostfixExpression)expression).getOperationTokenType(); |
| if (operand instanceof PsiReferenceExpression && (sign == JavaTokenType.PLUSPLUS || sign == JavaTokenType.MINUSMINUS)) { |
| reference = (PsiReferenceExpression)operand; |
| } |
| readBeforeWrite = true; |
| } |
| else if (expression instanceof PsiPrefixExpression) { |
| final PsiExpression operand = PsiUtil.skipParenthesizedExprDown(((PsiPrefixExpression)expression).getOperand()); |
| final IElementType sign = ((PsiPrefixExpression)expression).getOperationTokenType(); |
| if (operand instanceof PsiReferenceExpression && (sign == JavaTokenType.PLUSPLUS || sign == JavaTokenType.MINUSMINUS)) { |
| reference = (PsiReferenceExpression)operand; |
| } |
| readBeforeWrite = true; |
| } |
| final PsiElement resolved = reference == null ? null : reference.resolve(); |
| PsiVariable variable = resolved instanceof PsiVariable ? (PsiVariable)resolved : null; |
| if (variable == null || !variable.hasModifierProperty(PsiModifier.FINAL)) return null; |
| final boolean canWrite = canWriteToFinal(variable, expression, reference, containingFile) && checkWriteToFinalInsideLambda(variable, reference) == null; |
| if (readBeforeWrite || !canWrite) { |
| final String name = variable.getName(); |
| String description = canWrite ? |
| JavaErrorMessages.message("variable.not.initialized", name) : |
| JavaErrorMessages.message("assignment.to.final.variable", name); |
| final HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(reference.getTextRange()).descriptionAndTooltip(description).create(); |
| final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression); |
| if (innerClass == null || variable instanceof PsiField) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, |
| QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); |
| } |
| else { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass)); |
| } |
| return highlightInfo; |
| } |
| |
| return null; |
| } |
| |
| private static boolean canWriteToFinal(PsiVariable variable, PsiExpression expression, final PsiReferenceExpression reference, @NotNull PsiFile containingFile) { |
| if (variable.hasInitializer()) return false; |
| if (variable instanceof PsiParameter) return false; |
| PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression); |
| if (variable instanceof PsiField) { |
| // if inside some field initializer |
| if (HighlightUtil.findEnclosingFieldInitializer(expression) != null) return true; |
| // assignment from within inner class is illegal always |
| PsiField field = (PsiField)variable; |
| if (innerClass != null && !containingFile.getManager().areElementsEquivalent(innerClass, field.getContainingClass())) return false; |
| final PsiMember enclosingCtrOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); |
| return enclosingCtrOrInitializer != null && isSameField(variable, enclosingCtrOrInitializer, field, reference, containingFile); |
| } |
| if (variable instanceof PsiLocalVariable) { |
| boolean isAccessedFromOtherClass = innerClass != null; |
| if (isAccessedFromOtherClass) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean isSameField(final PsiVariable variable, |
| final PsiMember enclosingCtrOrInitializer, |
| final PsiField field, |
| final PsiReferenceExpression reference, @NotNull PsiFile containingFile) { |
| |
| if (!containingFile.getManager().areElementsEquivalent(enclosingCtrOrInitializer.getContainingClass(), field.getContainingClass())) return false; |
| PsiExpression qualifierExpression = reference.getQualifierExpression(); |
| return qualifierExpression == null || qualifierExpression instanceof PsiThisExpression; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkVariableMustBeFinal(@NotNull PsiVariable variable, |
| @NotNull PsiJavaCodeReferenceElement context, |
| @NotNull LanguageLevel languageLevel) { |
| if (variable.hasModifierProperty(PsiModifier.FINAL)) return null; |
| final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, context); |
| if (innerClass instanceof PsiClass) { |
| if (variable instanceof PsiParameter) { |
| final PsiElement parent = variable.getParent(); |
| if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression && |
| notAccessedForWriting(variable, new LocalSearchScope(((PsiParameter)variable).getDeclarationScope()))) { |
| return null; |
| } |
| } |
| if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && isEffectivelyFinal(variable, innerClass, context)) { |
| return null; |
| } |
| final String description = JavaErrorMessages.message("variable.must.be.final", context.getText()); |
| |
| final HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass)); |
| return highlightInfo; |
| } |
| return checkWriteToFinalInsideLambda(variable, context); |
| } |
| |
| private static HighlightInfo checkWriteToFinalInsideLambda(PsiVariable variable, PsiJavaCodeReferenceElement context) { |
| final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(context, PsiLambdaExpression.class); |
| if (lambdaExpression != null && !PsiTreeUtil.isAncestor(lambdaExpression, variable, true)) { |
| final PsiElement parent = variable.getParent(); |
| if (parent instanceof PsiParameterList && parent.getParent() == lambdaExpression) { |
| return null; |
| } |
| if (!isEffectivelyFinal(variable, lambdaExpression, context)) { |
| final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) |
| .range(context) |
| .descriptionAndTooltip("Variable used in lambda expression should be effectively final") |
| .create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, lambdaExpression)); |
| return highlightInfo; |
| } |
| } |
| return null; |
| } |
| |
| public static boolean isEffectivelyFinal(PsiVariable variable, PsiElement scope, @Nullable PsiJavaCodeReferenceElement context) { |
| boolean effectivelyFinal; |
| if (variable instanceof PsiParameter) { |
| effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(((PsiParameter)variable).getDeclarationScope())); |
| } else { |
| final ControlFlow controlFlow; |
| try { |
| controlFlow = getControlFlow(PsiUtil.getVariableCodeBlock(variable, context)); |
| } |
| catch (AnalysisCanceledException e) { |
| return true; |
| } |
| |
| final List<PsiReferenceExpression> readBeforeWriteLocals = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow); |
| for (PsiReferenceExpression expression : readBeforeWriteLocals) { |
| if (expression.resolve() == variable) { |
| return PsiUtil.isAccessedForReading(expression); |
| } |
| } |
| |
| final Collection<ControlFlowUtil.VariableInfo> initializedTwice = ControlFlowUtil.getInitializedTwice(controlFlow); |
| effectivelyFinal = !initializedTwice.contains(new ControlFlowUtil.VariableInfo(variable, null)); |
| if (effectivelyFinal) { |
| effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(scope)); |
| } |
| } |
| return effectivelyFinal; |
| } |
| |
| private static boolean notAccessedForWriting(PsiVariable variable, final LocalSearchScope searchScope) { |
| for (PsiReference reference : ReferencesSearch.search(variable, searchScope)) { |
| final PsiElement element = reference.getElement(); |
| if (element instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression)element)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Nullable |
| public static PsiElement getInnerClassVariableReferencedFrom(@NotNull PsiVariable variable, @NotNull PsiElement context) { |
| final PsiElement[] scope; |
| if (variable instanceof PsiResourceVariable) { |
| scope = ((PsiResourceVariable)variable).getDeclarationScope(); |
| } |
| else if (variable instanceof PsiLocalVariable) { |
| final PsiElement parent = variable.getParent(); |
| scope = new PsiElement[]{parent != null ? parent.getParent() : null}; // code block or for statement |
| } |
| else if (variable instanceof PsiParameter) { |
| scope = new PsiElement[]{((PsiParameter)variable).getDeclarationScope()}; |
| } |
| else { |
| scope = new PsiElement[]{variable.getParent()}; |
| } |
| if (scope.length < 1 || scope[0] == null || scope[0].getContainingFile() != context.getContainingFile()) return null; |
| |
| PsiElement parent = context.getParent(); |
| PsiElement prevParent = context; |
| outer: |
| while (parent != null) { |
| for (PsiElement scopeElement : scope) { |
| if (parent.equals(scopeElement)) break outer; |
| } |
| if (parent instanceof PsiClass && !(prevParent instanceof PsiExpressionList && parent instanceof PsiAnonymousClass)) { |
| return parent; |
| } |
| if (parent instanceof PsiLambdaExpression) { |
| return parent; |
| } |
| prevParent = parent; |
| parent = parent.getParent(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkInitializerCompleteNormally(PsiClassInitializer initializer) { |
| final PsiCodeBlock body = initializer.getBody(); |
| // unhandled exceptions already reported |
| try { |
| final ControlFlow controlFlow = getControlFlowNoConstantEvaluate(body); |
| final int completionReasons = ControlFlowUtil.getCompletionReasons(controlFlow, 0, controlFlow.getSize()); |
| if ((completionReasons & ControlFlowUtil.NORMAL_COMPLETION_REASON) == 0) { |
| String description = JavaErrorMessages.message("initializer.must.be.able.to.complete.normally"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(body).descriptionAndTooltip(description).create(); |
| } |
| } |
| catch (AnalysisCanceledException e) { |
| // incomplete code |
| } |
| return null; |
| } |
| } |