Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
338f5d8
removing all checks from Resolve
vicente-romero-oracle Jul 22, 2025
68bd63e
additional experiments
vicente-romero-oracle Aug 14, 2025
32c3f4c
Merge branch 'lworld' into JDK-8359370-v2
vicente-romero-oracle Aug 14, 2025
808fbef
additional changes
vicente-romero-oracle Aug 15, 2025
993b657
additional changes
vicente-romero-oracle Aug 15, 2025
fd2cd23
restoring removed class files
vicente-romero-oracle Aug 15, 2025
3b4e77d
minor fixes
vicente-romero-oracle Aug 15, 2025
c175f76
test changes
vicente-romero-oracle Aug 16, 2025
6433fe2
refactoring
vicente-romero-oracle Aug 18, 2025
399bd51
more changes
vicente-romero-oracle Aug 18, 2025
841bfc0
renaming methods in LocalProxyVarGen
vicente-romero-oracle Aug 19, 2025
48f12d2
merge with lworld
vicente-romero-oracle Aug 19, 2025
bfd0cda
bug fix
vicente-romero-oracle Aug 20, 2025
af7939a
another bug fix
vicente-romero-oracle Aug 21, 2025
e55b897
refactorings
vicente-romero-oracle Aug 21, 2025
260ddf3
merge with lworld
vicente-romero-oracle Aug 21, 2025
0eb8168
fixing bugs: new test cases brought in with latest merge are not acce…
vicente-romero-oracle Aug 21, 2025
31c7b8c
minor refactorings and simplifications
vicente-romero-oracle Aug 22, 2025
a5f2947
more simplifications and bug fixes
vicente-romero-oracle Aug 22, 2025
5cb0f42
documentation
vicente-romero-oracle Aug 26, 2025
cdfdb76
minor bug fix
vicente-romero-oracle Aug 26, 2025
74945ee
some simplifications
vicente-romero-oracle Aug 26, 2025
b7eab98
adding documentation
vicente-romero-oracle Aug 27, 2025
3965918
code simplifications
vicente-romero-oracle Aug 27, 2025
ad1fb80
minor diff
vicente-romero-oracle Aug 28, 2025
29fc914
addressing review comments
vicente-romero-oracle Aug 28, 2025
48a4713
removing unnecessary imports
vicente-romero-oracle Aug 28, 2025
c987a08
addressing review comments
vicente-romero-oracle Aug 29, 2025
32ccabf
additional changes, more tests
vicente-romero-oracle Aug 29, 2025
b101d1e
moving isEarlyReference to Attr
vicente-romero-oracle Aug 29, 2025
e87108d
some documentation
vicente-romero-oracle Aug 29, 2025
9014afc
addressing review comments
vicente-romero-oracle Sep 1, 2025
792d98f
minor refactoring
vicente-romero-oracle Sep 1, 2025
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
229 changes: 229 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 @@ -1252,6 +1254,25 @@ 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,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I understand that you wanted to simplify the visitor -- but doing a linear pass on the constructor and creating a new list of statements is also kind of expensive -- maybe when we're done with this change we can see if there's a way to set a flag on the visitor to shortcircuit the analysis after the super call is found.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I like the trade-off, we could try to infer if a constructor invocation corresponds to the class we are interested in. Like for example analyzing the symbol associated to a super or this invocation. But for erroneous invocations the symbol could be null. So what to do when we find a null symbol? We would have no clues I think.

Copy link
Collaborator

Choose a reason for hiding this comment

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

let's wait until we address the other comments first. What I had in mind (but can be addressed in a separate PR) was maybe have a general visitor for prologue -- e.g. an helper visitor class that only visits things inside the prologue. Then you can extend that helper visitor here, to do what you need to do.

* including the constructor invocation, that way we don't need to worry in the visitor below if
* if we are dealing or not with prologue code
*/
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 @@ -1263,6 +1284,209 @@ 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);
classDeclAndLambdaHelper(TreeInfo.symbolsFor(lambda.body));
}

