Skip to content
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9fe8e82
8194743: Permit additional statements before this/super in constructors
archiecobbs Feb 1, 2023
51a27c4
Merge branch 'master' into SuperInit
archiecobbs Feb 9, 2023
4aae83b
Merge branch 'master' into SuperInit
archiecobbs Mar 20, 2023
61c0eac
Use for() loop instead of stream for efficiency.
archiecobbs Mar 20, 2023
805b781
Merge branch 'master' into SuperInit
archiecobbs Apr 17, 2023
50d17ca
Merge branch 'master' into SuperInit
archiecobbs Apr 18, 2023
95140ee
Merge branch 'master' into SuperInit
archiecobbs Apr 19, 2023
53d19a0
Merge branch 'master' into SuperInit
archiecobbs Apr 20, 2023
dcf6fa6
Merge branch 'master' into SuperInit
archiecobbs Apr 24, 2023
9deaa03
Merge branch 'master' into SuperInit
archiecobbs Apr 25, 2023
ceb41f8
Fix Javadoc comment.
archiecobbs Apr 25, 2023
a1f1efc
Merge branch 'master' into SuperInit
archiecobbs Apr 25, 2023
7c874eb
Fix typo in comment.
archiecobbs Apr 25, 2023
0eac8b6
Use for() loop instead of stream for efficiency.
archiecobbs Apr 25, 2023
0b8a8c7
Fix typo in comment.
archiecobbs Apr 26, 2023
0e638da
Small refactoring to avoid redundant test.
archiecobbs Apr 26, 2023
3c9322b
Make "statements before super()" support a preview feature.
archiecobbs May 16, 2023
73bb327
Use @enablePreview in tests in preference to explicit command line fl…
archiecobbs May 17, 2023
80ba6be
Merge branch 'master' into SuperInit
archiecobbs May 23, 2023
eb1eb5a
Add unit tests with local class decl's prior to super().
archiecobbs May 23, 2023
9a2cb45
Update unit test after merged-in commit eaa80ad08.
archiecobbs May 23, 2023
543a596
Rename unit test to be consistent with other feature exampless.
archiecobbs May 23, 2023
a352a58
Merge branch 'master' into SuperInit
archiecobbs May 26, 2023
276e449
Fix mistake in previous merge commit 80ba6be4.
archiecobbs May 26, 2023
a5f8cc5
Merge branch 'master' into SuperInit
archiecobbs Jun 13, 2023
c6cf80f
Use TreeInfo.isConstructor() for detecting constructors.
archiecobbs Jul 7, 2023
b0b13b2
Add unit test verifying super() can't appear inside a lambda.
archiecobbs Jul 7, 2023
599d761
Create and cache a single instance of the oft-used SuperThisChecker.
archiecobbs Jul 7, 2023
e2f8813
Merge branch 'master' into SuperInit
archiecobbs Jul 8, 2023
5cced39
Eliminate "isSelfCall" flag, which is now made obsolete by "ctorProlo…
archiecobbs Sep 7, 2023
4ff4c5c
Merge branch 'master' into SuperInit
archiecobbs Sep 7, 2023
a5510b7
Change the error message to match the applicable spec rule.
archiecobbs Sep 16, 2023
599cf0e
Remove obsolete flag "constructorArgs".
archiecobbs Sep 22, 2023
150d794
Change variable name "statik" -> "isStatic" per review recommendation.
archiecobbs Sep 25, 2023
3e75e19
Have RefBeforeCtorCalledError extend StaticError and customize debug …
archiecobbs Sep 25, 2023
355edfb
Address review comments relating to error messages.
archiecobbs Sep 25, 2023
2356ac6
After further review, revert 599d761 to avoid mutable state lying aro…
archiecobbs Sep 25, 2023
738c10a
Reword new errors to use the JLS term for "explicit constructor invoc…
archiecobbs Sep 28, 2023
99a277e
Merge branch 'master' into SuperInit
archiecobbs Oct 20, 2023
bbdb814
Merge branch 'master' into SuperInit
archiecobbs Nov 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 26 additions & 41 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,8 @@ public void visitClassDef(JCClassDecl tree) {
Optional<ArgumentAttr.LocalCacheContext> localCacheContext =
Optional.ofNullable(env.info.attributionMode.isSpeculative ?
argumentAttr.withLocalCacheContext() : null);
boolean ctorProloguePrev = env.info.ctorPrologue;
env.info.ctorPrologue = false;
try {
// Local and anonymous classes have not been entered yet, so we need to
// do it now.
Expand Down Expand Up @@ -973,6 +975,7 @@ public void visitClassDef(JCClassDecl tree) {
}
} finally {
localCacheContext.ifPresent(LocalCacheContext::leave);
env.info.ctorPrologue = ctorProloguePrev;
}
}

