private static void checkCodeBlock( final PsiCodeBlock body, final Set<PsiField> candidates, Set<PsiField> usedFields) { try { final ControlFlow controlFlow = ControlFlowFactory.getInstance(body.getProject()) .getControlFlow(body, AllVariablesControlFlowPolicy.getInstance()); final List<PsiVariable> usedVars = ControlFlowUtil.getUsedVariables(controlFlow, 0, controlFlow.getSize()); for (PsiVariable usedVariable : usedVars) { if (usedVariable instanceof PsiField) { final PsiField usedField = (PsiField) usedVariable; if (!usedFields.add(usedField)) { candidates.remove(usedField); // used in more than one code block } } } final List<PsiReferenceExpression> readBeforeWrites = ControlFlowUtil.getReadBeforeWrite(controlFlow); for (final PsiReferenceExpression readBeforeWrite : readBeforeWrites) { final PsiElement resolved = readBeforeWrite.resolve(); if (resolved instanceof PsiField) { final PsiField field = (PsiField) resolved; PsiElement parent = body.getParent(); if (!(parent instanceof PsiMethod) || !((PsiMethod) parent).isConstructor() || field.getInitializer() == null || field.hasModifierProperty(PsiModifier.STATIC)) { candidates.remove(field); } } } } catch (AnalysisCanceledException e) { candidates.clear(); } }
private static boolean isEffectivelyFinal( PsiVariable variable, PsiElement scope, 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; } if (ControlFlowUtil.isVariableDefinitelyAssigned(variable, controlFlow)) { final Collection<ControlFlowUtil.VariableInfo> initializedTwice = ControlFlowUtil.getInitializedTwice(controlFlow); effectivelyFinal = !initializedTwice.contains(new ControlFlowUtil.VariableInfo(variable, null)); if (effectivelyFinal) { effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(scope)); } } else { effectivelyFinal = false; } } return effectivelyFinal; }
@Override public boolean isValueCompatible() { final PsiElement body = getBody(); if (body != null) { try { final ControlFlow controlFlow = ControlFlowFactory.getInstance(getProject()) .getControlFlow( body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); if (ControlFlowUtil.findExitPointsAndStatements( controlFlow, 0, controlFlow.getSize(), new IntArrayList(), PsiReturnStatement.class, PsiThrowStatement.class) .isEmpty()) { return false; } } catch (AnalysisCanceledException e) { return true; } } return true; }
@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 PsiReturnStatement addReturnForMethod(final PsiFile file, final PsiMethod method) { final PsiModifierList modifiers = method.getModifierList(); if (modifiers.hasModifierProperty(PsiModifier.ABSTRACT) || method.getBody() == null) { return null; } try { final ConvertReturnStatementsVisitor visitor = new ConvertReturnStatementsVisitor(factory, method, myTargetType); ControlFlow controlFlow; try { controlFlow = HighlightControlFlowUtil.getControlFlowNoConstantEvaluate(method.getBody()); } catch (AnalysisCanceledException e) { return null; // must be an error } PsiReturnStatement returnStatement; if (controlFlow != null && ControlFlowUtil.processReturns(controlFlow, visitor)) { // extra return statement not needed // get latest modified return statement and select... returnStatement = visitor.getLatestReturn(); } else { returnStatement = visitor.createReturnInLastStatement(); } if (method.getContainingFile() != file) { UndoUtil.markPsiFileForUndo(file); } return returnStatement; } catch (IncorrectOperationException e) { LOG.error(e); } return null; }
private static boolean variableDefinitelyNotAssignedIn(PsiVariable variable, PsiElement context) { try { ControlFlow controlFlow = getControlFlow(context); return ControlFlowUtil.isVariableDefinitelyNotAssigned(variable, controlFlow); } catch (AnalysisCanceledException e) { return false; } }
public static boolean isEffectivelyFinal( @NotNull PsiVariable variable, @NotNull PsiElement scope, @Nullable PsiJavaCodeReferenceElement context) { boolean effectivelyFinal; if (variable instanceof PsiParameter) { effectivelyFinal = notAccessedForWriting( variable, new LocalSearchScope(((PsiParameter) variable).getDeclarationScope())); } else { final ControlFlow controlFlow; try { PsiElement codeBlock = PsiUtil.getVariableCodeBlock(variable, context); if (codeBlock == null) return true; controlFlow = getControlFlow(codeBlock); } 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; }
public Collection<PsiStatement> prepareExitStatements( final @NotNull PsiElement[] elements, final @NotNull PsiElement enclosingCodeFragment) throws ExitStatementsNotSameException { myExitPoints = new IntArrayList(); myExitStatements = ControlFlowUtil.findExitPointsAndStatements( myControlFlow, myFlowStart, myFlowEnd, myExitPoints, ControlFlowUtil.DEFAULT_EXIT_STATEMENTS_CLASSES); if (ControlFlowUtil.hasObservableThrowExitPoints( myControlFlow, myFlowStart, myFlowEnd, elements, enclosingCodeFragment)) { throw new ExitStatementsNotSameException(); } if (LOG.isDebugEnabled()) { LOG.debug("exit points:"); for (int i = 0; i < myExitPoints.size(); i++) { LOG.debug(" " + myExitPoints.get(i)); } LOG.debug("exit statements:"); for (PsiStatement exitStatement : myExitStatements) { LOG.debug(" " + exitStatement); } } if (myExitPoints.isEmpty()) { // if the fragment never exits assume as if it exits in the end myExitPoints.add(myControlFlow.getEndOffset(elements[elements.length - 1])); } if (myExitPoints.size() != 1) { myGenerateConditionalExit = true; areExitStatementsTheSame(); } return myExitStatements; }
@NotNull private static Collection<ControlFlowUtil.VariableInfo> getFinalVariableProblemsInBlock( @NotNull Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems, @NotNull PsiElement codeBlock) { Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = finalVarProblems.get(codeBlock); if (codeBlockProblems == null) { try { final ControlFlow controlFlow = getControlFlowNoConstantEvaluate(codeBlock); codeBlockProblems = ControlFlowUtil.getInitializedTwice(controlFlow); } catch (AnalysisCanceledException e) { codeBlockProblems = Collections.emptyList(); } finalVarProblems.put(codeBlock, codeBlockProblems); } return codeBlockProblems; }
@NotNull public PsiVariable[] getOutputVariables(boolean collectVariablesAtExitPoints) { PsiVariable[] myOutputVariables = ControlFlowUtil.getOutputVariables( myControlFlow, myFlowStart, myFlowEnd, myExitPoints.toArray()); if (collectVariablesAtExitPoints) { // variables declared in selected block used in return statements are to be considered output // variables when extracting guard methods final Set<PsiVariable> outputVariables = new HashSet<>(Arrays.asList(myOutputVariables)); for (PsiStatement statement : myExitStatements) { statement.accept( new JavaRecursiveElementVisitor() { @Override public void visitReferenceExpression(PsiReferenceExpression expression) { super.visitReferenceExpression(expression); final PsiElement resolved = expression.resolve(); if (resolved instanceof PsiVariable) { final PsiVariable variable = (PsiVariable) resolved; if (isWrittenInside(variable)) { outputVariables.add(variable); } } } private boolean isWrittenInside(final PsiVariable variable) { final List<Instruction> instructions = myControlFlow.getInstructions(); for (int i = myFlowStart; i < myFlowEnd; i++) { Instruction instruction = instructions.get(i); if (instruction instanceof WriteVariableInstruction && variable.equals(((WriteVariableInstruction) instruction).variable)) { return true; } } return false; } }); } myOutputVariables = outputVariables.toArray(new PsiVariable[outputVariables.size()]); } Arrays.sort(myOutputVariables, PsiUtil.BY_POSITION); return myOutputVariables; }
@Override public boolean isVoidCompatible() { final PsiElement body = getBody(); if (body != null) { try { ControlFlow controlFlow = ControlFlowFactory.getInstance(getProject()) .getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); int startOffset = controlFlow.getStartOffset(body); int endOffset = controlFlow.getEndOffset(body); return startOffset != -1 && endOffset != -1 && !ControlFlowUtil.canCompleteNormally(controlFlow, startOffset, endOffset); } catch (AnalysisCanceledException e) { return true; } } return true; }
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; }
@Nullable static HighlightInfo checkFinalVariableInitializedInLoop( @NotNull PsiReferenceExpression expression, @NotNull 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 static HighlightInfo checkInitializerCompleteNormally(@NotNull 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 (!BitUtil.isSet(completionReasons, ControlFlowUtil.NORMAL_COMPLETION_REASON)) { 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; }
private static boolean canBeFinal(PsiVariable variable, List<PsiReferenceExpression> references) { // if there is at least one assignment to this variable, it cannot be final Map<PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems = new THashMap<PsiElement, Collection<PsiReferenceExpression>>(); Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems = new THashMap<PsiElement, Collection<ControlFlowUtil.VariableInfo>>(); for (PsiReferenceExpression expression : references) { if (ControlFlowUtil.isVariableAssignedInLoop(expression, variable)) return false; HighlightInfo highlightInfo = HighlightControlFlowUtil.checkVariableInitializedBeforeUsage( expression, variable, uninitializedVarProblems); if (highlightInfo != null) return false; highlightInfo = HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo( variable, expression, finalVarProblems); if (highlightInfo != null) return false; if (variable instanceof PsiParameter && PsiUtil.isAccessedForWriting(expression)) return false; } return true; }
static HighlightInfo checkUnreachableStatement(@Nullable PsiCodeBlock codeBlock) { if (codeBlock == null) return null; // do not compute constant expressions for if() statement condition // see JLS 14.20 Unreachable Statements try { AllVariablesControlFlowPolicy policy = AllVariablesControlFlowPolicy.getInstance(); final ControlFlow controlFlow = ControlFlowFactory.getInstance(codeBlock.getProject()) .getControlFlow(codeBlock, policy, false, false); 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 | IndexNotReadyException e) { // incomplete code } return null; }
public List<PsiVariable> getInputVariables( final PsiElement codeFragment, PsiElement[] elements, PsiVariable[] outputVariables) { final List<PsiVariable> inputVariables = ControlFlowUtil.getInputVariables(myControlFlow, myFlowStart, myFlowEnd); List<PsiVariable> myInputVariables; if (skipVariablesFromExitStatements(outputVariables)) { List<PsiVariable> inputVariableList = new ArrayList<>(inputVariables); removeParametersUsedInExitsOnly(codeFragment, inputVariableList); myInputVariables = inputVariableList; } else { List<PsiVariable> inputVariableList = new ArrayList<>(inputVariables); for (Iterator<PsiVariable> iterator = inputVariableList.iterator(); iterator.hasNext(); ) { PsiVariable variable = iterator.next(); for (PsiElement element : elements) { if (PsiTreeUtil.isAncestor(element, variable, false)) { iterator.remove(); break; } } } myInputVariables = inputVariableList; } // varargs variables go last, otherwise order is induced by original ordering Collections.sort( myInputVariables, (v1, v2) -> { if (v1.getType() instanceof PsiEllipsisType) { return 1; } if (v2.getType() instanceof PsiEllipsisType) { return -1; } return v1.getTextOffset() - v2.getTextOffset(); }); return myInputVariables; }
private static void findSubmemberHidesFieldCollisions( final PsiField field, final String newName, final List<UsageInfo> result) { if (field.getContainingClass() == null) return; if (field.hasModifierProperty(PsiModifier.PRIVATE)) return; final PsiClass containingClass = field.getContainingClass(); Collection<PsiClass> inheritors = ClassInheritorsSearch.search(containingClass).findAll(); for (PsiClass inheritor : inheritors) { PsiField conflictingField = inheritor.findFieldByName(newName, false); if (conflictingField != null) { result.add(new SubmemberHidesMemberUsageInfo(conflictingField, field)); } else { // local class final PsiMember member = PsiTreeUtil.getParentOfType(inheritor, PsiMember.class); if (member != null) { final ArrayList<PsiVariable> variables = new ArrayList<>(); ControlFlowUtil.collectOuterLocals(variables, inheritor, inheritor, member); for (PsiVariable variable : variables) { if (newName.equals(variable.getName())) { result.add(new FieldHidesLocalUsageInfo(variable, field)); } } } } } }
public Collection<ControlFlowUtil.VariableInfo> getInitializedTwice(int start) { return ControlFlowUtil.getInitializedTwice(myControlFlow, start, myControlFlow.getSize()); }
public List<PsiVariable> getUsedVariables(int start, int end) { return ControlFlowUtil.getUsedVariables(myControlFlow, start, end); }
public boolean isReturnPresentBetween() { return ControlFlowUtil.returnPresentBetween(myControlFlow, myFlowStart, myFlowEnd); }
@Nullable public static HighlightInfo checkVariableInitializedBeforeUsage( @NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable, @NotNull 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 ? ((PsiField) variable).getContainingClass() : 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; PsiCodeBlock body = redirectedConstructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body)) { 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); int offset = startOffset; if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) { offset = 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 (offset < constructor.getTextRange().getStartOffset()) continue; PsiCodeBlock body = constructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body)) { 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 (offset < redirectedConstructor.getTextRange().getStartOffset()) continue; PsiCodeBlock redirectedBody = redirectedConstructor.getBody(); if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) { 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 | 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; }
@Override public void onReferencesBuild(RefElement refElement) { if (refElement instanceof RefClass) { final PsiClass psiClass = (PsiClass) refElement.getElement(); if (psiClass != null) { if (refElement.isEntry()) { ((RefClassImpl) refElement).setFlag(false, CAN_BE_FINAL_MASK); } PsiMethod[] psiMethods = psiClass.getMethods(); PsiField[] psiFields = psiClass.getFields(); HashSet<PsiVariable> allFields = new HashSet<PsiVariable>(); ContainerUtil.addAll(allFields, psiFields); ArrayList<PsiVariable> instanceInitializerInitializedFields = new ArrayList<PsiVariable>(); boolean hasInitializers = false; for (PsiClassInitializer initializer : psiClass.getInitializers()) { PsiCodeBlock body = initializer.getBody(); hasInitializers = true; ControlFlow flow; try { flow = ControlFlowFactory.getInstance(body.getProject()) .getControlFlow( body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); } catch (AnalysisCanceledException e) { flow = ControlFlow.EMPTY; } Collection<PsiVariable> writtenVariables = new ArrayList<PsiVariable>(); ControlFlowUtil.getWrittenVariables(flow, 0, flow.getSize(), false, writtenVariables); for (PsiVariable psiVariable : writtenVariables) { if (allFields.contains(psiVariable)) { if (instanceInitializerInitializedFields.contains(psiVariable)) { allFields.remove(psiVariable); instanceInitializerInitializedFields.remove(psiVariable); } else { instanceInitializerInitializedFields.add(psiVariable); } } } for (PsiVariable psiVariable : writtenVariables) { if (!instanceInitializerInitializedFields.contains(psiVariable)) { allFields.remove(psiVariable); } } } for (PsiMethod psiMethod : psiMethods) { if (psiMethod.isConstructor()) { PsiCodeBlock body = psiMethod.getBody(); if (body != null) { hasInitializers = true; ControlFlow flow; try { flow = ControlFlowFactory.getInstance(body.getProject()) .getControlFlow( body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); } catch (AnalysisCanceledException e) { flow = ControlFlow.EMPTY; } Collection<PsiVariable> writtenVariables = ControlFlowUtil.getWrittenVariables(flow, 0, flow.getSize(), false); for (PsiVariable psiVariable : writtenVariables) { if (instanceInitializerInitializedFields.contains(psiVariable)) { allFields.remove(psiVariable); instanceInitializerInitializedFields.remove(psiVariable); } } List<PsiMethod> redirectedConstructors = HighlightControlFlowUtil.getChainedConstructors(psiMethod); if (redirectedConstructors == null || redirectedConstructors.isEmpty()) { List<PsiVariable> ssaVariables = ControlFlowUtil.getSSAVariables(flow); ArrayList<PsiVariable> good = new ArrayList<PsiVariable>(ssaVariables); good.addAll(instanceInitializerInitializedFields); allFields.retainAll(good); } else { allFields.removeAll(writtenVariables); } } } } for (PsiField psiField : psiFields) { if ((!hasInitializers || !allFields.contains(psiField)) && psiField.getInitializer() == null) { final RefFieldImpl refField = (RefFieldImpl) myManager.getReference(psiField); if (refField != null) { refField.setFlag(false, CAN_BE_FINAL_MASK); } } } } } else if (refElement instanceof RefMethod) { final RefMethod refMethod = (RefMethod) refElement; if (refMethod.isEntry()) { ((RefMethodImpl) refMethod).setFlag(false, CAN_BE_FINAL_MASK); } } }
@Nullable public static HighlightInfo checkFinalVariableMightAlreadyHaveBeenAssignedTo( @NotNull PsiVariable variable, @NotNull PsiReferenceExpression expression, @NotNull 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); PsiCodeBlock body = redirectedConstructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body)) { 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; }