@Override
public void visitClassDef(JCClassDecl classDecl) {
super.visitClassDef(classDecl);
classDeclAndLambdaHelper(TreeInfo.symbolsFor(classDecl.defs));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this look for early references in all the symbols referenced inside the class?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, why do we use symbolsFor instead of recursing this this visitor?

}

private void classDeclAndLambdaHelper(java.util.List<TreeInfo.SymAndTree> symbols) {
for (TreeInfo.SymAndTree symAndTree : symbols) {
Symbol sym = symAndTree.symbol();
JCTree tree = TreeInfo.skipParens(symAndTree.tree());
if (sym.kind == VAR &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why doesn't this call analyze symbol?

rs.isEarlyReference(
true, // localEnv could potentially have the `ctorPrologue` field set to false
localEnv,
tree instanceof JCFieldAccess fa ?
fa.selected :
null,
sym)) {
reportPrologueError(tree, sym);
}
}
}

private void reportPrologueError(JCTree tree, Symbol sym) {
preview.checkSourceLevel(tree, Feature.FLEXIBLE_CONSTRUCTORS);
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);
// is this an instance method call or an illegal constructor invocation like: `this.super()`?
if (msym != null && // for erroneous invocations msym can be null, ignore those
(!isConstructorCall ||
isConstructorCall && tree.meth.hasTag(SELECT))) {
if (rs.isEarlyReference(
true,
localEnv,
tree.meth instanceof JCFieldAccess fa ? fa.selected : null,
msym))
reportPrologueError(tree.meth, msym);
}
}

@Override
public void visitIdent(JCIdent tree) {
// skip if this identifier is part of a select, context is important here
if (!analyzingSelect) {
analyzeSymbol(tree);
}
}

boolean analyzingSelect = false;

@Override
public void visitSelect(JCFieldAccess tree) {
// skip if part of a larger select, context is important here
if (!analyzingSelect) {
boolean previousAnalyzingSelect = analyzingSelect;
try {
analyzingSelect = true;
super.visitSelect(tree);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't we cut recursion here (instead of using analyzingSelect ? That's also what the new TreeInfo.symbolsFor does. In general it seems like these two visitors are trying to do similar things but are not 100% aligned?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if one has a complex select like for example: new SuperInitFails(){}.x it is still necessary to look inside and see if there are some forbidden accesses

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, the issue might then be that, while looking inside SuperInitFails you find a plain early access to a field (e.g. an ident) and we end up skipping it because analyzingSelect is set. At the very least we should unset inside classes (maybe lambdas too).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Example:

class Test {

    int x = 4;

    static String m(Runnable r) { return null; }

    Test() {
        m(() -> System.out.println(x)).toString();
        super();
    }

    public static void main(String[] args) {
        new Test();
    }
}

This seems to compile, but then fails with verifier error.

Copy link
Collaborator

Choose a reason for hiding this comment

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

(e.g. we really need to make sure that analyzeSelect is not applied too broadly)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

true, now that we removed the visitor at TreeInfo that is a problem

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually -- this is enough:

class Test {

    int x = 4;

    static String m(Object r) { return null; }

    Test() {
        m(x).toString();
        super();
    }

    public static void main(String[] args) {
        new Test();
    }
}

No lambda. So probably was an issue even before, with TreeInfo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep :(

analyzeSymbol(tree);
} finally {
analyzingSelect = previousAnalyzingSelect;
}
}
}

@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(localEnv.enclClass.sym) &&
!t.tsym.isStatic() &&
!t.tsym.isDirectlyOrIndirectlyLocal()) {
reportPrologueError(tree, t.getEnclosingType().tsym);
}
}

/* if a symbol is in the LHS of an assignment expression we won't consider it as a candidate
* for a proxy local variable later on
*/
boolean isInLHS = false;

@Override
public void visitAssign(JCAssign tree) {
boolean previousIsInLHS = isInLHS;
try {
isInLHS = true;
scan(tree.lhs);
} finally {
isInLHS = previousIsInLHS;
}
scan(tree.rhs);
}

@Override
public void visitMethodDef(JCMethodDecl tree) {
// ignore any declarative part, mainly to avoid scanning receiver parameters
scan(tree.body);
}

