8325805: Compiler Implementation for Flexible Constructor Bodies (Second Preview)

Reviewed-by: vromero, jlahoda
This commit is contained in:
Archie Cobbs 2024-05-28 13:15:20 +00:00 committed by Vicente Romero
parent e708d135e3
commit 87a06b6ce4
17 changed files with 494 additions and 29 deletions

View File

@ -208,7 +208,7 @@ public class Preview {
public boolean isPreview(Feature feature) {
return switch (feature) {
case IMPLICIT_CLASSES -> true;
case SUPER_INIT -> true;
case FLEXIBLE_CONSTRUCTORS -> true;
case PRIMITIVE_PATTERNS -> true;
case MODULE_IMPORTS -> true;
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).

View File

@ -254,7 +254,7 @@ public enum Source {
WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
UNNAMED_VARIABLES(JDK22, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL),
PRIMITIVE_PATTERNS(JDK23, Fragments.FeaturePrimitivePatterns, DiagKind.PLURAL),
SUPER_INIT(JDK22, Fragments.FeatureSuperInit, DiagKind.NORMAL),
FLEXIBLE_CONSTRUCTORS(JDK22, Fragments.FeatureFlexibleConstructors, DiagKind.NORMAL),
MODULE_IMPORTS(JDK23, Fragments.FeatureModuleImports, DiagKind.PLURAL),
;

View File

@ -293,7 +293,9 @@ public class Attr extends JCTree.Visitor {
void checkAssignable(DiagnosticPosition pos, VarSymbol v, JCTree base, Env<AttrContext> env) {
if (v.name == names._this) {
log.error(pos, Errors.CantAssignValToThis);
} else if ((v.flags() & FINAL) != 0 &&
return;
}
if ((v.flags() & FINAL) != 0 &&
((v.flags() & HASINIT) != 0
||
!((base == null ||
@ -304,6 +306,23 @@ public class Attr extends JCTree.Visitor {
} else {
log.error(pos, Errors.CantAssignValToVar(Flags.toSource(v.flags() & (STATIC | FINAL)), v));
}
return;
}
// Check instance field assignments that appear in constructor prologues
if (rs.isEarlyReference(env, base, v)) {
// Field may not be inherited from a superclass
if (v.owner != env.enclClass.sym) {
log.error(pos, Errors.CantRefBeforeCtorCalled(v));
return;
}
// Field may not have an initializer
if ((v.flags() & HASINIT) != 0) {
log.error(pos, Errors.CantAssignInitializedBeforeCtorCalled(v));
return;
}
}
}
@ -2779,6 +2798,7 @@ public class Attr extends JCTree.Visitor {
}
}
} else if (!clazztype.tsym.isInterface() &&
(clazztype.tsym.flags_field & NOOUTERTHIS) == 0 &&
clazztype.getEnclosingType().hasTag(CLASS)) {
// Check for the existence of an apropos outer instance
rs.resolveImplicitThis(tree.pos(), env, clazztype);

View File

@ -4060,9 +4060,9 @@ public class Check {
break;
}
// If super()/this() isn't first, require "statements before super()" feature
// If super()/this() isn't first, require flexible constructors feature
if (!firstStatement)
preview.checkSourceLevel(apply.pos(), Feature.SUPER_INIT);
preview.checkSourceLevel(apply.pos(), Feature.FLEXIBLE_CONSTRUCTORS);
// We found a legitimate super()/this() call; remember it
initCall = methodName;

View File

@ -1508,7 +1508,7 @@ public class Resolve {
(sym.flags() & STATIC) == 0) {
if (staticOnly)
return new StaticError(sym);
if (env1.info.ctorPrologue && (sym.flags_field & SYNTHETIC) == 0)
if (env1.info.ctorPrologue && !isAllowedEarlyReference(env1, (VarSymbol)sym))
return new RefBeforeCtorCalledError(sym);
}
return sym;
@ -3766,6 +3766,7 @@ public class Resolve {
Env<AttrContext> env,
TypeSymbol c,
Name name) {
Assert.check(name == names._this || name == names._super);
Env<AttrContext> env1 = env;
boolean staticOnly = false;
while (env1.outer != null) {
@ -3775,7 +3776,7 @@ public class Resolve {
if (sym != null) {
if (staticOnly)
sym = new StaticError(sym);
else if (env1.info.ctorPrologue)
else if (env1.info.ctorPrologue && !isAllowedEarlyReference(env1, (VarSymbol)sym))
sym = new RefBeforeCtorCalledError(sym);
return accessBase(sym, pos, env.enclClass.sym.type,
name, true);
@ -3828,6 +3829,70 @@ public class Resolve {
return result.toList();
}
/**
* Determine if an early instance field reference may appear in a constructor prologue.
*
* <p>
* This is only allowed when:
* - The field is being assigned a value (i.e., written but not read)
* - The field is not inherited from a superclass
* - The assignment is not within a lambda, because that would require
* capturing 'this' which is not allowed prior to super().
*
* <p>
* Note, this method doesn't catch all such scenarios, because this method
* is invoked for symbol "x" only for "x = 42" but not for "this.x = 42".
* We also don't verify that the field has no initializer, which is required.
* To catch those cases, we rely on similar logic in Attr.checkAssignable().
*/
private boolean isAllowedEarlyReference(Env<AttrContext> env, VarSymbol v) {
// Check assumptions
Assert.check(env.info.ctorPrologue);
Assert.check((v.flags_field & STATIC) == 0);
// The symbol must appear in the LHS of an assignment statement
if (!(env.tree instanceof JCAssign assign))
return false;
// The assignment statement must not be within a lambda
if (env.info.isLambda)
return false;
// Get the symbol's qualifier, if any
JCExpression lhs = TreeInfo.skipParens(assign.lhs);
JCExpression base = lhs instanceof JCFieldAccess select ? select.selected : null;
// If an early reference, the field must not be declared in a superclass
if (isEarlyReference(env, base, v) && v.owner != env.enclClass.sym)
return false;
// OK
return true;
}
/**
* Determine if the variable appearance constitutes an early reference to the current class.
*
* <p>
* This means the variable is an instance field of the current class and it appears
* in an early initialization context of it (i.e., one of its constructor prologues).
*
* <p>
* Such a reference is only allowed for assignments to non-initialized fields that are
* not inherited from a superclass, though that is not enforced by this method.
*
* @param env The current environment
* @param base Variable qualifier, if any, otherwise null
* @param v The variable
*/
public boolean isEarlyReference(Env<AttrContext> env, JCTree base, VarSymbol v) {
return env.info.ctorPrologue &&
(v.flags() & STATIC) == 0 &&
v.owner.kind == TYP &&
types.isSubtype(env.enclClass.type, v.owner.type) &&
(base == null || TreeInfo.isExplicitThisReference(types, (ClassType)env.enclClass.type, base));
}
/**
* Resolve `c.this' for an enclosing class c that contains the

View File

@ -399,6 +399,10 @@ compiler.err.cant.inherit.from.final=\
compiler.err.cant.ref.before.ctor.called=\
cannot reference {0} before supertype constructor has been called
# 0: symbol or name
compiler.err.cant.assign.initialized.before.ctor.called=\
cannot assign initialized field ''{0}'' before supertype constructor has been called
compiler.err.cant.select.static.class.from.param.type=\
cannot select a static class from a parameterized type
@ -3215,8 +3219,8 @@ compiler.misc.feature.unconditional.patterns.in.instanceof=\
compiler.misc.feature.implicit.classes=\
implicitly declared classes
compiler.misc.feature.super.init=\
statements before super()
compiler.misc.feature.flexible.constructors=\
flexible constructors
compiler.misc.feature.module.imports=\
module imports

View File

@ -0,0 +1,51 @@
/*
* @test /nodynamiccopyright/
* @bug 8325805
* @summary Permit non-superclass instance field assignments before this/super in constructors
* @compile/fail/ref=DA_DUConstructors.out -XDrawDiagnostics DA_DUConstructors.java
* @enablePreview
*/
public class DA_DUConstructors {
// identity
class C1 {
final int x;
final int y = x + 1;
C1() {
x = 12;
super();
}
}
class C2 {
final int x;
C2() {
this(x = 3); // error
}
C2(int i) {
x = 4;
}
}
class C3 {
C3(int i) {}
}
class C4 extends C3 {
final int x;
C4() {
super(x = 3); // ok
}
}
class C5 {
final int x;
final int y = x + 1; // x is not DA
C5() {
x = 12; super();
}
C5(int i) {
/* no prologue */
x = i;
}
}
}

View File

@ -0,0 +1,5 @@
DA_DUConstructors.java:23:17: compiler.err.var.might.already.be.assigned: x
DA_DUConstructors.java:42:23: compiler.err.var.might.not.have.been.initialized: x
- compiler.note.preview.filename: DA_DUConstructors.java, DEFAULT
- compiler.note.preview.recompile
2 errors

View File

@ -0,0 +1,161 @@
/*
* @test /nodynamiccopyright/
* @bug 8325805
* @summary Permit non-superclass instance field assignments before this/super in constructors
* @compile/fail/ref=EarlyAssignments.out -XDrawDiagnostics EarlyAssignments.java
* @enablePreview
*/
public class EarlyAssignments {
public static class Inner1 {
public int x;
public Inner1() {
x = 123; // OK - "x" belongs to this class
this.x = 123; // OK - "x" belongs to this class
Inner1.this.x = 123; // OK - "x" belongs to this class
super();
}
public Inner1(int y) {
y = x; // FAIL - early 'this' reference
y = this.x; // FAIL - early 'this' reference
y = Inner1.this.x; // FAIL - early 'this' reference
super();
}
public class Inner1a extends Inner1 {
public int z;
public Inner1a(byte value) {
Inner1.this.x = value; // OK - "x" belongs to outer class
z = super.x; // FAIL - "x" belongs to superclass
z = x; // FAIL - "x" belongs to superclass
this.z = x; // FAIL - "x" belongs to superclass
Inner1a.this.z = x; // FAIL - "x" belongs to superclass
Object o1 = Inner1.this; // OK - Inner1 is an outer class
Object o2 = Inner1a.this; // FAIL - Inner1a is this class
super();
}
public Inner1a(short value) {
x = value; // FAIL - "x" belongs to superclass
super();
}
public Inner1a(char value) {
this.x = value; // FAIL - "x" belongs to superclass
super();
}
public Inner1a(int value) {
super.x = value; // FAIL - "x" belongs to superclass
super();
}
}
public class Inner1b {
public Inner1b(int value) {
Inner1.this.x = value; // OK - "x" belongs to outer class
super();
}
}
}
public static class Inner2 extends Inner1 {
int y;
public Inner2(int value) {
y = value; // OK - "y" belongs to this class
this.y = value; // OK - "y" belongs to this class
x = value; // FAIL - "x" belongs to superclass
this.x = value; // FAIL - "x" belongs to superclass
Object o1 = this; // FAIL - can't acces 'this' yet
Object o2 = Inner2.this; // FAIL - can't acces 'this' yet
super();
}
}
public static class Inner3 {
public int e;
public class Inner3a {
public static int x;
public Inner3a(int val) {
x = val; // OK - "x" is a static field
val = x; // OK - "x" is a static field
e = val; // OK - "e" belongs to outer class
val = e; // OK - "e" belongs to outer class
Inner3.this.e = val; // OK - "e" belongs to outer class
super();
}
}
}
public static class Inner4 {
public int x;
public Inner4() {
x = 0; // OK
x = x + 1; // FAIL - illegal early access
super();
}
public Inner4(int a) {
this.x = 0; // OK
this.x = this.x + 1; // FAIL - illegal early access
super();
}
public Inner4(char a) {
Inner4.this.x = 0; // OK
Inner4.this.x = Inner4.this.x + 1; // FAIL - illegal early access
super();
}
}
public static class Inner5 extends Inner4 {
public int y;
public Inner5() {
y = x + 1; // FAIL - illegal early access
super();
}
public Inner5(int a) {
this.y = x + 1; // FAIL - illegal early access
super();
}
public Inner5(char a) {
Inner5.this.y = x + 1; // FAIL - illegal early access
super();
}
public Inner5(short a) {
y = super.x + 1; // FAIL - illegal early access
super();
}
public Inner5(float a) {
y = Inner5.this.x + 1; // FAIL - illegal early access
super();
}
}
public static class Inner6 {
public int x = 1;
public Inner6() {
x = 2; // FAIL - illegal early access
super();
}
}
public static class Inner7 {
public final int x = 1;
public Inner7() {
x = 2; // FAIL - illegal early access
super();
}
}
}

View File

@ -0,0 +1,28 @@
EarlyAssignments.java:21:17: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:22:17: compiler.err.cant.ref.before.ctor.called: this
EarlyAssignments.java:23:23: compiler.err.cant.ref.before.ctor.called: this
EarlyAssignments.java:31:21: compiler.err.cant.ref.before.ctor.called: super
EarlyAssignments.java:32:21: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:33:26: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:34:34: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:36:36: compiler.err.cant.ref.before.ctor.called: this
EarlyAssignments.java:40:17: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:44:21: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:48:22: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:66:13: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:67:17: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:68:25: compiler.err.cant.ref.before.ctor.called: this
EarlyAssignments.java:69:31: compiler.err.cant.ref.before.ctor.called: this
EarlyAssignments.java:98:17: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:104:22: compiler.err.cant.ref.before.ctor.called: this
EarlyAssignments.java:110:35: compiler.err.cant.ref.before.ctor.called: this
EarlyAssignments.java:119:17: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:124:22: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:129:29: compiler.err.cant.ref.before.ctor.called: x
EarlyAssignments.java:134:17: compiler.err.cant.ref.before.ctor.called: super
EarlyAssignments.java:139:23: compiler.err.cant.ref.before.ctor.called: this
EarlyAssignments.java:148:13: compiler.err.cant.assign.initialized.before.ctor.called: x
EarlyAssignments.java:157:13: compiler.err.cant.assign.val.to.var: final, x
- compiler.note.preview.filename: EarlyAssignments.java, DEFAULT
- compiler.note.preview.recompile
25 errors

View File

@ -0,0 +1,19 @@
/*
* @test /nodynamiccopyright/
* @bug 8325805
* @summary Verify local class in early construction context has no outer instance
* @compile/fail/ref=EarlyLocalClass.out -XDrawDiagnostics EarlyLocalClass.java
* @enablePreview
*/
public class EarlyLocalClass {
EarlyLocalClass() {
class Local {
void foo() {
EarlyLocalClass.this.hashCode(); // this should FAIL
}
}
new Local(); // this is OK
super();
new Local(); // this is OK
}
}

View File

@ -0,0 +1,4 @@
EarlyLocalClass.java:12:32: compiler.err.no.encl.instance.of.type.in.scope: EarlyLocalClass
- compiler.note.preview.filename: EarlyLocalClass.java, DEFAULT
- compiler.note.preview.recompile
1 error

View File

@ -91,7 +91,7 @@ public class SuperInitFails extends AtomicReference<Object> implements Iterable<
}
public SuperInitFails(short[] x) {
this.x = x.length; // this should FAIL
this.x++; // this should FAIL
super();
}
@ -145,16 +145,6 @@ public class SuperInitFails extends AtomicReference<Object> implements Iterable<
return null;
}
public SuperInitFails(short[][] x) {
class Foo {
Foo() {
SuperInitFails.this.hashCode();
}
};
new Foo(); // this should FAIL
super();
}
public SuperInitFails(float[][] x) {
Runnable r = () -> {
super(); // this should FAIL
@ -168,4 +158,32 @@ public class SuperInitFails extends AtomicReference<Object> implements Iterable<
public SuperInitFails(long[][] z) {
super(new Inner1()); // this should FAIL
}
public static class Inner2 {
int x;
}
public static class Inner3 extends Inner2 {
int y;
Inner3(byte z) {
x = z; // this should FAIL
super();
}
Inner3(short z) {
this.x = z; // this should FAIL
super();
}
Inner3(char z) {
Inner3.this.x = z; // this should FAIL
super();
}
Inner3(int z) {
super.x = z; // this should FAIL
super();
}
}
public SuperInitFails(double[][] x) {
Runnable r = () -> this.x = 7; // this should FAIL
super();
}
}

View File

@ -12,9 +12,13 @@ SuperInitFails.java:119:22: compiler.err.call.must.only.appear.in.ctor
SuperInitFails.java:125:9: compiler.err.cant.ref.before.ctor.called: this
SuperInitFails.java:133:9: compiler.err.non.canonical.constructor.invoke.another.constructor: SuperInitFails.Record1
SuperInitFails.java:138:9: compiler.err.non.canonical.constructor.invoke.another.constructor: SuperInitFails.Record2
SuperInitFails.java:154:9: compiler.err.cant.ref.before.ctor.called: this
SuperInitFails.java:165:31: compiler.err.cant.ref.before.ctor.called: x
SuperInitFails.java:169:15: compiler.err.cant.ref.before.ctor.called: this
SuperInitFails.java:155:31: compiler.err.cant.ref.before.ctor.called: x
SuperInitFails.java:159:15: compiler.err.cant.ref.before.ctor.called: this
SuperInitFails.java:168:13: compiler.err.cant.ref.before.ctor.called: x
SuperInitFails.java:172:17: compiler.err.cant.ref.before.ctor.called: x
SuperInitFails.java:176:24: compiler.err.cant.ref.before.ctor.called: x
SuperInitFails.java:180:18: compiler.err.cant.ref.before.ctor.called: x
SuperInitFails.java:186:28: compiler.err.cant.ref.before.ctor.called: this
SuperInitFails.java:33:13: compiler.err.call.must.only.appear.in.ctor
SuperInitFails.java:37:14: compiler.err.call.must.only.appear.in.ctor
SuperInitFails.java:41:14: compiler.err.call.must.only.appear.in.ctor
@ -23,7 +27,7 @@ SuperInitFails.java:49:33: compiler.err.call.must.only.appear.in.ctor
SuperInitFails.java:53:32: compiler.err.call.must.only.appear.in.ctor
SuperInitFails.java:83:18: compiler.err.ctor.calls.not.allowed.here
SuperInitFails.java:89:13: compiler.err.return.before.superclass.initialized
SuperInitFails.java:160:18: compiler.err.ctor.calls.not.allowed.here
SuperInitFails.java:150:18: compiler.err.ctor.calls.not.allowed.here
- compiler.note.preview.filename: SuperInitFails.java, DEFAULT
- compiler.note.preview.recompile
26 errors
30 errors

View File

@ -407,6 +407,54 @@ public class SuperInitGood {
}
}
// we allow 'this' reference prior to super() for field assignments only
public static class Test20 {
private int x;
public Test20(short x) {
x = x;
super();
}
public Test20(int x) {
this.x = x;
super();
}
public Test20(char x) {
Test20.this.x = x;
super();
}
public Test20(byte y) {
x = y;
this((int)y);
this.x++;
}
}
// allow creating and using local and anonymous classes before super()
// they will not have enclosing instances though
public static class Test21 {
public Test21(int x) {
Runnable r = new Runnable() {
public void run() {
this.hashCode();
}
};
r.run();
super();
r.run();
}
public Test21(float x) {
class Foo {
public void bar() {
this.hashCode();
}
};
new Foo().bar();
super();
new Foo().bar();
}
}
public static void main(String[] args) {
new Test0();
new Test1();
@ -448,5 +496,8 @@ public class SuperInitGood {
assert false : "unexpected exception: " + e;
}
new Test19(123);
new Test20(123);
new Test21((int)123);
new Test21((float)123);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// key: compiler.err.cant.assign.initialized.before.ctor.called
// options: --enable-preview -source ${jdk.version}
class CantAssignInitializedBeforeCtorCalled {
int x = 1;
CantAssignInitializedBeforeCtorCalled() {
x = 2;
super();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,12 +21,12 @@
* questions.
*/
// key: compiler.misc.feature.super.init
// key: compiler.misc.feature.flexible.constructors
// key: compiler.warn.preview.feature.use
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class FeatureStatementsBeforeSuper {
FeatureStatementsBeforeSuper() {
class FeatureFlexibleConstructors {
FeatureFlexibleConstructors() {
System.out.println();
super();
}