Expand All @@ -982,6 +985,8 @@ public void visitMethodDef(JCMethodDecl tree) {

Lint lint = env.info.lint.augment(m);
Lint prevLint = chk.setLint(lint);
boolean ctorProloguePrev = env.info.ctorPrologue;
env.info.ctorPrologue = false;
MethodSymbol prevMethod = chk.setMethod(m);
try {
deferredLintHandler.flush(tree.pos());
Expand Down Expand Up @@ -1045,6 +1050,9 @@ public void visitMethodDef(JCMethodDecl tree) {
chk.validate(tree.recvparam, newEnv);
}

// Is this method a constructor?
boolean isConstructor = tree.name == names.init;

if (env.enclClass.sym.isRecord() && tree.sym.owner.kind == TYP) {
// lets find if this method is an accessor
Optional<? extends RecordComponent> recordComponent = env.enclClass.sym.getRecordComponents().stream()
Expand Down Expand Up @@ -1072,14 +1080,11 @@ public void visitMethodDef(JCMethodDecl tree) {
}
}

if (tree.name == names.init) {
if (isConstructor) {
// if this a constructor other than the canonical one
if ((tree.sym.flags_field & RECORD) == 0) {
JCMethodInvocation app = TreeInfo.firstConstructorCall(tree);
if (app == null ||
TreeInfo.name(app.meth) != names._this ||
!checkFirstConstructorStat(app, tree, false)) {
log.error(tree, Errors.FirstStatementMustBeCallToAnotherConstructor(env.enclClass.sym));
if (!TreeInfo.hasConstructorCall(tree, names._this)) {
log.error(tree, Errors.NonCanonicalConstructorInvokeAnotherConstructor(env.enclClass.sym));
}
} else {
// but if it is the canonical:
Expand All @@ -1105,11 +1110,7 @@ public void visitMethodDef(JCMethodDecl tree) {
);
}

JCMethodInvocation app = TreeInfo.firstConstructorCall(tree);
if (app != null &&
(TreeInfo.name(app.meth) == names._this ||
TreeInfo.name(app.meth) == names._super) &&
checkFirstConstructorStat(app, tree, false)) {
if (TreeInfo.hasAnyConstructorCall(tree)) {
log.error(tree, Errors.InvalidCanonicalConstructorInRecord(
Fragments.Canonical, env.enclClass.sym.name,
Fragments.CanonicalMustNotContainExplicitConstructorInvocation));
Expand Down Expand Up @@ -1187,16 +1188,14 @@ public void visitMethodDef(JCMethodDecl tree) {
// Add an implicit super() call unless an explicit call to
// super(...) or this(...) is given
// or we are compiling class java.lang.Object.
if (tree.name == names.init && owner.type != syms.objectType) {
JCBlock body = tree.body;
if (body.stats.isEmpty() ||
TreeInfo.getConstructorInvocationName(body.stats, names) == names.empty) {
JCStatement supCall = make.at(body.pos).Exec(make.Apply(List.nil(),
if (isConstructor && owner.type != syms.objectType) {
if (!TreeInfo.hasAnyConstructorCall(tree)) {
JCStatement supCall = make.at(tree.body.pos).Exec(make.Apply(List.nil(),
make.Ident(names._super), make.Idents(List.nil())));
body.stats = body.stats.prepend(supCall);
tree.body.stats = tree.body.stats.prepend(supCall);
} else if ((env.enclClass.sym.flags() & ENUM) != 0 &&
(tree.mods.flags & GENERATEDCONSTR) == 0 &&
TreeInfo.isSuperCall(body.stats.head)) {
TreeInfo.hasConstructorCall(tree, names._super)) {
// enum constructors are not allowed to call super
// directly, so make sure there aren't any super calls
// in enum constructors, except in the compiler
Expand Down Expand Up @@ -1226,6 +1225,9 @@ public void visitMethodDef(JCMethodDecl tree) {
annotate.queueScanTreeAndTypeAnnotate(tree.body, localEnv, m, null);
annotate.flush();

// Start of constructor prologue
localEnv.info.ctorPrologue = isConstructor;

// Attribute method body.
attribStat(tree.body, localEnv);
}
Expand All @@ -1235,6 +1237,7 @@ public void visitMethodDef(JCMethodDecl tree) {
} finally {
chk.setLint(prevLint);
chk.setMethod(prevMethod);
env.info.ctorPrologue = ctorProloguePrev;
}
}

Expand Down Expand Up @@ -2492,9 +2495,8 @@ public void visitApply(JCMethodInvocation tree) {

ListBuffer<Type> argtypesBuf = new ListBuffer<>();
if (isConstructorCall) {
// We are seeing a ...this(...) or ...super(...) call.
// Check that this is the first statement in a constructor.
checkFirstConstructorStat(tree, env.enclMethod, true);
// We are seeing a this()/super() call. End of constructor prologue.
env.info.ctorPrologue = false;

// Record the fact
// that this is a constructor call (using isSelfCall).
Expand Down Expand Up @@ -2635,26 +2637,6 @@ Type adjustMethodReturnType(Symbol msym, Type qualifierType, Name methodName, Li
}
}

/** Check that given application node appears as first statement
* in a constructor call.
* @param tree The application node
* @param enclMethod The enclosing method of the application.
* @param error Should an error be issued?
*/
boolean checkFirstConstructorStat(JCMethodInvocation tree, JCMethodDecl enclMethod, boolean error) {
if (enclMethod != null && enclMethod.name == names.init) {
JCBlock body = enclMethod.body;
if (body.stats.head.hasTag(EXEC) &&
((JCExpressionStatement) body.stats.head).expr == tree)
return true;
}
if (error) {
log.error(tree.pos(),
Errors.CallMustBeFirstStmtInCtor(TreeInfo.name(tree.meth)));
}
return false;
}

/** Obtain a method type with given argument types.
*/
Type newMethodTemplate(Type restype, List<Type> argtypes, List<Type> typeargtypes) {
Expand Down Expand Up @@ -5606,6 +5588,9 @@ private void attribClassBody(Env<AttrContext> env, ClassSymbol c) {
}
}

// Check for proper placement of super()/init() calls.
chk.checkSuperInitCalls(tree);

// Check for cycles among non-initial constructors.
chk.checkCyclicConstructors(tree);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public class AttrContext {
*/
boolean constructorArgs = false;

/** Are we in the 'prologue' part of a constructor, prior to an explicit this()/super()?
*/
boolean ctorPrologue = false;

/** Are we evaluating the selector of a `super' or type name?
*/
boolean selectSuper = false;
Expand Down Expand Up @@ -138,6 +142,7 @@ AttrContext dup(WriteableScope scope) {
info.staticLevel = staticLevel;
info.isSelfCall = isSelfCall;
info.constructorArgs = constructorArgs;
info.ctorPrologue = ctorPrologue;
info.selectSuper = selectSuper;
info.pendingResolutionPhase = pendingResolutionPhase;
info.lint = lint;
Expand Down
122 changes: 118 additions & 4 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
Original file line number Diff line number Diff line change
Expand Up @@ -3885,10 +3885,13 @@ void checkCyclicConstructors(JCClassDecl tree) {

// enter each constructor this-call into the map
for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
JCMethodInvocation app = TreeInfo.firstConstructorCall(l.head);
if (app == null) continue;
JCMethodDecl meth = (JCMethodDecl) l.head;
if (TreeInfo.name(app.meth) == names._this) {
if (!l.head.hasTag(METHODDEF))
continue;
JCMethodDecl meth = (JCMethodDecl)l.head;
if (meth.name != names.init)
continue;
JCMethodInvocation app = TreeInfo.findConstructorCall(meth);
if (app != null && TreeInfo.name(app.meth) == names._this) {
callMap.put(meth.sym, TreeInfo.symbol(app.meth));
} else {
meth.sym.flags_field |= ACYCLIC;
Expand Down Expand Up @@ -3921,6 +3924,117 @@ private void checkCyclicConstructor(JCClassDecl tree, Symbol ctor,
}
}

/* *************************************************************************
* Verify the proper placement of super()/this() calls.
*
* - super()/this() may only appear in constructors
* - There must be at most one super()/this() call per constructor
* - The super()/this() call, if any, must be a top-level statement in the
* constructor, i.e., not nested inside any other statement or block
* - There must be no return statements prior to the super()/this() call
**************************************************************************/

void checkSuperInitCalls(JCClassDecl tree) {
new SuperThisChecker().check(tree);
}

private class SuperThisChecker extends TreeScanner {

// Match this scan stack: 1=JCMethodDecl, 2=JCExpressionStatement, 3=JCMethodInvocation
private static final int MATCH_SCAN_DEPTH = 3;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic seems a bit fragile in that it relies on the shape of the AST. Also, in principle you can create something that will overflow the int, and will cause the check to spuriously pass for a very very very large number of nested expressions :-)

That said, I understand that, in order to make this code "tighter", you would need to override al visitors that can "nest" code inside other code, which is also not great.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree about this appearing fragile. The thinking was that, well, the JLS rule for this is simply:

ConstructorBody:
{ [BlockStatements] }
{ [BlockStatements] ExplicitConstructorInvocation [BlockStatements] }

So this tries to mirror exactly that in the most direct way. The fragility comes from the possibility of some unexpected change to the AST class model... but at least if that ever did happen, you would probably get a clear warning when a bunch of JDK code stopped compiling :)


private boolean constructor; // is this method a constructor?
private JCReturn earlyReturn; // first return prior to the super()/init(), if any
private Name initCall; // whichever of "super" or "init" we've seen already
private int scanDepth; // current scan recursion depth in method body

public void check(JCClassDecl classDef) {
scan(classDef.defs);
}

@Override
public void visitMethodDef(JCMethodDecl tree) {
Assert.check(!constructor);
Assert.check(earlyReturn == null);
Assert.check(initCall == null);
Assert.check(scanDepth == 1);

// Initialize state for this method
constructor = tree.name == names.init;
try {

// Scan method body
if (tree.body != null)
scan(tree.body.stats);

// Verify no 'return' seen prior to an explicit super()/this() call
if (constructor && earlyReturn != null && initCall != null)
log.error(earlyReturn.pos(), Errors.ReturnBeforeSuperclassInitialized(initCall));
} finally {
constructor = false;
earlyReturn = null;
initCall = null;
}
}

@Override
public void scan(JCTree tree) {
scanDepth++;
try {
super.scan(tree);
} finally {
scanDepth--;
}
}

@Override
public void visitApply(JCMethodInvocation apply) {
do {

// Is this a super() or this() call?
Name methodName = TreeInfo.name(apply.meth);
if (methodName != names._super && methodName != names._this)
break;

// super()/this() calls must only appear in a constructor
if (!constructor) {
log.error(apply.pos(), Errors.CallMustOnlyAppearInCtor(methodName));
break;
}

// super()/this() calls must be a top level statement
if (scanDepth != MATCH_SCAN_DEPTH) {
log.error(apply.pos(), Errors.CallsNotAllowedHere(methodName));
break;
}

// super()/this() calls must not appear more than once
if (initCall != null) {
log.error(apply.pos(), Errors.RedundantSuperclassInit(methodName, initCall));
break;
}

// We found a legitimate super()/this() call; remember it
initCall = methodName;
} while (false);

// Proceed
super.visitApply(apply);
}

@Override
public void visitReturn(JCReturn tree) {
if (constructor && initCall == null && earlyReturn == null)
earlyReturn = tree; // we have seen a return but not (yet) a super()/this()
super.visitReturn(tree);
}

@Override
public void visitClassDef(JCClassDecl tree) {
// don't descend any further
}
}

/* *************************************************************************
* Miscellaneous
**************************************************************************/
Expand Down
Loading