Skip to content

8359370: [lworld] allow instance fields of identity classes to be readable in the prologue phase #1523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: lworld
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
196 changes: 196 additions & 0 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 @@ -124,6 +124,7 @@ public class Attr extends JCTree.Visitor {
final ArgumentAttr argumentAttr;
final MatchBindingsComputer matchBindingsComputer;
final AttrRecover attrRecover;
final LocalProxyVarsGen localProxyVarsGen;

public static Attr instance(Context context) {
Attr instance = context.get(attrKey);
Expand Down Expand Up @@ -163,6 +164,7 @@ protected Attr(Context context) {
argumentAttr = ArgumentAttr.instance(context);
matchBindingsComputer = MatchBindingsComputer.instance(context);
attrRecover = AttrRecover.instance(context);
localProxyVarsGen = LocalProxyVarsGen.instance(context);

Options options = Options.instance(context);

Expand Down Expand Up @@ -1250,6 +1252,24 @@ public void visitMethodDef(JCMethodDecl tree) {

// Attribute method body.
attribStat(tree.body, localEnv);
if (isConstructor) {
ListBuffer<JCTree> prologueCode = new ListBuffer<>();
for (JCTree stat : tree.body.stats) {
prologueCode.add(stat);
/* gather all the stats in the body until a `super` or `this` constructor invocation is found,
* the constructor invocation will also be included
*/
if (stat instanceof JCExpressionStatement expStmt &&
expStmt.expr instanceof JCMethodInvocation mi &&
TreeInfo.isConstructorCall(mi)) {
break;
}
}
if (!prologueCode.isEmpty()) {
CtorPrologueVisitor ctorPrologueVisitor = new CtorPrologueVisitor(localEnv);
ctorPrologueVisitor.scan(prologueCode.toList());
}
}
}

localEnv.info.scope.leave();
Expand All @@ -1261,6 +1281,178 @@ public void visitMethodDef(JCMethodDecl tree) {
}
}

class CtorPrologueVisitor extends TreeScanner {
Env<AttrContext> localEnv;
CtorPrologueVisitor(Env<AttrContext> localEnv) {
this.localEnv = localEnv;
}

@Override
public void visitLambda(JCLambda lambda) {
super.visitLambda(lambda);
// lambda in prologue
classDeclAndLambdaHelper(TreeInfo.symbolsFor(lambda.body));
}

@Override
public void visitClassDef(JCClassDecl classDecl) {
super.visitClassDef(classDecl);
// local class in prologue
classDeclAndLambdaHelper(TreeInfo.symbolsFor(classDecl.defs));
}

private void classDeclAndLambdaHelper(java.util.List<TreeInfo.SymAndTree> symbols) {
for (TreeInfo.SymAndTree symAndTree : symbols) {
Symbol sym = symAndTree.symbol();
JCTree tree = filter(symAndTree.tree());
if (sym.kind == VAR &&
rs.isEarlyReference(
localEnv,
tree instanceof JCFieldAccess fa ?
fa.selected :
null,
(VarSymbol) sym)) {
log.error(tree, Errors.CantRefBeforeCtorCalled(sym));
}
}
}

@Override
public void visitApply(JCMethodInvocation tree) {
super.visitApply(tree);
Name name = TreeInfo.name(tree.meth);
boolean isConstructorCall = name == names._this || name == names._super;
Symbol msym = TreeInfo.symbolFor(tree.meth);
if (!isConstructorCall && !msym.isStatic()) {
if (msym.owner == env.enclClass.sym ||
msym.isMemberOf(env.enclClass.sym, types)) {
if (tree.meth instanceof JCFieldAccess fa) {
if (TreeInfo.isExplicitThisReference(types, (ClassType)env.enclClass.sym.type, fa.selected)) {
log.error(tree.meth, Errors.CantRefBeforeCtorCalled(msym));
}
} else {
log.error(tree.meth, Errors.CantRefBeforeCtorCalled(msym));
}
}
}
if (isConstructorCall) {
if (tree.meth instanceof JCFieldAccess fa) {
if (TreeInfo.isExplicitThisReference(types, (ClassType)env.enclClass.sym.type, fa.selected)) {
log.error(tree.meth, Errors.CantRefBeforeCtorCalled(msym));
}
}
}
// we still need to check the args
for (JCExpression arg : tree.args) {
analyzeTree(arg);
}
}

@Override
public void visitNewClass(JCNewClass tree) {
super.visitNewClass(tree);
checkNewClassAndMethRefs(tree, tree.type);
}

@Override
public void visitReference(JCMemberReference tree) {
super.visitReference(tree);
if (tree.getMode() == JCMemberReference.ReferenceMode.NEW) {
checkNewClassAndMethRefs(tree, tree.expr.type);
}
}

void checkNewClassAndMethRefs(JCTree tree, Type t) {
if (t.tsym.isEnclosedBy(env.enclClass.sym) &&
!t.tsym.isStatic() &&
!t.tsym.isDirectlyOrIndirectlyLocal()) {
log.error(tree, Errors.CantRefBeforeCtorCalled(t.getEnclosingType().tsym));
}
}

@Override
public void visitAssign(JCAssign tree) {
super.visitAssign(tree);
if (tree.rhs.type.constValue() == null) {
analyzeTree(tree.rhs);
}
}

@Override
public void visitVarDef(JCVariableDecl tree) {
super.visitVarDef(tree);
if (tree.init != null && tree.init.type.constValue() == null) {
analyzeTree(tree.init);
}
}

JCTree filter(JCTree tree) {
tree = TreeInfo.skipParens(tree);
if (tree.hasTag(TYPECAST)) {
tree = filter(((JCTypeCast)tree).expr);
}
return tree;
}

void analyzeTree(JCTree jcTree) {
jcTree = filter(jcTree);
java.util.List<TreeInfo.SymAndTree> symbols = TreeInfo.symbolsFor(jcTree);
for (TreeInfo.SymAndTree symAndTree : symbols) {
Symbol sym = symAndTree.symbol();
JCTree tree = filter(symAndTree.tree());
if (!sym.isStatic() && !isMethodParam(tree)) {
if (sym.name == names._this || sym.name == names._super) {
if (TreeInfo.isExplicitThisReference(types, (ClassType)localEnv.enclClass.sym.type, tree)) {
log.error(tree, Errors.CantRefBeforeCtorCalled(sym));
}
} else {
if (sym != null &&
!sym.owner.isAnonymous() &&
sym.owner != localEnv.enclClass.sym &&
sym.kind == VAR &&
sym.owner.kind == TYP) {
if (!tree.hasTag(IDENT) && !tree.hasTag(SELECT)) {
throw new AssertionError("unexpected tree " + tree + " with tag " + tree.getTag());
}
Symbol owner = tree.hasTag(IDENT) ?
sym.owner :
((JCFieldAccess)tree).selected.type.tsym;
if (localEnv.enclClass.sym.isSubClass(owner, types) &&
sym.isInheritedIn(localEnv.enclClass.sym, types)) {
log.error(tree, Errors.CantRefBeforeCtorCalled(sym));
}
} else {
if ((sym.isFinal() || sym.isStrict()) &&
sym.kind == VAR &&
sym.owner.kind == TYP)
localProxyVarsGen.addStrictFieldReadInPrologue(localEnv.enclMethod, sym);
}
}
}
}
}

boolean isMethodParam(JCTree tree) {
JCTree treeToCheck = null;
if (tree.hasTag(IDENT)) {
treeToCheck = tree;
} else if (tree instanceof JCFieldAccess) {
JCFieldAccess fa = (JCFieldAccess) tree;
while (fa.selected.hasTag(SELECT)) {
fa = (JCFieldAccess)fa.selected;
}
treeToCheck = fa;
}
if (treeToCheck != null) {
Symbol sym = TreeInfo.symbolFor(treeToCheck instanceof JCFieldAccess fa ? fa.selected : treeToCheck);
if (sym != null){
return sym.owner.kind == MTH;
}
}
return false;
}
}

public void visitVarDef(JCVariableDecl tree) {
// Local variables have not been entered yet, so we need to do it now:
if (env.info.scope.owner.kind == MTH || env.info.scope.owner.kind == VAR) {
Expand Down Expand Up @@ -1333,6 +1525,10 @@ public void visitVarDef(JCVariableDecl tree) {
//fixup local variable type
v.type = chk.checkLocalVarType(tree, tree.init.type, tree.name);
}
if (v.owner.kind == TYP && !v.isStatic() && v.isStrict()) {
CtorPrologueVisitor ctorPrologueVisitor = new CtorPrologueVisitor(initEnv);
ctorPrologueVisitor.scan(tree.init);
}
} finally {
initEnv.info.ctorPrologue = previousCtorPrologue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1537,16 +1537,6 @@ Symbol findVar(DiagnosticPosition pos, Env<AttrContext> env, Name name) {
(sym.flags() & STATIC) == 0) {
if (staticOnly)
return new StaticError(sym);
if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbol)sym)) {
if (!env.tree.hasTag(ASSIGN) || !TreeInfo.isIdentOrThisDotIdent(((JCAssign)env.tree).lhs)) {
if (!sym.isStrictInstance()) {
return new RefBeforeCtorCalledError(sym);
} else {
localProxyVarsGen.addStrictFieldReadInPrologue(env.enclMethod, sym);
return sym;
}
}
}
}
return sym;
} else {
Expand Down Expand Up @@ -2054,8 +2044,6 @@ Symbol findFun(Env<AttrContext> env, Name name,
(sym.flags() & STATIC) == 0) {
if (staticOnly)
return new StaticError(sym);
if (env1.info.ctorPrologue && env1 == env)
return new RefBeforeCtorCalledError(sym);
}
return sym;
} else {
Expand Down Expand Up @@ -3826,9 +3814,6 @@ Symbol findSelfContaining(DiagnosticPosition pos,
if (staticOnly) {
// current class is not an inner class, stop search
return new StaticError(sym);
} else if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbol)sym)) {
// early construction context, stop search
return new RefBeforeCtorCalledError(sym);
} else {
// found it
return sym;
Expand Down Expand Up @@ -3887,8 +3872,6 @@ Symbol resolveSelf(DiagnosticPosition pos,
if (sym != null) {
if (staticOnly)
sym = new StaticError(sym);
else if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbol)sym))
sym = new RefBeforeCtorCalledError(sym);
return accessBase(sym, pos, env.enclClass.sym.type,
name, true);
}
Expand All @@ -3902,8 +3885,6 @@ else if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbo
//this might be a default super call if one of the superinterfaces is 'c'
for (Type t : pruneInterfaces(env.enclClass.type)) {
if (t.tsym == c) {
if (env.info.ctorPrologue)
log.error(pos, Errors.CantRefBeforeCtorCalled(name));
env.info.defaultSuperCallSite = t;
return new VarSymbol(0, names._super,
types.asSuper(env.enclClass.type, c), env.enclClass.sym);
Expand Down Expand Up @@ -3940,64 +3921,6 @@ private List<Type> pruneInterfaces(Type t) {
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(DiagnosticPosition pos, 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;
switch (lhs.getTag()) {
case IDENT:
base = null;
break;
case SELECT:
JCFieldAccess select = (JCFieldAccess)lhs;
base = select.selected;
if (!TreeInfo.isExplicitThisReference(types, (ClassType)env.enclClass.type, base))
return false;
break;
default:
return false;
}

// 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;

// The flexible constructors feature must be enabled
preview.checkSourceLevel(pos, Feature.FLEXIBLE_CONSTRUCTORS);

// OK
return true;
}

/**
* Determine if the variable appearance constitutes an early reference to the current class.
*
Expand Down
Loading