void analyzeSymbol(JCTree tree) {
tree = TreeInfo.skipParens(tree);
Symbol sym = TreeInfo.symbolFor(tree);
if (sym != null) {
if (!sym.isStatic() && !isMethodArgument(tree)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

if you have a sym, in order to understand if something is a method parameter (not argument?) don't you need to check if sym.owner == MTH ?

Copy link
Contributor Author

@vicente-romero-oracle vicente-romero-oracle Aug 28, 2025

Choose a reason for hiding this comment

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

this is for cases when we have an argument that is for example of the same type as the current class so like:

class Test {
    String s;
    
    Test(Test t) {
        // the owner of s is Test not MTH so we need to check what is the qualifier for s which at the end is the argument
        // `t` so we ignore it
        String s1 = t.s;
        super();
    }
}

Copy link
Collaborator

@mcimadamore mcimadamore Aug 29, 2025

Choose a reason for hiding this comment

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

yes, so isn't just checking owner.kind != TYP enough? (e.g. "not a field")

Copy link
Collaborator

Choose a reason for hiding this comment

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

I tried to replace this with:

if (!sym.isStatic() && sym.kind == VAR && sym.owner.kind == TYP) { ... }

And no tests seem to fail.

if (sym.name == names._this || sym.name == names._super) {
// are we seeing something like `this` or `CurrentClass.this` or `SuperClass.super::foo`?
if (TreeInfo.isExplicitThisReference(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we always report an error when seeing Foo.this ? What if we're not inside the prologue of Foo ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

all the code we analyze in this visitor is in the prologue, this is why we pre-select what code we will see

Copy link
Collaborator

Choose a reason for hiding this comment

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

I understand -- but in the prologue of Foo we can have a Bar.this where Bar is some enclosing class?

class Foo {
        class Bar {
            Bar() { Object o = Foo.this; super();}
        }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep I see, there seems to be a bug here, thanks

Copy link
Collaborator

Choose a reason for hiding this comment

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

What has fixed this exactly? It seems like this was already working as expected because TreeInfo.isExplicitThisReference already checks for possible enclosing types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What has fixed this exactly? It seems like this was already working as expected because TreeInfo.isExplicitThisReference already checks for possible enclosing types?

yes that could be the case

types,
(ClassType)localEnv.enclClass.sym.type,
tree)) {
reportPrologueError(tree, sym);
}
} else if (sym.kind == VAR && sym.owner.kind == TYP) { // now fields only
if (sym.owner != localEnv.enclClass.sym) {
if (localEnv.enclClass.sym.isSubClass(sym.owner, types) &&
sym.isInheritedIn(localEnv.enclClass.sym, types)) {
/* if we are dealing with a field that doesn't belong to the current class, but the
* field is inherited, this is an error. Unless, the super class is also an outer
* class and the field's qualifier refers to the outer class
*/
if (tree.hasTag(IDENT) ||
TreeInfo.isExplicitThisReference(
types,
(ClassType)localEnv.enclClass.sym.type,
((JCFieldAccess)tree).selected)) {
reportPrologueError(tree, sym);
}
}
} else if (rs.isEarlyReference( // now this is a `proper` instance field of the current class
true,
localEnv,
tree.hasTag(SELECT) ? ((JCFieldAccess)tree).selected : null,
sym
)) {
/* references to fields of identity classes which happen to have initializers are
* not allowed in the prologue
*/
if (!localEnv.enclClass.sym.isValueClass() && (sym.flags_field & HASINIT) != 0)
reportPrologueError(tree, sym);
// we will need to generate a proxy for this field later on
if (!isInLHS)
localProxyVarsGen.addFieldReadInPrologue(localEnv.enclMethod, sym);
}
}
}
}
}

boolean isMethodArgument(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 @@ -1335,6 +1559,11 @@ 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()) {
// strict field initializers are inlined in constructor's prologues
CtorPrologueVisitor ctorPrologueVisitor = new CtorPrologueVisitor(initEnv);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice reuse

ctorPrologueVisitor.scan(tree.init);
}
} finally {
initEnv.info.ctorPrologue = previousCtorPrologue;
}
Expand Down
Loading