[java-highlighting] More control-flow-related stuff migrated

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 3c292fdf4869e6d13c16fabbf42e2055ea54f724
This commit is contained in:
Tagir Valeev
2025-02-06 10:35:32 +00:00
committed by intellij-monorepo-bot
parent 69cdc7a345
commit cc643a5ae4
31 changed files with 455 additions and 451 deletions
@@ -378,6 +378,8 @@ variable.must.be.final=Variable ''{0}'' is accessed from within inner class, nee
variable.must.be.effectively.final=Variable ''{0}'' is accessed from within inner class, needs to be final or effectively final
variable.must.be.effectively.final.lambda=Variable used in lambda expression should be final or effectively final
variable.must.be.effectively.final.guard=Variable used in guard expression should be final or effectively final
variable.not.initialized=Variable ''{0}'' might not have been initialized
field.not.initialized=Field ''{0}'' might not have been initialized
instanceof.type.parameter=Class or array expected
instanceof.illegal.generic.type=Illegal generic type for instanceof
@@ -11,13 +11,22 @@ import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.BitUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
final class ControlFlowChecker {
private final @NotNull JavaErrorVisitor myVisitor;
// map codeBlock->List of PsiReferenceExpression of uninitialized final variables
private final Map<PsiElement, Collection<PsiReferenceExpression>> myUninitializedVarProblems = new HashMap<>();
// map codeBlock->List of PsiReferenceExpression of extra initialization of final variable
private final Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> myFinalVarProblems = new HashMap<>();
ControlFlowChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; }
@@ -123,6 +132,20 @@ final class ControlFlowChecker {
}
}
void checkFinalFieldInitialized(@NotNull PsiField field) {
if (!field.hasModifierProperty(PsiModifier.FINAL)) return;
if (ControlFlowUtil.isFieldInitializedAfterObjectConstruction(field)) return;
if (PsiUtilCore.hasErrorElementChild(field)) return;
myVisitor.report(JavaErrorKinds.FIELD_NOT_INITIALIZED.create(field));
}
void checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable) {
if (ControlFlowUtil.isInitializedBeforeUsage(expression, variable, myUninitializedVarProblems, false)) {
return;
}
myVisitor.report(JavaErrorKinds.VARIABLE_NOT_INITIALIZED.create(expression, variable));
}
/**
* @return field that has initializer with this element as subexpression or null if not found
*/
@@ -506,6 +506,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
public void visitField(@NotNull PsiField field) {
super.visitField(field);
if (!hasErrorResults()) myClassChecker.checkIllegalInstanceMemberInRecord(field);
if (!hasErrorResults()) myControlFlowChecker.checkFinalFieldInitialized(field);
}
@Override
@@ -701,10 +702,11 @@ final class JavaErrorVisitor extends JavaElementVisitor {
PsiElement resolved = result.getElement();
PsiElement parent = expression.getParent();
PsiExpression qualifierExpression = expression.getQualifierExpression();
if (resolved instanceof PsiVariable && resolved.getContainingFile() == expression.getContainingFile()) {
if (resolved instanceof PsiVariable variable && resolved.getContainingFile() == expression.getContainingFile()) {
if (!hasErrorResults() && resolved instanceof PsiLocalVariable localVariable) {
myExpressionChecker.checkVarTypeSelfReferencing(localVariable, expression);
}
if (!hasErrorResults()) myControlFlowChecker.checkVariableInitializedBeforeUsage(expression, variable);
}
if (parent instanceof PsiMethodCallExpression methodCallExpression &&
methodCallExpression.getMethodExpression() == expression &&
@@ -1236,6 +1236,13 @@ public final class JavaErrorKinds {
parameterized(PsiReturnStatement.class, PsiMethodCallExpression.class, "return.before.explicit.constructor.call")
.withRawDescription((psi, call) -> message("return.before.explicit.constructor.call", call.getMethodExpression().getText() + "()"));
public static final Simple<PsiField> FIELD_NOT_INITIALIZED =
error(PsiField.class, "field.not.initialized")
.withRange(JavaErrorFormatUtil::getFieldDeclarationTextRange)
.withRawDescription(var -> message("field.not.initialized", var.getName()));
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_NOT_INITIALIZED =
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.not.initialized")
.withRawDescription((ref, var) -> message("variable.not.initialized", var.getName()));
public static final Parameterized<PsiJavaCodeReferenceElement, PsiVariable> VARIABLE_MUST_BE_FINAL =
parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.final")
.withRawDescription((ref, var) -> message("variable.must.be.final", var.getName()));
@@ -4,23 +4,13 @@ package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
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.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.lang.jvm.JvmModifier;
import com.intellij.lang.jvm.actions.ChangeModifierRequest;
import com.intellij.lang.jvm.actions.JvmElementActionFactories;
import com.intellij.lang.jvm.actions.MemberRequestsKt;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.util.Predicates;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.light.LightRecordField;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
@@ -28,7 +18,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Predicate;
public final class HighlightControlFlowUtil {
@@ -46,106 +35,25 @@ public final class HighlightControlFlowUtil {
return ControlFlowFactory.getControlFlowNoConstantEvaluate(body);
}
/**
* @deprecated use {@link ControlFlowUtil#variableDefinitelyAssignedIn(PsiVariable, PsiElement)}
*/
@Deprecated
public static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
return ControlFlowUtil.variableDefinitelyAssignedIn(variable, context);
}
private static @NotNull ControlFlow getControlFlow(@NotNull PsiElement context) throws AnalysisCanceledException {
LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance();
return ControlFlowFactory.getControlFlow(context, policy, ControlFlowOptions.create(true, true, true));
}
public static boolean isFieldInitializedAfterObjectConstruction(@NotNull PsiField field) {
if (field.hasInitializer()) return true;
boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
PsiClass aClass = field.getContainingClass();
if (aClass != null) {
// field might be assigned in the other field initializers
if (isFieldInitializedInOtherFieldInitializer(aClass, field, isFieldStatic, Predicates.alwaysTrue())) return true;
}
PsiClassInitializer[] initializers;
if (aClass != null) {
initializers = aClass.getInitializers();
}
else {
return false;
}
if (isFieldInitializedInClassInitializer(field, isFieldStatic, initializers)) return true;
if (isFieldStatic) {
return false;
}
else {
// instance field should be initialized at the end of each constructor
PsiMethod[] constructors = aClass.getConstructors();
if (constructors.length == 0) return false;
nextConstructor:
for (PsiMethod constructor : constructors) {
PsiCodeBlock ctrBody = constructor.getBody();
if (ctrBody == null) return false;
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
PsiCodeBlock body = redirectedConstructor.getBody();
if (body != null && variableDefinitelyAssignedIn(field, body, true)) continue nextConstructor;
}
if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody, true)) {
continue;
}
return false;
}
return true;
}
}
private static boolean isFieldInitializedInClassInitializer(@NotNull PsiField field,
boolean isFieldStatic,
PsiClassInitializer @NotNull [] initializers) {
return ContainerUtil.find(initializers, initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic
&& variableDefinitelyAssignedIn(field, initializer.getBody(), true)) != null;
}
private static boolean isFieldInitializedInOtherFieldInitializer(@NotNull PsiClass aClass,
@NotNull PsiField field,
boolean fieldStatic,
@NotNull Predicate<? super PsiField> condition) {
for (PsiField psiField : aClass.getFields()) {
if (psiField != field
&& psiField.hasModifierProperty(PsiModifier.STATIC) == fieldStatic
&& variableDefinitelyAssignedIn(field, psiField, true)
&& condition.test(psiField)) {
return true;
}
}
return false;
}
public static boolean isAssigned(@NotNull PsiParameter parameter) {
ParamWriteProcessor processor = new ParamWriteProcessor();
ReferencesSearch.search(parameter, new LocalSearchScope(parameter.getDeclarationScope()), true).forEach(processor);
return processor.isWriteRefFound();
}
/**
* @return field that has initializer with this element as subexpression or null if not found
*/
private static PsiField findEnclosingFieldInitializer(@NotNull PsiElement entry) {
PsiElement element = entry;
while (element != null) {
PsiElement parent = element.getParent();
if (parent instanceof PsiField field) {
if (element == field.getInitializer()) return field;
if (field instanceof PsiEnumConstant enumConstant && element == enumConstant.getArgumentList()) return field;
}
if (element instanceof PsiClass || element instanceof PsiMethod) return null;
element = parent;
}
return null;
}
public static @NotNull TextRange getFixRange(@NotNull PsiElement element) {
PsiElement nextSibling = element.getNextSibling();
TextRange range = element.getTextRange();
if (PsiUtil.isJavaToken(nextSibling, JavaTokenType.SEMICOLON)) {
return range.grown(1);
}
return range;
}
private static class ParamWriteProcessor implements Processor<PsiReference> {
private volatile boolean myIsWriteRefFound;
@Override
@@ -163,25 +71,6 @@ public final class HighlightControlFlowUtil {
}
}
/**
* see JLS chapter 16
* @return true if variable assigned (maybe more than once)
*/
public static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
return variableDefinitelyAssignedIn(variable, context, false);
}
private static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable,
@NotNull PsiElement context,
boolean resultOnIncompleteCode) {
try {
return ControlFlowUtil.isVariableDefinitelyAssigned(variable, getControlFlow(context));
}
catch (AnalysisCanceledException e) {
return resultOnIncompleteCode;
}
}
private static boolean variableDefinitelyNotAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
try {
return ControlFlowUtil.isVariableDefinitelyNotAssigned(variable, getControlFlow(context));
@@ -191,244 +80,11 @@ public final class HighlightControlFlowUtil {
}
}
static HighlightInfo.Builder checkFinalFieldInitialized(@NotNull PsiField field) {
if (!field.hasModifierProperty(PsiModifier.FINAL)) return null;
if (isFieldInitializedAfterObjectConstruction(field)) return null;
if (PsiUtilCore.hasErrorElementChild(field)) return null;
String description = JavaErrorBundle.message("variable.not.initialized", field.getName());
TextRange range = HighlightNamesUtil.getFieldDeclarationTextRange(field);
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description);
IntentionAction action3 = getQuickFixFactory().createCreateConstructorParameterFromFieldFix(field);
builder.registerFix(action3, null, null, getFixRange(field), null);
IntentionAction action2 = getQuickFixFactory().createInitializeFinalFieldInConstructorFix(field);
builder.registerFix(action2, null, null, getFixRange(field), null);
IntentionAction action1 = getQuickFixFactory().createAddVariableInitializerFix(field);
builder.registerFix(action1, null, null, null, null);
PsiClass containingClass = field.getContainingClass();
if (containingClass != null && !containingClass.isInterface()) {
IntentionAction action = getQuickFixFactory().createModifierListFix(field, PsiModifier.FINAL, false, false);
builder.registerFix(action, null, null, null, null);
}
return builder;
}
static HighlightInfo.Builder checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems) {
if (isInitializedBeforeUsage(expression, variable, uninitializedVarProblems, false)) return null;
return createNotInitializedError(expression, variable);
}
public static boolean isInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems,
boolean ignoreFinality) {
if (variable instanceof ImplicitVariable) return true;
if (!PsiUtil.isAccessedForReading(expression)) return true;
int startOffset = expression.getTextRange().getStartOffset();
PsiElement topBlock = getTopBlock(expression, variable);
if (topBlock == null) return true;
if (!variable.hasInitializer()) {
if (variable instanceof PsiField field) {
// non-final field already initialized with default value
if (!ignoreFinality && !variable.hasModifierProperty(PsiModifier.FINAL)) return true;
// a 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
&& findEnclosingFieldInitializer(expression) == null) {
return true;
}
PsiElement parent = topBlock.getParent();
// access to final fields from inner classes always allowed
if (inInnerClass(expression, field.getContainingClass())) return true;
PsiCodeBlock block;
PsiClass aClass;
if (parent instanceof PsiMethod constructor) {
if (!constructor.getManager().areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) return true;
// static variables already initialized in class initializers
if (variable.hasModifierProperty(PsiModifier.STATIC)) return true;
// as a last chance, field may be initialized in this() call
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
// variable must be initialized before its usage
//???
//if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue;
if (JavaPsiRecordUtil.isCompactConstructor(redirectedConstructor)) return true;
PsiCodeBlock body = redirectedConstructor.getBody();
if (body != null && variableDefinitelyAssignedIn(variable, body, true)) {
return true;
}
}
block = constructor.getBody();
aClass = constructor.getContainingClass();
}
else if (parent instanceof PsiClassInitializer classInitializer) {
if (!classInitializer.getManager().areElementsEquivalent(classInitializer.getContainingClass(), field.getContainingClass())) {
return true;
}
block = classInitializer.getBody();
aClass = classInitializer.getContainingClass();
if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, field, variable.hasModifierProperty(PsiModifier.STATIC),
f -> startOffset > f.getTextOffset())) {
return true;
}
}
else {
// field reference outside code block
// check variable initialized before its usage
aClass = field.getContainingClass();
PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class);
if (aClass == null ||
isFieldInitializedInOtherFieldInitializer(aClass, field, field.hasModifierProperty(PsiModifier.STATIC),
f -> f != anotherField && startOffset > f.getTextOffset())) {
return true;
}
if (anotherField != null
&& !anotherField.hasModifierProperty(PsiModifier.STATIC)
&& field.hasModifierProperty(PsiModifier.STATIC)
&& isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) {
return true;
}
if (anotherField != null && anotherField.hasInitializer() && !PsiAugmentProvider.canTrustFieldInitializer(anotherField)) {
return true;
}
int offset = startOffset;
if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) {
offset = 0;
}
block = null;
// initializers will be checked later
for (PsiMethod constructor : aClass.getConstructors()) {
// variable must be initialized before its usage
if (offset < constructor.getTextRange().getStartOffset()) continue;
PsiCodeBlock body = constructor.getBody();
if (body != null && variableDefinitelyAssignedIn(variable, body)) {
return true;
}
// as a last chance, field may be initialized in this() call
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
// variable must be initialized before its usage
if (offset < redirectedConstructor.getTextRange().getStartOffset()) continue;
PsiCodeBlock redirectedBody = redirectedConstructor.getBody();
if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) {
return true;
}
}
}
}
if (aClass != null) {
// field may be initialized in class initializer
for (PsiClassInitializer initializer : aClass.getInitializers()) {
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 true;
}
}
}
}
}
Collection<PsiReferenceExpression> codeBlockProblems = uninitializedVarProblems.get(topBlock);
if (codeBlockProblems == null) {
try {
ControlFlow controlFlow = getControlFlow(topBlock);
codeBlockProblems = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow);
}
catch (AnalysisCanceledException | IndexNotReadyException e) {
codeBlockProblems = Collections.emptyList();
}
uninitializedVarProblems.put(topBlock, codeBlockProblems);
}
return !codeBlockProblems.contains(expression);
}
private static @Nullable PsiElement getTopBlock(@NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable) {
PsiElement topBlock;
if (variable.hasInitializer()) {
topBlock = PsiUtil.getVariableCodeBlock(variable, variable);
if (topBlock == null) return null;
}
else {
PsiElement scope = variable instanceof PsiField field
? field.getContainingClass()
: variable.getParent() != null ? variable.getParent().getParent() : null;
while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchBlock) {
scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class);
}
topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile
? scope
: PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
}
return topBlock;
}
private static HighlightInfo.@NotNull Builder createNotInitializedError(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable) {
String name = expression.getElement().getText();
String description = JavaErrorBundle.message("variable.not.initialized", name);
HighlightInfo.Builder builder =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description);
if (!(variable instanceof LightRecordField)) {
IntentionAction action1 = getQuickFixFactory().createAddVariableInitializerFix(variable);
builder.registerFix(action1, null, null, null, null);
}
if (variable instanceof PsiLocalVariable) {
PsiElement topBlock = getTopBlock(expression, variable);
if (topBlock != null) {
IntentionAction action = HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, expression);
if (action != null) {
builder.registerFix(action, null, null, null, null);
}
}
}
if (variable instanceof PsiField field) {
ChangeModifierRequest request = MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false);
QuickFixAction.registerQuickFixActions(builder, null, JvmElementActionFactories.createModifierActions(field, request));
}
return builder;
}
private static boolean inInnerClass(@NotNull PsiElement psiElement, @Nullable PsiClass containingClass) {
for (PsiElement element = psiElement; element != null; element = element.getParent()) {
if (element instanceof PsiClass aClass) {
boolean innerClass = !psiElement.getManager().areElementsEquivalent(element, containingClass);
if (innerClass) {
if (element instanceof PsiAnonymousClass anonymous) {
if (PsiTreeUtil.isAncestor(anonymous.getArgumentList(), psiElement, false)) {
continue;
}
return !insideClassInitialization(containingClass, aClass);
}
PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class);
return lambdaExpression == null || !insideClassInitialization(containingClass, aClass);
}
return false;
}
}
return false;
}
private static boolean insideClassInitialization(@Nullable PsiClass containingClass, PsiClass aClass) {
PsiMember member = aClass;
while (member != null) {
if (member.getContainingClass() == containingClass) {
return member instanceof PsiField ||
member instanceof PsiMethod method && method.isConstructor() ||
member instanceof PsiClassInitializer;
}
member = PsiTreeUtil.getParentOfType(member, PsiMember.class, true);
}
return false;
}
/**
* @param variable variable to check
* @param finalVarProblems cache map to reuse information
* @return true if variable is reassigned
*/
public static boolean isReassigned(@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) {
if (variable instanceof PsiLocalVariable) {
@@ -67,8 +67,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private PreviewFeatureUtil.PreviewFeatureVisitor myPreviewFeatureVisitor;
// map codeBlock->List of PsiReferenceExpression of uninitialized final variables
private final Map<PsiElement, Collection<PsiReferenceExpression>> myUninitializedVarProblems = new HashMap<>();
// map codeBlock->List of PsiReferenceExpression of extra initialization of final variable
private final Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> myFinalVarProblems = new HashMap<>();
@@ -159,7 +157,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
finally {
myUninitializedVarProblems.clear();
myFinalVarProblems.clear();
myJavaModule = null;
myFile = null;
@@ -274,12 +271,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!hasErrorResults()) GenericsHighlightUtil.checkTypeParameterOverrideEquivalentMethods(aClass, myLanguageLevel, myErrorSink, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
}
@Override
public void visitField(@NotNull PsiField field) {
super.visitField(field);
if (!hasErrorResults()) add(HighlightControlFlowUtil.checkFinalFieldInitialized(field));
}
@Override
public void visitImportStaticStatement(@NotNull PsiImportStaticStatement statement) {
visitElement(statement);
@@ -491,13 +482,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
add(HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, myFinalVarProblems));
}
}
if (!hasErrorResults()) {
try {
add(HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, variable, myUninitializedVarProblems));
}
catch (IndexNotReadyException ignored) {
}
}
}
if (!hasErrorResults()) add(HighlightUtil.checkClassReferenceAfterQualifier(expression, resolved));
@@ -28,6 +28,7 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.impl.light.LightRecordField;
import com.intellij.psi.impl.light.LightRecordMethod;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.infos.MethodCandidateInfo;
@@ -387,6 +388,31 @@ final class JavaErrorFixProvider {
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
fixes(FIELD_NOT_INITIALIZED, (error, sink) -> {
PsiField field = error.psi();
sink.accept(myFactory.createCreateConstructorParameterFromFieldFix(field));
sink.accept(myFactory.createInitializeFinalFieldInConstructorFix(field));
sink.accept(myFactory.createAddVariableInitializerFix(field));
PsiClass containingClass = field.getContainingClass();
if (containingClass != null && !containingClass.isInterface()) {
sink.accept(removeModifierFix(field, PsiModifier.FINAL));
}
});
fixes(VARIABLE_NOT_INITIALIZED, (error, sink) -> {
PsiVariable variable = error.context();
if (!(variable instanceof LightRecordField)) {
sink.accept(myFactory.createAddVariableInitializerFix(variable));
}
if (variable instanceof PsiLocalVariable) {
PsiElement topBlock = PsiUtil.getVariableCodeBlock(variable, null);
if (topBlock != null) {
sink.accept(HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, error.psi()));
}
}
if (variable instanceof PsiField field) {
sink.accept(removeModifierFix(field, PsiModifier.FINAL));
}
});
}
private void createExpressionFixes() {
@@ -23,7 +23,7 @@ public final class FinalUtils {
// parameters have an implicit initializer
return !VariableAccessUtils.variableIsAssigned(variable);
}
if (variable instanceof PsiField && !HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction((PsiField)variable)) {
if (variable instanceof PsiField && !ControlFlowUtil.isFieldInitializedAfterObjectConstruction((PsiField)variable)) {
return false;
}
return checkIfElementViolatesFinality(variable);
@@ -57,7 +57,7 @@ public final class FinalUtils {
Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) {
if (!(e instanceof PsiReferenceExpression ref)) return true;
if (!ref.isReferenceTo(variable)) return true;
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(
if (!ControlFlowUtil.isInitializedBeforeUsage(
ref, variable, uninitializedVarProblems, true)) {
return false;
}
@@ -4,7 +4,6 @@ package com.intellij.codeInspection.classCanBeRecord;
import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.util.IntentionFamilyName;
@@ -12,6 +11,7 @@ import com.intellij.java.JavaBundle;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.PsiAnnotation.TargetType;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PropertyUtil;
@@ -343,7 +343,7 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
myCanonical = false;
return;
}
if (!HighlightControlFlowUtil.variableDefinitelyAssignedIn(instanceField, ctorBody)) {
if (!ControlFlowUtil.variableDefinitelyAssignedIn(instanceField, ctorBody)) {
myCanonical = false;
return;
}
@@ -2,7 +2,6 @@
package com.intellij.codeInspection.defUse;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.dataFlow.java.ControlFlowAnalyzer;
import com.intellij.codeInspection.dataFlow.java.anchor.JavaExpressionAnchor;
@@ -179,7 +178,7 @@ public final class DefUseInspection extends AbstractBaseJavaLocalInspectionTool
if (classInitializer.hasModifierProperty(PsiModifier.STATIC) == isStatic) {
final List<PsiAssignmentExpression> assignments = collectAssignments(field, classInitializer);
if (!assignments.isEmpty()) {
boolean isDefinitely = HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, classInitializer.getBody());
boolean isDefinitely = ControlFlowUtil.variableDefinitelyAssignedIn(field, classInitializer.getBody());
if (isDefinitely) {
try {
ControlFlow flow = ControlFlowFactory.getControlFlowNoConstantEvaluate(classInitializer.getBody());
@@ -229,7 +228,7 @@ public final class DefUseInspection extends AbstractBaseJavaLocalInspectionTool
for (PsiMethod constructor : constructors) {
if (!JavaPsiConstructorUtil.getChainedConstructors(constructor).isEmpty()) continue;
final PsiCodeBlock body = constructor.getBody();
if (body == null || !HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
if (body == null || !ControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
return false;
}
try {
@@ -2,7 +2,6 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.intention.PriorityAction;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.codeInspection.util.IntentionName;
@@ -14,6 +13,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.util.*;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
@@ -170,7 +170,7 @@ public final class BringVariableIntoScopeFix implements ModCommandAction {
outOfScopeVariable.delete();
}
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(reference, addedVar, new HashMap<>(), false)) {
if (!ControlFlowUtil.isInitializedBeforeUsage(reference, addedVar, new HashMap<>(), false)) {
initialize(addedVar);
}
}
@@ -5,7 +5,6 @@ import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.generation.PsiFieldMember;
import com.intellij.codeInsight.generation.PsiMethodMember;
import com.intellij.codeInsight.intention.impl.AssignFieldFromParameterAction;
@@ -16,6 +15,7 @@ import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.util.CommonJavaRefactoringUtil;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.containers.ContainerUtil;
@@ -103,7 +103,7 @@ public class CreateConstructorParameterFromFieldFix extends PsiBasedModCommandAc
if (field == startField ||
(!field.hasModifierProperty(PsiModifier.STATIC) &&
field.hasModifierProperty(PsiModifier.FINAL) &&
!HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) &&
!ControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) &&
(constructors.isEmpty() || ContainerUtil.exists(constructors, ctr -> !isFieldAssignedInConstructor(field, ctr))))) {
fields.add(field);
}
@@ -2,7 +2,6 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixActionRegistrar;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
@@ -51,7 +50,7 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider
MoveClassToModuleFix.registerFixes(registrar, ref);
if (ref instanceof PsiReferenceExpression refExpr) {
TextRange fixRange = HighlightControlFlowUtil.getFixRange(ref);
TextRange fixRange = getFixRange(ref);
registrar.register(fixRange, new RenameWrongRefFix(refExpr), null);
PsiExpression qualifier = refExpr.getQualifierExpression();
if (qualifier != null) {
@@ -186,4 +185,13 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider
public @NotNull Class<PsiJavaCodeReferenceElement> getReferenceClass() {
return PsiJavaCodeReferenceElement.class;
}
private static @NotNull TextRange getFixRange(@NotNull PsiElement element) {
PsiElement nextSibling = element.getNextSibling();
TextRange range = element.getTextRange();
if (PsiUtil.isJavaToken(nextSibling, JavaTokenType.SEMICOLON)) {
return range.grown(1);
}
return range;
}
}
@@ -317,7 +317,7 @@ public class VariableAccessFromInnerClassFix implements IntentionAction {
Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems = new HashMap<>();
for (PsiReferenceExpression expression : references) {
if (ControlFlowUtil.isVariableAssignedInLoop(expression, variable)) return false;
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(
if (!ControlFlowUtil.isInitializedBeforeUsage(
expression, variable, uninitializedVarProblems, false)) {
return false;
}
@@ -1,7 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.generation.RecordConstructorMember;
import com.intellij.java.JavaBundle;
import com.intellij.modcommand.ActionContext;
@@ -9,6 +8,7 @@ import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.Presentation;
import com.intellij.modcommand.PsiUpdateModCommandAction;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.Nls;
@@ -60,7 +60,7 @@ public final class ConvertCompactConstructorToCanonicalAction extends PsiUpdateM
PsiElementFactory factory = JavaPsiFacade.getElementFactory(compactConstructor.getProject());
for (PsiRecordComponent component : recordClass.getRecordComponents()) {
PsiField field = JavaPsiRecordUtil.getFieldForComponent(component);
if (field != null && !HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
if (field != null && !ControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
body.add(factory.createStatementFromText("this." + field.getName() + "=" + field.getName() + ";", body));
}
}
@@ -6,7 +6,6 @@ import com.intellij.codeInsight.NullabilityAnnotationInfo;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.AddVariableInitializerFix;
import com.intellij.codeInsight.daemon.impl.quickfix.InitializeFinalFieldInConstructorFix;
import com.intellij.codeInsight.intention.QuickFixFactory;
@@ -19,6 +18,7 @@ import com.intellij.codeInspection.util.InspectionMessage;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.annotations.Language;
@@ -56,7 +56,7 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(field);
if (info == null || info.getNullability() != Nullability.NOT_NULL) return;
if (HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) ||
if (ControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) ||
isWrittenIndirectly(field)) {
return;
}
@@ -124,7 +124,7 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
if (target != null && !target.hasModifierProperty(PsiModifier.STATIC) &&
target.getContainingClass() == constructor.getContainingClass() && !target.isConstructor()) {
PsiCodeBlock targetBody = target.getBody();
if (targetBody != null && HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, targetBody)) {
if (targetBody != null && ControlFlowUtil.variableDefinitelyAssignedIn(field, targetBody)) {
return true;
}
}
@@ -145,7 +145,7 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
PsiMethod method = TestFrameworks.getInstance().findSetUpMethod(field.getContainingClass());
if (method != null) {
PsiCodeBlock body = method.getBody();
if (body != null && HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
if (body != null && ControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
return true;
}
}
@@ -4,9 +4,14 @@ package com.intellij.psi.controlFlow;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.util.Predicates;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.util.FileTypeUtils;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.*;
@@ -17,6 +22,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.IntFunction;
import java.util.function.Predicate;
public final class ControlFlowUtil {
private static final Logger LOG = Logger.getInstance(ControlFlowUtil.class);
@@ -31,20 +37,7 @@ public final class ControlFlowUtil {
* another place might exist.
*/
public static @Nullable PsiElement getScopeEnforcingEffectiveFinality(@NotNull PsiVariable variable, @NotNull PsiElement context) {
PsiElement[] scope;
if (variable instanceof PsiResourceVariable) {
scope = ((PsiResourceVariable)variable).getDeclarationScope();
}
else if (variable instanceof PsiLocalVariable) {
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()};
}
PsiElement[] scope = getVariableScope(variable);
if (scope.length < 1 || scope[0] == null || scope[0].getContainingFile() != context.getContainingFile()) return null;
PsiElement parent = context.getParent();
PsiElement prevParent = context;
@@ -68,6 +61,24 @@ public final class ControlFlowUtil {
return null;
}
private static PsiElement @NotNull [] getVariableScope(@NotNull PsiVariable variable) {
PsiElement[] scope;
if (variable instanceof PsiResourceVariable) {
scope = ((PsiResourceVariable)variable).getDeclarationScope();
}
else if (variable instanceof PsiLocalVariable) {
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()};
}
return scope;
}
/**
* @param variable variable to check
* @param scope variable scope
@@ -90,16 +101,9 @@ public final class ControlFlowUtil {
}
else {
PsiElement codeBlock = PsiUtil.getVariableCodeBlock(variable, context);
ControlFlow controlFlow;
try {
if (codeBlock == null) return true;
LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance();
controlFlow = ControlFlowFactory.getControlFlow(
codeBlock, policy, ControlFlowOptions.create(true, true, true));
}
catch (AnalysisCanceledException e) {
return true;
}
if (codeBlock == null) return true;
ControlFlow controlFlow = getControlFlow(codeBlock);
if (controlFlow == null) return true;
Collection<VariableInfo> initializedTwice = getInitializedTwice(controlFlow);
effectivelyFinal = !initializedTwice.contains(new VariableInfo(variable, null));
@@ -131,12 +135,306 @@ public final class ControlFlowUtil {
return effectivelyFinal;
}
private static @Nullable ControlFlow getControlFlow(PsiElement codeBlock) {
try {
LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance();
return ControlFlowFactory.getControlFlow(
codeBlock, policy, ControlFlowOptions.create(true, true, true));
}
catch (AnalysisCanceledException e) {
return null;
}
}
private static boolean variableIsAssigned(@NotNull PsiVariable variable, @NotNull PsiElement scope) {
return !PsiTreeUtil.processElements(scope, PsiReferenceExpression.class, e -> {
return !(PsiUtil.isAccessedForWriting(e) && e.isReferenceTo(variable));
});
}
/**
* @param field field to check
* @return true if the field is initialized (in class initializer, own initializer, another field initializer, or constructor)
*/
public static boolean isFieldInitializedAfterObjectConstruction(@NotNull PsiField field) {
if (field.hasInitializer()) return true;
boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
PsiClass aClass = field.getContainingClass();
if (aClass == null) return false;
// field might be assigned in the other field initializers
if (isFieldInitializedInOtherFieldInitializer(aClass, field, Predicates.alwaysTrue())) return true;
if (isFieldInitializedInClassInitializer(field)) return true;
if (isFieldStatic) return false;
// instance field should be initialized at the end of each constructor
PsiMethod[] constructors = aClass.getConstructors();
if (constructors.length == 0) return false;
nextConstructor:
for (PsiMethod constructor : constructors) {
PsiCodeBlock ctrBody = constructor.getBody();
if (ctrBody == null) return false;
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
PsiCodeBlock body = redirectedConstructor.getBody();
if (body != null && variableDefinitelyAssignedIn(field, body, true)) continue nextConstructor;
}
if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody, true)) {
continue;
}
return false;
}
return true;
}
/**
* @param field field to check
* @return true if the field is initialized in its class initializers
*/
private static boolean isFieldInitializedInClassInitializer(@NotNull PsiField field) {
PsiClass aClass = field.getContainingClass();
if (aClass == null) return false;
PsiClassInitializer[] initializers = aClass.getInitializers();
boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
return ContainerUtil.find(initializers, initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic
&& variableDefinitelyAssignedIn(field, initializer.getBody(), true)) != null;
}
private static boolean isFieldInitializedInOtherFieldInitializer(@NotNull PsiClass aClass,
@NotNull PsiField field,
@NotNull Predicate<? super PsiField> condition) {
boolean fieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
for (PsiField psiField : aClass.getFields()) {
if (psiField != field
&& psiField.hasModifierProperty(PsiModifier.STATIC) == fieldStatic
&& variableDefinitelyAssignedIn(field, psiField, true)
&& condition.test(psiField)) {
return true;
}
}
return false;
}
/**
* @return field that has initializer with this element as subexpression or null if not found
*/
private static PsiField findEnclosingFieldInitializer(@NotNull PsiElement entry) {
PsiElement element = entry;
while (element != null) {
PsiElement parent = element.getParent();
if (parent instanceof PsiField) {
PsiField field = (PsiField)parent;
if (element == field.getInitializer()) return field;
if (field instanceof PsiEnumConstant && element == ((PsiEnumConstant)field).getArgumentList()) return field;
}
if (element instanceof PsiClass || element instanceof PsiMethod) return null;
element = parent;
}
return null;
}
/**
* see JLS chapter 16
* @param variable variable to check
* @param scope variable scope (code block, field initializer, etc.)
* @return true if variable assigned (maybe more than once)
*/
public static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement scope) {
return variableDefinitelyAssignedIn(variable, scope, false);
}
private static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable,
@NotNull PsiElement scope,
boolean resultOnIncompleteCode) {
ControlFlow flow = getControlFlow(scope);
return flow == null ? resultOnIncompleteCode : isVariableDefinitelyAssigned(variable, flow);
}
/**
* @param expression variable reference (usage)
* @param variable variable
* @param uninitializedVarProblems map to cache results from the same code block
* @param treatNonFinalFieldsAsNonInitialized if true, the non-final field will not be considered as initialized with the default value
* @return true if the variable is initialized before usage
*/
public static boolean isInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems,
boolean treatNonFinalFieldsAsNonInitialized) {
if (variable instanceof ImplicitVariable) return true;
if (!PsiUtil.isAccessedForReading(expression)) return true;
int startOffset = expression.getTextRange().getStartOffset();
PsiElement topBlock = getTopBlock(expression, variable);
if (topBlock == null) return true;
if (!variable.hasInitializer()) {
if (variable instanceof PsiField) {
PsiField field = (PsiField)variable;
// non-final field already initialized with default value
if (!treatNonFinalFieldsAsNonInitialized && !variable.hasModifierProperty(PsiModifier.FINAL)) return true;
// a final field may be initialized in ctor or class initializer only
// if we're inside a non-constructor method, skip it
if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null
&& findEnclosingFieldInitializer(expression) == null) {
return true;
}
PsiElement parent = topBlock.getParent();
// access to final fields from inner classes always allowed
if (inInnerClass(expression, field.getContainingClass())) return true;
PsiCodeBlock block;
PsiClass aClass;
if (parent instanceof PsiMethod) {
PsiMethod constructor = (PsiMethod)parent;
if (!constructor.getManager().areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) return true;
// static variables already initialized in class initializers
if (variable.hasModifierProperty(PsiModifier.STATIC)) return true;
// as a last chance, the field may be initialized in this() call
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
// variable must be initialized before its usage
//???
//if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue;
if (JavaPsiRecordUtil.isCompactConstructor(redirectedConstructor)) return true;
PsiCodeBlock body = redirectedConstructor.getBody();
if (body != null && variableDefinitelyAssignedIn(variable, body, true)) {
return true;
}
}
block = constructor.getBody();
aClass = constructor.getContainingClass();
}
else if (parent instanceof PsiClassInitializer) {
PsiClassInitializer classInitializer = (PsiClassInitializer)parent;
if (!classInitializer.getManager().areElementsEquivalent(classInitializer.getContainingClass(), field.getContainingClass())) {
return true;
}
block = classInitializer.getBody();
aClass = classInitializer.getContainingClass();
if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, field, f -> startOffset > f.getTextOffset())) {
return true;
}
}
else {
// field reference outside code block
// check variable initialized before its usage
aClass = field.getContainingClass();
PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class);
if (aClass == null ||
isFieldInitializedInOtherFieldInitializer(aClass, field, f -> f != anotherField && startOffset > f.getTextOffset())) {
return true;
}
if (anotherField != null
&& !anotherField.hasModifierProperty(PsiModifier.STATIC)
&& field.hasModifierProperty(PsiModifier.STATIC)
&& isFieldInitializedInClassInitializer(field)) {
return true;
}
if (anotherField != null && anotherField.hasInitializer() && !PsiAugmentProvider.canTrustFieldInitializer(anotherField)) {
return true;
}
int offset = startOffset;
if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) {
offset = 0;
}
block = null;
// initializers will be checked later
for (PsiMethod constructor : aClass.getConstructors()) {
// the variable must be initialized before its usage
if (offset < constructor.getTextRange().getStartOffset()) continue;
PsiCodeBlock body = constructor.getBody();
if (body != null && variableDefinitelyAssignedIn(variable, body)) {
return true;
}
// as a last chance, the field may be initialized in this() call
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
// the variable must be initialized before its usage
if (offset < redirectedConstructor.getTextRange().getStartOffset()) continue;
PsiCodeBlock redirectedBody = redirectedConstructor.getBody();
if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) {
return true;
}
}
}
}
if (aClass != null) {
// field may be initialized in class initializer
for (PsiClassInitializer initializer : aClass.getInitializers()) {
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 true;
}
}
}
}
}
Collection<PsiReferenceExpression> codeBlockProblems = uninitializedVarProblems.get(topBlock);
if (codeBlockProblems == null) {
try {
ControlFlow controlFlow = getControlFlow(topBlock);
codeBlockProblems = controlFlow == null ? Collections.emptyList() : getReadBeforeWriteLocals(controlFlow);
}
catch (IndexNotReadyException e) {
codeBlockProblems = Collections.emptyList();
}
uninitializedVarProblems.put(topBlock, codeBlockProblems);
}
return !codeBlockProblems.contains(expression);
}
private static @Nullable PsiElement getTopBlock(@NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable) {
if (variable.hasInitializer()) {
return PsiUtil.getVariableCodeBlock(variable, null);
}
PsiElement scope = variable instanceof PsiField
? ((PsiField)variable).getContainingClass()
: variable.getParent() != null ? variable.getParent().getParent() : null;
while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchBlock) {
scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class);
}
return FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile
? scope
: PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
}
private static boolean inInnerClass(@NotNull PsiElement psiElement, @Nullable PsiClass containingClass) {
for (PsiElement element = psiElement; element != null; element = element.getParent()) {
if (element instanceof PsiClass) {
PsiClass aClass = (PsiClass)element;
boolean innerClass = !psiElement.getManager().areElementsEquivalent(element, containingClass);
if (innerClass) {
if (element instanceof PsiAnonymousClass) {
if (PsiTreeUtil.isAncestor(((PsiAnonymousClass)element).getArgumentList(), psiElement, false)) {
continue;
}
return !insideClassInitialization(containingClass, aClass);
}
PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class);
return lambdaExpression == null || !insideClassInitialization(containingClass, aClass);
}
return false;
}
}
return false;
}
private static boolean insideClassInitialization(@Nullable PsiClass containingClass, PsiClass aClass) {
PsiMember member = aClass;
while (member != null) {
if (member.getContainingClass() == containingClass) {
return member instanceof PsiField ||
member instanceof PsiMethod && ((PsiMethod)member).isConstructor() ||
member instanceof PsiClassInitializer;
}
member = PsiTreeUtil.getParentOfType(member, PsiMember.class, true);
}
return false;
}
private static class SSAInstructionState {
private final int myWriteCount;
private final int myInstructionIdx;
@@ -387,7 +685,7 @@ public final class ControlFlowUtil {
}
/**
* If the variable occurs only once in the element and it's read access return that occurrence
* If the variable occurs only once in the element, and it's read access return that occurrence
*/
public static PsiReferenceExpression findSingleReadOccurrence(@NotNull ControlFlow flow,
@NotNull PsiElement element,
@@ -223,7 +223,7 @@ class AssignmentInUnreachablePolyadic {
if (true && (<error descr="Variable 'b' might already have been assigned to">b</error> = false)) ;
}
<error descr="Variable 'bb' might not have been initialized">private final boolean bb</error>;
<error descr="Field 'bb' might not have been initialized">private final boolean bb</error>;
{
if (false && (bb = false) && (<error descr="Variable 'bb' might already have been assigned to">bb</error> = true)) ;
}
@@ -255,7 +255,7 @@ class QualifiedThis {
class ParenthesizedThis {
final int x;
final int y = <error descr="Variable '(this).x' might not have been initialized">(this).x</error> + 1;
final int y = <error descr="Variable 'x' might not have been initialized">(this).x</error> + 1;
ParenthesizedThis() {
(this).x = 5; // javac disallows this -- probably a bug in javac
@@ -54,7 +54,7 @@ class c4 {
}
// redirected ctrs
class c5 {
<error descr="Variable 'k' might not have been initialized">final int k</error>;
<error descr="Field 'k' might not have been initialized">final int k</error>;
c5(int i) {
k =1;
}
@@ -5,15 +5,15 @@ class a {
/**
* javadoc should not be highlighted
*/
<error descr="Variable 'javaDoced' might not have been initialized">final int javaDoced</error>;
<error descr="Field 'javaDoced' might not have been initialized">final int javaDoced</error>;
<error descr="Variable 'sfi1' might not have been initialized">static final int sfi1</error>;
<error descr="Variable 'sfi2' might not have been initialized">static final int sfi2</error>;
<error descr="Variable 'fi1' might not have been initialized">final int fi1</error>;
<error descr="Variable 'fi2' might not have been initialized">final int fi2</error>;
<error descr="Field 'sfi1' might not have been initialized">static final int sfi1</error>;
<error descr="Field 'sfi2' might not have been initialized">static final int sfi2</error>;
<error descr="Field 'fi1' might not have been initialized">final int fi1</error>;
<error descr="Field 'fi2' might not have been initialized">final int fi2</error>;
class inner {
<error descr="Variable 'fii' might not have been initialized">final int fii</error>;
<error descr="Field 'fii' might not have been initialized">final int fii</error>;
}
final int fi3;
final int fi4;
@@ -140,7 +140,7 @@ class c2 {
}
class UninitializedFinal2 {
<error descr="Variable 's' might not have been initialized">private final String s</error>;
<error descr="Field 's' might not have been initialized">private final String s</error>;
UninitializedFinal2(){
try {
@@ -151,7 +151,7 @@ class UninitializedFinal2 {
}
class UninitedFinalFied {
<error descr="Variable 'string' might not have been initialized">private final String string</error>;
<error descr="Field 'string' might not have been initialized">private final String string</error>;
public UninitedFinalFied() throws IOException {
init();
@@ -277,7 +277,7 @@ class InitializedInClassInitializerUsedInTheFollowingFieldInitializer {
}
class AssignInAssert {
<error descr="Variable 'b' might not have been initialized">private final boolean b</error>;
<error descr="Field 'b' might not have been initialized">private final boolean b</error>;
AssignInAssert() {
assert b = true;
@@ -301,7 +301,7 @@ class DefiniteAssignmentInFinally {
}
class StaticInitializerUsedInAnotherInstanceField {
private final int myEnumerationCacheConstant = ENUMERATION_CACHE_SIZE;
private static final int ourEnumerationCacheConstant = <error descr="Cannot read value of field 'ENUMERATION_CACHE_SIZE' before the field's definition">ENUMERATION_CACHE_SIZE</error>;
private static final int ourEnumerationCacheConstant = <error descr="Variable 'ENUMERATION_CACHE_SIZE' might not have been initialized">ENUMERATION_CACHE_SIZE</error>;
private static final int ENUMERATION_CACHE_SIZE;
@@ -122,7 +122,7 @@ public class a {
public String getTreeCellRendererComponent(String value) {
if (value instanceof String) {
int i = myLabel.length();
int i = <error descr="Variable 'myLabel' might not have been initialized">myLabel</error>.length();
}
return null;
}
@@ -5,7 +5,7 @@ class Unassigned {
public int b;
Unassigned(int value) {
b = <error descr="Variable '(this).a' might not have been initialized">(this).a</error>;
b = <error descr="Variable 'a' might not have been initialized">(this).a</error>;
a = value;
}
}
@@ -92,11 +92,11 @@ class TestAnonymousWithRefToTheTopLevelUninitializedField {
class TestThisQualified {
final String s;
final Runnable r = () -> System.out.println(<error descr="Variable 'this.s' might not have been initialized">this.s</error>.length());
final Runnable r = () -> System.out.println(<error descr="Variable 's' might not have been initialized">this.s</error>.length());
final Runnable r2 = () -> System.out.println(this.r2);
final Runnable r3;
{
r3 = () -> System.out.println(<error descr="Variable 'this.r3' might not have been initialized">this.r3</error>);
r3 = () -> System.out.println(<error descr="Variable 'r3' might not have been initialized">this.r3</error>);
}
public TestThisQualified() {
@@ -11,9 +11,9 @@ class Test {
private final IntFunction multiply = i -> i * Test.this.idx;
private final int idx3 = <error descr="Variable 'this.idx' might not have been initialized">this.idx</error> + 1;
private final int idx3 = <error descr="Variable 'idx' might not have been initialized">this.idx</error> + 1;
private final IntFunction multiply2 = i -> i * <error descr="Variable 'this.idx' might not have been initialized">this.idx</error>;
private final IntFunction multiply2 = i -> i * <error descr="Variable 'idx' might not have been initialized">this.idx</error>;
public Test(int idx) {
this.idx = idx;
@@ -1,6 +1,6 @@
class MyTest {
final Runnable lambdaRunnable = () -> {
System.out.println(<error descr="Cannot read value of field 'o' before the field's definition">o</error>);
System.out.println(<error descr="Variable 'o' might not have been initialized">o</error>);
};
final Object o;
@@ -69,7 +69,7 @@ class C {
static class FinalFieldAssignedInSomeBranches {
<error descr="Variable 'n' might not have been initialized">final int n</error>;
<error descr="Field 'n' might not have been initialized">final int n</error>;
{
switch ((int)Math.random()) {
case 1 -> n = 1;
@@ -79,7 +79,7 @@ class C {
}
static class FinalFieldAssignedInSomeBranchesNoDefault {
<error descr="Variable 'n' might not have been initialized">final int n</error>;
<error descr="Field 'n' might not have been initialized">final int n</error>;
{
switch ((int)Math.random()) {
case 1 -> n = 1;
@@ -1,6 +1,6 @@
value class One {
<error descr="Variable 'value' might not have been initialized">private int value</error>;
<error descr="Field 'value' might not have been initialized">private int value</error>;
<error descr="Modifier 'synchronized' not allowed here">synchronized</error> void x() {}
}
@@ -8,7 +8,7 @@ class TypeUseWithTypeHighlighting {
<warning descr="Missorted modifiers '@ReadOnly private'">@ReadOnly</warning> private String fieldOneLine;
private <warning descr="Missorted modifiers '@ReadOnly final'">@ReadOnly</warning> <error descr="Variable 'field1' might not have been initialized">final String field1</error>;
private <warning descr="Missorted modifiers '@ReadOnly final'">@ReadOnly</warning> <error descr="Field 'field1' might not have been initialized">final String field1</error>;
private final @ReadOnly String field2 = "2";
}
@Target({ElementType.TYPE_USE, ElementType.FIELD})
@@ -8,7 +8,7 @@ class TypeUseWithTypeHighlighting {
@ReadOnly private String fieldOneLine;
private <warning descr="Missorted modifiers '@ReadOnly final'">@ReadOnly</warning> <error descr="Variable 'field1' might not have been initialized">final String field1</error>;
private <warning descr="Missorted modifiers '@ReadOnly final'">@ReadOnly</warning> <error descr="Field 'field1' might not have been initialized">final String field1</error>;
private final @ReadOnly String field2 = "2";
}
@Target({ElementType.TYPE_USE, ElementType.FIELD})
@@ -2,7 +2,6 @@
package org.jetbrains.kotlin.nj2k
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil
import com.intellij.codeInspection.localCanBeFinal.LocalCanBeFinal
import com.intellij.psi.*
import com.intellij.psi.controlFlow.ControlFlowUtil
@@ -186,7 +185,7 @@ private fun fieldConstructionImpliesMutable(field: PsiField): Boolean {
val usefulRedirectedConstructors: MutableList<PsiMethod> = ArrayList()
for (redirectedConstructor in redirectedConstructors) {
val body = redirectedConstructor.getBody()
if (body != null && (HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, body) || isValidThisMethodInConstructor(
if (body != null && (ControlFlowUtil.variableDefinitelyAssignedIn(field, body) || isValidThisMethodInConstructor(
redirectedConstructor
))
) {
@@ -194,7 +193,7 @@ private fun fieldConstructionImpliesMutable(field: PsiField): Boolean {
}
}
if (usefulRedirectedConstructors.isNotEmpty() && usefulRedirectedConstructors.size != redirectedConstructors.size) return true
if (ctrBody.isValid() && (HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, ctrBody) || isValidThisMethodInConstructor(
if (ctrBody.isValid() && (ControlFlowUtil.variableDefinitelyAssignedIn(field, ctrBody) || isValidThisMethodInConstructor(
constructor
))
) {
@@ -1,6 +1,6 @@
<error descr="Class contains required fields, you have to force NoArgsConstructor.">@lombok.NoArgsConstructor</error>
public class NoArgsConstructorWithRequiredFieldsShouldBeForced {
<error descr="Variable 'test' might not have been initialized">private final String test</error>;
<error descr="Variable 'test2' might not have been initialized">private final String test2</error>;
<error descr="Variable 'test3' might not have been initialized">private final int test3</error>;
<error descr="Field 'test' might not have been initialized">private final String test</error>;
<error descr="Field 'test2' might not have been initialized">private final String test2</error>;
<error descr="Field 'test3' might not have been initialized">private final int test3</error>;
}