Skip to content

Commit 3bc205d

Browse files
Fix cases were additionally a prologue exists
1 parent 168a892 commit 3bc205d

2 files changed

Lines changed: 111 additions & 15 deletions

File tree

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public void analyseCode(ClassScope classScope, InitializationFlowContext initial
9090
// Effect of 'AnalysisMode mode':
9191
// ALL: analyse in one go as normal.
9292
// PROLOGUE: analyse only statements up-to the explicit constructor call (arguments of this call are technically prologue, too)
93+
// if no relevant prologue exists, this invocation does nothing, and due to prologueInfo==null the next invocation will use mode ALL
9394
// REST: analyse only statements after the explicit constructor call
9495
// FlowContext and FlowInfo produced during PROLOGUE will be held in fields prologueContext and prologueInfo for use during REST
9596
// prologueInfo is furthermore assumed to happen *before* any field initializers, see its use in TypeDeclaration.internalAnalyseCode()
@@ -214,20 +215,16 @@ public void analyseCode(ClassScope classScope, InitializationFlowContext initial
214215
// propagate to constructor call
215216
if (this.constructorCall != null) {
216217
flowInfo = this.constructorCall.analyseCode(this.scope, constructorContext, flowInfo);
217-
if (mode == AnalysisMode.PROLOGUE && hasArgumentNeedingAnalysis)
218-
this.prologueInfo = flowInfo.copy();
219-
}
220-
}
221-
if (this.constructorCall != null && this.constructorCall.accessMode == ExplicitConstructorCall.This) {
222-
// if calling 'this(...)', then flag all non-static fields as definitely
223-
// set since they are supposed to be set inside other local constructor
224-
FieldBinding[] fields = this.binding.declaringClass.fields();
225-
for (FieldBinding field : fields) {
226-
if (!field.isStatic()) {
227-
flowInfo.markAsDefinitelyAssigned(field);
218+
if (mode == AnalysisMode.PROLOGUE) {
219+
if (hasArgumentNeedingAnalysis)
220+
this.prologueInfo = flowInfo.copy();
221+
return;
228222
}
229223
}
230224
}
225+
if (this.constructorCall != null && mode != AnalysisMode.PROLOGUE) {
226+
markFieldsAsInitializedAfterThisCall(this.constructorCall, flowInfo);
227+
}
231228
// reuse the reachMode from non static field info
232229
flowInfo.setReachMode(nonStaticFieldInfoReachMode);
233230

@@ -238,8 +235,10 @@ public void analyseCode(ClassScope classScope, InitializationFlowContext initial
238235
int complaintLevel = (nonStaticFieldInfoReachMode & FlowInfo.UNREACHABLE) == 0 ? Statement.NOT_COMPLAINED : Statement.COMPLAINED_FAKE_REACHABLE;
239236
for (Statement stat : this.statements) {
240237
if (mode == AnalysisMode.REST && lateConstructorCall != null) {
241-
if (stat == lateConstructorCall) // if true this is where we start analysing
238+
if (stat == lateConstructorCall) { // if true this is where we start analysing
239+
markFieldsAsInitializedAfterThisCall(lateConstructorCall, flowInfo);
242240
lateConstructorCall = null; // no more checking for subsequent statements
241+
}
243242
continue; // skip statements already processed during PROLOGUE analysis
244243
}
245244
if ((complaintLevel = stat.complainIfUnreachable(flowInfo, this.scope, complaintLevel, true)) < Statement.COMPLAINED_UNREACHABLE) {
@@ -256,9 +255,8 @@ public void analyseCode(ClassScope classScope, InitializationFlowContext initial
256255
}
257256
}
258257
if (mode == AnalysisMode.PROLOGUE) {
259-
if (this.prologueInfo == null) // don't overwrite info stored in the context of this.constructorCall
260-
this.prologueInfo = flowInfo; // keep for second iteration, also signals the need for REST analysis
261-
return; // we're done for this time
258+
this.prologueInfo = flowInfo; // keep for second iteration, also signals the need for REST analysis
259+
return; // we're done for this time
262260
}
263261
}
264262
// check for missing returning path
@@ -296,6 +294,19 @@ public void analyseCode(ClassScope classScope, InitializationFlowContext initial
296294
}
297295
}
298296

297+
private void markFieldsAsInitializedAfterThisCall(ExplicitConstructorCall call, FlowInfo flowInfo) {
298+
if (call.accessMode == ExplicitConstructorCall.This) {
299+
// if calling 'this(...)', then flag all non-static fields as definitely
300+
// set since they are supposed to be set inside other local constructor
301+
FieldBinding[] fields = this.binding.declaringClass.fields();
302+
for (FieldBinding field : fields) {
303+
if (!field.isStatic()) {
304+
flowInfo.markAsDefinitelyAssigned(field);
305+
}
306+
}
307+
}
308+
}
309+
299310
@Override
300311
public AbstractVariableDeclaration[] arguments(boolean includedElided) {
301312
return includedElided && this.isCompactConstructor() ? this.protoArguments : super.arguments(includedElided);

org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/InitializationTests.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,27 @@ public WrongNotInitialized() {
513513
"""
514514
});
515515
}
516+
public void testIssue4416_withPrologue() {
517+
runConformTest(new String[] {
518+
"WrongNotInitialized.java",
519+
"""
520+
public class WrongNotInitialized {
521+
private final Object o;
522+
523+
public WrongNotInitialized(final Object o) {
524+
super();
525+
this.o = o;
526+
}
527+
528+
public WrongNotInitialized() {
529+
System.out.println();
530+
this(new Object());
531+
System.out.println(o.toString());
532+
}
533+
}
534+
"""
535+
});
536+
}
516537
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4416
517538
// Bogus error: The blank final field o may not have been initialized
518539
public void testIssue4416b() {
@@ -537,6 +558,29 @@ public class Test {
537558
"""
538559
});
539560
}
561+
public void testIssue4416b_withPrologue() {
562+
runConformTest(new String[] {
563+
"Test.java",
564+
"""
565+
public class Test {
566+
567+
final int x;
568+
569+
Test(int x) {
570+
this.x = x;
571+
}
572+
573+
Test(Test a) {
574+
System.out.println();
575+
this(a.x);
576+
//this(1);
577+
578+
System.out.println(x);
579+
}
580+
}
581+
"""
582+
});
583+
}
540584
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4416
541585
// Bogus error: The blank final field o may not have been initialized
542586
public void testIssue4416c() {
@@ -556,6 +600,24 @@ public TestRecord(int b)
556600
"""
557601
});
558602
}
603+
public void testIssue4416c_withPrologue() {
604+
if (this.complianceLevel < ClassFileConstants.JDK16)
605+
return;
606+
runConformTest(new String[] {
607+
"TestRecord.java",
608+
"""
609+
public record TestRecord(String a)
610+
{
611+
public TestRecord(int b)
612+
{
613+
System.out.println();
614+
this(String.valueOf(b));
615+
System.out.println(a.length());
616+
}
617+
}
618+
"""
619+
});
620+
}
559621
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4416
560622
// Bogus error: The blank final field o may not have been initialized
561623
public void testIssue4416d() {
@@ -578,6 +640,29 @@ public class Test {
578640
"""
579641
});
580642
}
643+
//https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4416
644+
//Bogus error: The blank final field o may not have been initialized
645+
public void testIssue4416d_withPrologue() {
646+
runConformTest(new String[] {
647+
"Test.java",
648+
"""
649+
public class Test {
650+
651+
private final Object r;
652+
653+
Test() {
654+
System.out.println();
655+
this(Integer.valueOf(1));
656+
r.hashCode();
657+
}
658+
659+
Test(Object r) {
660+
this.r = r;
661+
}
662+
}
663+
"""
664+
});
665+
}
581666
public static Class testClass() {
582667
return InitializationTests.class;
583668
}

0 commit comments

Comments
 (0)