constructor reference: don't ignore constructor parameters during method reference...
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInsight / daemon / impl / analysis / HighlightControlFlowUtil.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInsight.daemon.impl.analysis;
3
4 import com.intellij.codeInsight.daemon.JavaErrorMessages;
5 import com.intellij.codeInsight.daemon.QuickFixBundle;
6 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
7 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
8 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
9 import com.intellij.codeInsight.intention.QuickFixFactory;
10 import com.intellij.lang.jvm.JvmModifier;
11 import com.intellij.lang.jvm.actions.ChangeModifierRequest;
12 import com.intellij.lang.jvm.actions.JvmElementActionFactories;
13 import com.intellij.lang.jvm.actions.MemberRequestsKt;
14 import com.intellij.openapi.project.IndexNotReadyException;
15 import com.intellij.openapi.util.Condition;
16 import com.intellij.openapi.util.TextRange;
17 import com.intellij.pom.java.LanguageLevel;
18 import com.intellij.psi.*;
19 import com.intellij.psi.controlFlow.*;
20 import com.intellij.psi.search.LocalSearchScope;
21 import com.intellij.psi.search.searches.ReferencesSearch;
22 import com.intellij.psi.util.FileTypeUtils;
23 import com.intellij.psi.util.PsiTreeUtil;
24 import com.intellij.psi.util.PsiUtil;
25 import com.intellij.util.BitUtil;
26 import com.intellij.util.ObjectUtils;
27 import com.intellij.util.Processor;
28 import com.intellij.util.containers.ContainerUtil;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import java.util.*;
33
34 /**
35  * @author cdr
36  */
37 public class HighlightControlFlowUtil {
38   private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance();
39
40   private HighlightControlFlowUtil() { }
41
42   @Nullable
43   static HighlightInfo checkMissingReturnStatement(@Nullable PsiCodeBlock body, @Nullable PsiType returnType) {
44     if (body == null || returnType == null || PsiType.VOID.equals(returnType.getDeepComponentType())) {
45       return null;
46     }
47
48     // do not compute constant expressions for if() statement condition
49     // see JLS 14.20 Unreachable Statements
50     try {
51       ControlFlow controlFlow = getControlFlowNoConstantEvaluate(body);
52       if (!ControlFlowUtil.returnPresent(controlFlow)) {
53         PsiJavaToken rBrace = body.getRBrace();
54         PsiElement context = rBrace == null ? body.getLastChild() : rBrace;
55         String message = JavaErrorMessages.message("missing.return.statement");
56         HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(message).create();
57         PsiElement parent = body.getParent();
58         if (parent instanceof PsiMethod) {
59           PsiMethod method = (PsiMethod)parent;
60           QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddReturnFix(method));
61           QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.VOID, true));
62         }
63         if (parent instanceof PsiLambdaExpression) {
64           QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddReturnFix((PsiLambdaExpression)parent));
65         }
66         return info;
67       }
68     }
69     catch (AnalysisCanceledException ignored) { }
70
71     return null;
72   }
73
74   @NotNull
75   public static ControlFlow getControlFlowNoConstantEvaluate(@NotNull PsiElement body) throws AnalysisCanceledException {
76     LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance();
77     return ControlFlowFactory.getInstance(body.getProject()).getControlFlow(body, policy, false, false);
78   }
79
80   @NotNull
81   private static ControlFlow getControlFlow(@NotNull PsiElement context) throws AnalysisCanceledException {
82     LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance();
83     return ControlFlowFactory.getInstance(context.getProject()).getControlFlow(context, policy);
84   }
85
86   static HighlightInfo checkUnreachableStatement(@Nullable PsiCodeBlock codeBlock) {
87     if (codeBlock == null) return null;
88     // do not compute constant expressions for if() statement condition
89     // see JLS 14.20 Unreachable Statements
90     try {
91       AllVariablesControlFlowPolicy policy = AllVariablesControlFlowPolicy.getInstance();
92       final ControlFlow controlFlow = ControlFlowFactory.getInstance(codeBlock.getProject()).getControlFlow(codeBlock, policy, false, false);
93       final PsiElement unreachableStatement = ControlFlowUtil.getUnreachableStatement(controlFlow);
94       if (unreachableStatement != null) {
95         String description = JavaErrorMessages.message("unreachable.statement");
96         PsiElement keyword = null;
97         if (unreachableStatement instanceof PsiIfStatement ||
98             unreachableStatement instanceof PsiSwitchBlock ||
99             unreachableStatement instanceof PsiLoopStatement) {
100           keyword = unreachableStatement.getFirstChild();
101         }
102         final PsiElement element = keyword != null ? keyword : unreachableStatement;
103         final HighlightInfo info =
104           HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
105         QuickFixAction.registerQuickFixAction(
106           info, QUICK_FIX_FACTORY.createDeleteFix(unreachableStatement, QuickFixBundle.message("delete.unreachable.statement.fix.text")));
107         return info;
108       }
109     }
110     catch (AnalysisCanceledException | IndexNotReadyException e) {
111       // incomplete code
112     }
113     return null;
114   }
115
116   public static boolean isFieldInitializedAfterObjectConstruction(@NotNull PsiField field) {
117     if (field.hasInitializer()) return true;
118     final boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
119     final PsiClass aClass = field.getContainingClass();
120     if (aClass != null) {
121       // field might be assigned in the other field initializers
122       if (isFieldInitializedInOtherFieldInitializer(aClass, field, isFieldStatic, Condition.TRUE)) return true;
123     }
124     final PsiClassInitializer[] initializers;
125     if (aClass != null) {
126       initializers = aClass.getInitializers();
127     }
128     else {
129       return false;
130     }
131     if (isFieldInitializedInClassInitializer(field, isFieldStatic, initializers)) return true;
132     if (isFieldStatic) {
133       return false;
134     }
135     else {
136       // instance field should be initialized at the end of the each constructor
137       final PsiMethod[] constructors = aClass.getConstructors();
138
139       if (constructors.length == 0) return false;
140       nextConstructor:
141       for (PsiMethod constructor : constructors) {
142         PsiCodeBlock ctrBody = constructor.getBody();
143         if (ctrBody == null) return false;
144         final List<PsiMethod> redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor);
145         for (PsiMethod redirectedConstructor : redirectedConstructors) {
146           final PsiCodeBlock body = redirectedConstructor.getBody();
147           if (body != null && variableDefinitelyAssignedIn(field, body)) continue nextConstructor;
148         }
149         if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody)) {
150           continue;
151         }
152         return false;
153       }
154       return true;
155     }
156   }
157
158   private static boolean isFieldInitializedInClassInitializer(@NotNull PsiField field,
159                                                               boolean isFieldStatic,
160                                                               @NotNull PsiClassInitializer[] initializers) {
161     return ContainerUtil.find(initializers, initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic
162                                                            && variableDefinitelyAssignedIn(field, initializer.getBody())) != null;
163   }
164
165   private static boolean isFieldInitializedInOtherFieldInitializer(@NotNull PsiClass aClass,
166                                                                    @NotNull PsiField field,
167                                                                    final boolean fieldStatic,
168                                                                    @NotNull Condition<? super PsiField> condition) {
169     PsiField[] fields = aClass.getFields();
170     for (PsiField psiField : fields) {
171       if (psiField != field
172           && psiField.hasModifierProperty(PsiModifier.STATIC) == fieldStatic
173           && variableDefinitelyAssignedIn(field, psiField)
174           && condition.value(psiField)) {
175         return true;
176       }
177     }
178     return false;
179   }
180
181   static boolean isRecursivelyCalledConstructor(@NotNull PsiMethod constructor) {
182     final JavaHighlightUtil.ConstructorVisitorInfo info = new JavaHighlightUtil.ConstructorVisitorInfo();
183     JavaHighlightUtil.visitConstructorChain(constructor, info);
184     if (info.recursivelyCalledConstructor == null) return false;
185     // our constructor is reached from some other constructor by constructor chain
186     return info.visitedConstructors.indexOf(info.recursivelyCalledConstructor) <=
187            info.visitedConstructors.indexOf(constructor);
188   }
189
190   public static boolean isAssigned(@NotNull PsiParameter parameter) {
191     ParamWriteProcessor processor = new ParamWriteProcessor();
192     ReferencesSearch.search(parameter, new LocalSearchScope(parameter.getDeclarationScope()), true).forEach(processor);
193     return processor.isWriteRefFound();
194   }
195
196   private static class ParamWriteProcessor implements Processor<PsiReference> {
197     private volatile boolean myIsWriteRefFound;
198     @Override
199     public boolean process(PsiReference reference) {
200       final PsiElement element = reference.getElement();
201       if (element instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression)element)) {
202         myIsWriteRefFound = true;
203         return false;
204       }
205       return true;
206     }
207
208     private boolean isWriteRefFound() {
209       return myIsWriteRefFound;
210     }
211   }
212
213   /**
214    * see JLS chapter 16
215    * @return true if variable assigned (maybe more than once)
216    */
217   public static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
218     try {
219       ControlFlow controlFlow = getControlFlow(context);
220       return ControlFlowUtil.isVariableDefinitelyAssigned(variable, controlFlow);
221     }
222     catch (AnalysisCanceledException e) {
223       return false;
224     }
225   }
226
227   private static boolean variableDefinitelyNotAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
228     try {
229       ControlFlow controlFlow = getControlFlow(context);
230       return ControlFlowUtil.isVariableDefinitelyNotAssigned(variable, controlFlow);
231     }
232     catch (AnalysisCanceledException e) {
233       return false;
234     }
235   }
236
237
238   @Nullable
239   static HighlightInfo checkFinalFieldInitialized(@NotNull PsiField field) {
240     if (!field.hasModifierProperty(PsiModifier.FINAL)) return null;
241     if (isFieldInitializedAfterObjectConstruction(field)) return null;
242
243     String description = JavaErrorMessages.message("variable.not.initialized", field.getName());
244     TextRange range = HighlightNamesUtil.getFieldDeclarationTextRange(field);
245     HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description).create();
246     QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), QUICK_FIX_FACTORY.createCreateConstructorParameterFromFieldFix(field));
247     QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), QUICK_FIX_FACTORY.createInitializeFinalFieldInConstructorFix(field));
248     final PsiClass containingClass = field.getContainingClass();
249     if (containingClass != null && !containingClass.isInterface()) {
250       QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(field, PsiModifier.FINAL, false, false));
251     }
252     QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddVariableInitializerFix(field));
253     return highlightInfo;
254   }
255
256
257   @Nullable
258   public static HighlightInfo checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
259                                                                   @NotNull PsiVariable variable,
260                                                                   @NotNull Map<PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems,
261                                                                   @NotNull PsiFile containingFile) {
262     return checkVariableInitializedBeforeUsage(expression, variable, uninitializedVarProblems, containingFile, false);
263   }
264
265   @Nullable
266   public static HighlightInfo checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
267                                                                   @NotNull PsiVariable variable,
268                                                                   @NotNull Map<PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems,
269                                                                   @NotNull PsiFile containingFile,
270                                                                   boolean ignoreFinality) {
271     if (variable instanceof ImplicitVariable) return null;
272     if (!PsiUtil.isAccessedForReading(expression)) return null;
273     int startOffset = expression.getTextRange().getStartOffset();
274     final PsiElement topBlock;
275     if (variable.hasInitializer()) {
276       topBlock = PsiUtil.getVariableCodeBlock(variable, variable);
277       if (topBlock == null) return null;
278     }
279     else {
280       PsiElement scope = variable instanceof PsiField
281                                ? ((PsiField)variable).getContainingClass()
282                                : variable.getParent() != null ? variable.getParent().getParent() : null;
283       if (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchStatement) {
284         scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class);
285       }
286
287       topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile ? scope : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
288       if (variable instanceof PsiField) {
289         // non final field already initialized with default value
290         if (!ignoreFinality && !variable.hasModifierProperty(PsiModifier.FINAL)) return null;
291         // final field may be initialized in ctor or class initializer only
292         // if we're inside non-ctr method, skip it
293         if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null
294             && HighlightUtil.findEnclosingFieldInitializer(expression) == null) {
295           return null;
296         }
297         if (topBlock == null) return null;
298         final PsiElement parent = topBlock.getParent();
299         // access to final fields from inner classes always allowed
300         if (inInnerClass(expression, ((PsiField)variable).getContainingClass())) return null;
301         final PsiCodeBlock block;
302         final PsiClass aClass;
303         if (parent instanceof PsiMethod) {
304           PsiMethod constructor = (PsiMethod)parent;
305           if (!containingFile.getManager().areElementsEquivalent(constructor.getContainingClass(), ((PsiField)variable).getContainingClass())) return null;
306           // static variables already initialized in class initializers
307           if (variable.hasModifierProperty(PsiModifier.STATIC)) return null;
308           // as a last chance, field may be initialized in this() call
309           final List<PsiMethod> redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor);
310           for (PsiMethod redirectedConstructor : redirectedConstructors) {
311             // variable must be initialized before its usage
312             //???
313             //if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue;
314             PsiCodeBlock body = redirectedConstructor.getBody();
315             if (body != null && variableDefinitelyAssignedIn(variable, body)) {
316               return null;
317             }
318           }
319           block = constructor.getBody();
320           aClass = constructor.getContainingClass();
321         }
322         else if (parent instanceof PsiClassInitializer) {
323           final PsiClassInitializer classInitializer = (PsiClassInitializer)parent;
324           if (!containingFile.getManager().areElementsEquivalent(classInitializer.getContainingClass(), ((PsiField)variable).getContainingClass())) return null;
325           block = classInitializer.getBody();
326           aClass = classInitializer.getContainingClass();
327
328           if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, (PsiField)variable, variable.hasModifierProperty(PsiModifier.STATIC), field -> startOffset > field.getTextOffset())) return null;
329         }
330         else {
331           // field reference outside code block
332           // check variable initialized before its usage
333           final PsiField field = (PsiField)variable;
334
335           aClass = field.getContainingClass();
336           final PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class);
337           if (aClass == null ||
338               isFieldInitializedInOtherFieldInitializer(aClass, field, field.hasModifierProperty(PsiModifier.STATIC), psiField -> startOffset > psiField.getTextOffset())) {
339             return null;
340           }
341           if (anotherField != null && !anotherField.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.STATIC) &&
342               isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) {
343             return null;
344           }
345
346           int offset = startOffset;
347           if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) {
348             offset = 0;
349           }
350           block = null;
351           // initializers will be checked later
352           final PsiMethod[] constructors = aClass.getConstructors();
353           for (PsiMethod constructor : constructors) {
354             // variable must be initialized before its usage
355             if (offset < constructor.getTextRange().getStartOffset()) continue;
356             PsiCodeBlock body = constructor.getBody();
357             if (body != null && variableDefinitelyAssignedIn(variable, body)) {
358               return null;
359             }
360             // as a last chance, field may be initialized in this() call
361             final List<PsiMethod> redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor);
362             for (PsiMethod redirectedConstructor : redirectedConstructors) {
363               // variable must be initialized before its usage
364               if (offset < redirectedConstructor.getTextRange().getStartOffset()) continue;
365               PsiCodeBlock redirectedBody = redirectedConstructor.getBody();
366               if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) {
367                 return null;
368               }
369             }
370           }
371         }
372
373         if (aClass != null) {
374           // field may be initialized in class initializer
375           final PsiClassInitializer[] initializers = aClass.getInitializers();
376           for (PsiClassInitializer initializer : initializers) {
377             PsiCodeBlock body = initializer.getBody();
378             if (body == block) break;
379             // variable referenced in initializer must be initialized in initializer preceding assignment
380             // variable referenced in field initializer or in class initializer
381             boolean shouldCheckInitializerOrder = block == null || block.getParent() instanceof PsiClassInitializer;
382             if (shouldCheckInitializerOrder && startOffset < initializer.getTextRange().getStartOffset()) continue;
383             if (initializer.hasModifierProperty(PsiModifier.STATIC)
384                 == variable.hasModifierProperty(PsiModifier.STATIC)) {
385               if (variableDefinitelyAssignedIn(variable, body)) return null;
386             }
387           }
388         }
389       }
390     }
391     if (topBlock == null) return null;
392     Collection<PsiReferenceExpression> codeBlockProblems = uninitializedVarProblems.get(topBlock);
393     if (codeBlockProblems == null) {
394       try {
395         final ControlFlow controlFlow = getControlFlow(topBlock);
396         codeBlockProblems = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow);
397       }
398       catch (AnalysisCanceledException | IndexNotReadyException e) {
399         codeBlockProblems = Collections.emptyList();
400       }
401       uninitializedVarProblems.put(topBlock, codeBlockProblems);
402     }
403     if (codeBlockProblems.contains(expression)) {
404       final String name = expression.getElement().getText();
405       String description = JavaErrorMessages.message("variable.not.initialized", name);
406       HighlightInfo highlightInfo =
407         HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create();
408       QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddVariableInitializerFix(variable));
409       if (variable instanceof PsiLocalVariable) {
410         QuickFixAction.registerQuickFixAction(highlightInfo, HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, expression));
411       }
412       if (variable instanceof PsiField) {
413         ChangeModifierRequest request = MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false);
414         QuickFixAction.registerQuickFixActions(highlightInfo, null, JvmElementActionFactories.createModifierActions((PsiField)variable, request));
415       }
416       return highlightInfo;
417     }
418
419     return null;
420   }
421
422   private static boolean inInnerClass(@NotNull PsiElement psiElement, @Nullable PsiClass containingClass) {
423     for (PsiElement element = psiElement;element != null;element = element.getParent()) {
424       if (element instanceof PsiClass) {
425         final boolean innerClass = !psiElement.getManager().areElementsEquivalent(element, containingClass);
426         if (innerClass) {
427           if (element instanceof PsiAnonymousClass) {
428             if (PsiTreeUtil.isAncestor(((PsiAnonymousClass)element).getArgumentList(), psiElement, false)) {
429               continue;
430             }
431             return !insideClassInitialization(containingClass, (PsiClass)element);
432           }
433           final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class);
434           return lambdaExpression == null || !insideClassInitialization(containingClass, (PsiClass)element);
435         }
436         return false;
437       }
438     }
439     return false;
440   }
441
442   private static boolean insideClassInitialization(@Nullable PsiClass containingClass, PsiClass aClass) {
443     PsiMember member = aClass;
444     while (member != null) {
445       if (member.getContainingClass() == containingClass) {
446         return member instanceof PsiField ||
447                member instanceof PsiMethod && ((PsiMethod)member).isConstructor() ||
448                member instanceof PsiClassInitializer;
449       }
450       member = PsiTreeUtil.getParentOfType(member, PsiMember.class, true);
451     }
452     return false;
453   }
454
455   public static boolean isReassigned(@NotNull PsiVariable variable,
456                                      @NotNull Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) {
457     if (variable instanceof PsiLocalVariable) {
458       final PsiElement parent = variable.getParent();
459       if (parent == null) return false;
460       final PsiElement declarationScope = parent.getParent();
461       if (declarationScope == null) return false;
462       Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, declarationScope);
463       return codeBlockProblems.contains(new ControlFlowUtil.VariableInfo(variable, null));
464     }
465     if (variable instanceof PsiParameter) {
466       final PsiParameter parameter = (PsiParameter)variable;
467       return isAssigned(parameter);
468     }
469     return false;
470   }
471
472
473   @Nullable
474   public static HighlightInfo checkFinalVariableMightAlreadyHaveBeenAssignedTo(@NotNull PsiVariable variable,
475                                                                                @NotNull PsiReferenceExpression expression,
476                                                                                @NotNull Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) {
477     if (!PsiUtil.isAccessedForWriting(expression)) return null;
478
479     final PsiElement scope = variable instanceof PsiField ? variable.getParent() :
480                              variable.getParent() == null ? null : variable.getParent().getParent();
481     PsiElement codeBlock = PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
482     if (codeBlock == null) return null;
483     Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, codeBlock);
484
485     boolean alreadyAssigned = false;
486     for (ControlFlowUtil.VariableInfo variableInfo : codeBlockProblems) {
487       if (variableInfo.expression == expression) {
488         alreadyAssigned = true;
489         break;
490       }
491     }
492
493     if (!alreadyAssigned) {
494       if (!(variable instanceof PsiField)) return null;
495       final PsiField field = (PsiField)variable;
496       final PsiClass aClass = field.getContainingClass();
497       if (aClass == null) return null;
498       // field can get assigned in other field initializers or in class initializers
499       List<PsiMember> members = new ArrayList<>(Arrays.asList(aClass.getFields()));
500       boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
501       final PsiMember enclosingConstructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression);
502       if (enclosingConstructorOrInitializer != null
503           && aClass.getManager().areElementsEquivalent(enclosingConstructorOrInitializer.getContainingClass(), aClass)) {
504         members.addAll(Arrays.asList(aClass.getInitializers()));
505         Collections.sort(members, PsiUtil.BY_POSITION);
506       }
507
508       for (PsiMember member : members) {
509         if (member == field) continue;
510         PsiElement context = member instanceof PsiField ? ((PsiField)member).getInitializer()
511                                                         : ((PsiClassInitializer)member).getBody();
512
513         if (context != null
514             && member.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic
515             && !variableDefinitelyNotAssignedIn(field, context)) {
516           if (context == codeBlock) {
517             return null;
518           }
519           alreadyAssigned = true;
520           break;
521         }
522       }
523
524       if (!alreadyAssigned && !isFieldStatic) {
525         // then check if instance field already assigned in other constructor
526         final PsiMethod ctr = codeBlock.getParent() instanceof PsiMethod ?
527                               (PsiMethod)codeBlock.getParent() : null;
528         // assignment to final field in several constructors threatens us only if these are linked (there is this() call in the beginning)
529         final List<PsiMethod> redirectedConstructors = ctr != null && ctr.isConstructor() ? JavaHighlightUtil.getChainedConstructors(ctr) : Collections.emptyList();
530         for (PsiMethod redirectedConstructor : redirectedConstructors) {
531           PsiCodeBlock body = redirectedConstructor.getBody();
532           if (body != null && variableDefinitelyAssignedIn(variable, body)) {
533             alreadyAssigned = true;
534             break;
535           }
536         }
537       }
538     }
539
540     if (alreadyAssigned) {
541       String description = JavaErrorMessages.message("variable.already.assigned", variable.getName());
542       final HighlightInfo highlightInfo =
543         HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create();
544       if (variable instanceof PsiField) {
545         QuickFixAction.registerQuickFixActions(
546           highlightInfo, null,
547           JvmElementActionFactories.createModifierActions((PsiField)variable, MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false))
548         );
549       }
550       else {
551         QuickFixAction.registerQuickFixAction(
552           highlightInfo,
553           QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)
554         );
555       }
556       QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeferFinalAssignmentFix(variable, expression));
557       return highlightInfo;
558     }
559
560     return null;
561   }
562
563   @NotNull
564   private static Collection<ControlFlowUtil.VariableInfo> getFinalVariableProblemsInBlock(@NotNull Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems,
565                                                                                           @NotNull PsiElement codeBlock) {
566     Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = finalVarProblems.get(codeBlock);
567     if (codeBlockProblems == null) {
568       try {
569         final ControlFlow controlFlow = getControlFlow(codeBlock);
570         codeBlockProblems = ControlFlowUtil.getInitializedTwice(controlFlow);
571       }
572       catch (AnalysisCanceledException e) {
573         codeBlockProblems = Collections.emptyList();
574       }
575       finalVarProblems.put(codeBlock, codeBlockProblems);
576     }
577     return codeBlockProblems;
578   }
579
580
581   @Nullable
582   static HighlightInfo checkFinalVariableInitializedInLoop(@NotNull PsiReferenceExpression expression, @NotNull PsiElement resolved) {
583     if (ControlFlowUtil.isVariableAssignedInLoop(expression, resolved)) {
584       String description = JavaErrorMessages.message("variable.assigned.in.loop", ((PsiVariable)resolved).getName());
585       final HighlightInfo highlightInfo =
586         HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create();
587       if (resolved instanceof PsiField) {
588         QuickFixAction.registerQuickFixActions(
589           highlightInfo, null,
590           JvmElementActionFactories.createModifierActions((PsiField)resolved, MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false))
591         );
592       }
593       else {
594         QuickFixAction.registerQuickFixAction(
595           highlightInfo,
596           QUICK_FIX_FACTORY.createModifierListFix((PsiVariable)resolved, PsiModifier.FINAL, false, false)
597         );
598       }
599       return highlightInfo;
600     }
601     return null;
602   }
603
604
605   @Nullable
606   static HighlightInfo checkCannotWriteToFinal(@NotNull PsiExpression expression, @NotNull PsiFile containingFile) {
607     PsiExpression operand = null;
608     if (expression instanceof PsiAssignmentExpression) {
609       operand = ((PsiAssignmentExpression)expression).getLExpression();
610     }
611     else if (PsiUtil.isIncrementDecrementOperation(expression)) {
612       operand = ((PsiUnaryExpression)expression).getOperand();
613     }
614     PsiReferenceExpression reference = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(operand), PsiReferenceExpression.class);
615     PsiVariable variable = reference == null ? null : ObjectUtils.tryCast(reference.resolve(), PsiVariable.class);
616     if (variable == null || !variable.hasModifierProperty(PsiModifier.FINAL)) return null;
617     final boolean canWrite = canWriteToFinal(variable, expression, reference, containingFile) && checkWriteToFinalInsideLambda(variable, reference) == null;
618     if (canWrite) return null;
619     final String name = variable.getName();
620     String description = JavaErrorMessages.message("assignment.to.final.variable", name);
621     final HighlightInfo highlightInfo =
622       HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(reference.getTextRange()).descriptionAndTooltip(description).create();
623     final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression);
624     if (innerClass == null || variable instanceof PsiField) {
625       if (variable instanceof PsiField) {
626         QuickFixAction.registerQuickFixActions(
627           highlightInfo, null,
628           JvmElementActionFactories.createModifierActions((PsiField)variable, MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false))
629         );
630       }
631       else {
632         QuickFixAction.registerQuickFixAction(
633           highlightInfo,
634           QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)
635         );
636       }
637     }
638     else {
639       QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass));
640     }
641     return highlightInfo;
642   }
643
644   private static boolean canWriteToFinal(@NotNull PsiVariable variable,
645                                          @NotNull PsiExpression expression,
646                                          @NotNull  PsiReferenceExpression reference,
647                                          @NotNull PsiFile containingFile) {
648     if (variable.hasInitializer()) return false;
649     if (variable instanceof PsiParameter) return false;
650     PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression);
651     if (variable instanceof PsiField) {
652       // if inside some field initializer
653       if (HighlightUtil.findEnclosingFieldInitializer(expression) != null) return true;
654       // assignment from within inner class is illegal always
655       PsiField field = (PsiField)variable;
656       if (innerClass != null && !containingFile.getManager().areElementsEquivalent(innerClass, field.getContainingClass())) return false;
657       final PsiMember enclosingCtrOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression);
658       return enclosingCtrOrInitializer != null && isSameField(enclosingCtrOrInitializer, field, reference, containingFile);
659     }
660     if (variable instanceof PsiLocalVariable) {
661       boolean isAccessedFromOtherClass = innerClass != null;
662       if (isAccessedFromOtherClass) {
663         return false;
664       }
665     }
666     return true;
667   }
668
669   private static boolean isSameField(@NotNull PsiMember enclosingCtrOrInitializer,
670                                      @NotNull PsiField field,
671                                      @NotNull PsiReferenceExpression reference,
672                                      @NotNull PsiFile containingFile) {
673     if (!containingFile.getManager().areElementsEquivalent(enclosingCtrOrInitializer.getContainingClass(), field.getContainingClass())) return false;
674     return LocalsOrMyInstanceFieldsControlFlowPolicy.isLocalOrMyInstanceReference(reference);
675   }
676
677
678   @Nullable
679   static HighlightInfo checkVariableMustBeFinal(@NotNull PsiVariable variable,
680                                                 @NotNull PsiJavaCodeReferenceElement context,
681                                                 @NotNull LanguageLevel languageLevel) {
682     if (variable.hasModifierProperty(PsiModifier.FINAL)) return null;
683     final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, context);
684     if (innerClass instanceof PsiClass) {
685       if (variable instanceof PsiParameter) {
686         final PsiElement parent = variable.getParent();
687         if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression &&
688             notAccessedForWriting(variable, new LocalSearchScope(((PsiParameter)variable).getDeclarationScope()))) {
689           return null;
690         }
691       }
692       final boolean isToBeEffectivelyFinal = languageLevel.isAtLeast(LanguageLevel.JDK_1_8);
693       if (isToBeEffectivelyFinal && isEffectivelyFinal(variable, innerClass, context)) {
694         return null;
695       }
696       final String description = JavaErrorMessages.message(isToBeEffectivelyFinal ? "variable.must.be.final.or.effectively.final" : "variable.must.be.final", context.getText());
697
698       final HighlightInfo highlightInfo =
699         HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(description).create();
700       QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass));
701       return highlightInfo;
702     }
703     return checkWriteToFinalInsideLambda(variable, context);
704   }
705
706   private static HighlightInfo checkWriteToFinalInsideLambda(@NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context) {
707     final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(context, PsiLambdaExpression.class);
708     if (lambdaExpression != null && !PsiTreeUtil.isAncestor(lambdaExpression, variable, true)) {
709       final PsiElement parent = variable.getParent();
710       if (parent instanceof PsiParameterList && parent.getParent() == lambdaExpression) {
711         return null;
712       }
713       if (!isEffectivelyFinal(variable, lambdaExpression, context)) {
714         String text = JavaErrorMessages.message("lambda.variable.must.be.final");
715         HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(text).create();
716         QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, lambdaExpression));
717         return ErrorFixExtensionPoint.registerFixes(highlightInfo, context, "lambda.variable.must.be.final");
718       }
719     }
720     return null;
721   }
722
723   public static boolean isEffectivelyFinal(@NotNull PsiVariable variable, @NotNull PsiElement scope, @Nullable PsiJavaCodeReferenceElement context) {
724     boolean effectivelyFinal;
725     if (variable instanceof PsiParameter) {
726       effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(((PsiParameter)variable).getDeclarationScope()));
727     }
728     else {
729       final ControlFlow controlFlow;
730       try {
731         PsiElement codeBlock = PsiUtil.getVariableCodeBlock(variable, context);
732         if (codeBlock == null) return true;
733         controlFlow = getControlFlow(codeBlock);
734       }
735       catch (AnalysisCanceledException e) {
736         return true;
737       }
738
739       final Collection<ControlFlowUtil.VariableInfo> initializedTwice = ControlFlowUtil.getInitializedTwice(controlFlow);
740       effectivelyFinal = !initializedTwice.contains(new ControlFlowUtil.VariableInfo(variable, null));
741       if (effectivelyFinal) {
742         final List<PsiReferenceExpression> readBeforeWriteLocals = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow);
743         for (PsiReferenceExpression expression : readBeforeWriteLocals) {
744           if (expression.resolve() == variable) {
745             return PsiUtil.isAccessedForReading(expression);
746           }
747         }
748         effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(scope));
749         if (effectivelyFinal) {
750           return ReferencesSearch.search(variable).allMatch(ref -> {
751             PsiElement element = ref.getElement();
752             if (element instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression)element)) {
753               return !ControlFlowUtil.isVariableAssignedInLoop((PsiReferenceExpression)element, variable);
754             }
755             return true;
756           });
757         }
758       }
759     }
760     return effectivelyFinal;
761   }
762
763   private static boolean notAccessedForWriting(@NotNull PsiVariable variable, @NotNull LocalSearchScope searchScope) {
764     for (PsiReference reference : ReferencesSearch.search(variable, searchScope)) {
765       final PsiElement element = reference.getElement();
766       if (element instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression)element)) {
767         return false;
768       }
769     }
770     return true;
771   }
772
773   @Nullable
774   public static PsiElement getInnerClassVariableReferencedFrom(@NotNull PsiVariable variable, @NotNull PsiElement context) {
775     final PsiElement[] scope;
776     if (variable instanceof PsiResourceVariable) {
777       scope = ((PsiResourceVariable)variable).getDeclarationScope();
778     }
779     else if (variable instanceof PsiLocalVariable) {
780       final PsiElement parent = variable.getParent();
781       scope = new PsiElement[]{parent != null ? parent.getParent() : null}; // code block or for statement
782     }
783     else if (variable instanceof PsiParameter) {
784       scope = new PsiElement[]{((PsiParameter)variable).getDeclarationScope()};
785     }
786     else {
787       scope = new PsiElement[]{variable.getParent()};
788     }
789     if (scope.length < 1 || scope[0] == null || scope[0].getContainingFile() != context.getContainingFile()) return null;
790
791     PsiElement parent = context.getParent();
792     PsiElement prevParent = context;
793     outer:
794     while (parent != null) {
795       for (PsiElement scopeElement : scope) {
796         if (parent.equals(scopeElement)) break outer;
797       }
798       if (parent instanceof PsiClass && !(prevParent instanceof PsiExpressionList && parent instanceof PsiAnonymousClass)) {
799         return parent;
800       }
801       if (parent instanceof PsiLambdaExpression) {
802         return parent;
803       }
804       prevParent = parent;
805       parent = parent.getParent();
806     }
807     return null;
808   }
809
810
811   @Nullable
812   static HighlightInfo checkInitializerCompleteNormally(@NotNull PsiClassInitializer initializer) {
813     final PsiCodeBlock body = initializer.getBody();
814     // unhandled exceptions already reported
815     try {
816       final ControlFlow controlFlow = getControlFlowNoConstantEvaluate(body);
817       final int completionReasons = ControlFlowUtil.getCompletionReasons(controlFlow, 0, controlFlow.getSize());
818       if (!BitUtil.isSet(completionReasons, ControlFlowUtil.NORMAL_COMPLETION_REASON)) {
819         String description = JavaErrorMessages.message("initializer.must.be.able.to.complete.normally");
820         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(body).descriptionAndTooltip(description).create();
821       }
822     }
823     catch (AnalysisCanceledException e) {
824       // incomplete code
825     }
826     return null;
827   }
828 }