JDK-8293890 : javac creates an Element whose getEnclosingElement() does not enclose it
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 18,19
  • Priority: P4
  • Status: Closed
  • Resolution: Incomplete
  • OS: generic
  • CPU: generic
  • Submitted: 2022-09-16
  • Updated: 2025-02-26
  • Resolved: 2025-02-26
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdResolved
Related Reports
Relates :  
Description
javac 18 and 19 can produce an element for which this assertion fails:

  ExecutableElement myMethod = ...;
  TypeElement enclosingType = (TypeElement) myMethod.getEnclosingElement();
  assert enclosingType.getEnclosedElements().contains(myMethod)

javac 8, 11, and 17 do satisfy the assertion.

Here is code to reproduce the problem:

  interface CallInDefaultImplementation {
    public default String className() {
      return getClass().getSimpleName();
    }
  }

The ExecutableElement for `getClass()` in the body of `className()` has an enclosing element of CallInDefaultImplementation, which violates the assertion.
In javac before version 18, the ExecutableElement for `getClass()` has an enclosing element of Object, which satisfies the assertion.

I have only observed the problem within the body of a default method implementation (in an interface), when calling methods that are defined on Object such as getClass() and hashCode().

This violation of an expected invariant harms tools that use APIs in javax.lang.model.element.  I noticed the problem because the Checker Framework does not run properly under JDK 18, though it works fine under JDK 17 and earlier.  Therefore, I consider this a regression.

This problem doesn't seem to harm javac.  I see that javac's bytecode generation strategy has changed between javac 17 and javac 18.  javac 18 and later uses an invokeinterface bytecode to call getClass(), whereas javac 17 and earlier used invokevirtual.  With invokeinterface, it isn't necessary for the ExecutableElement to have a proper enclosing element.  However, I see no harm in it.
Comments
It is not clear there's an actual problem (please see above for details). Please reopen if there's more information. Thanks!
26-02-2025

Well, I am afraid it is not obvious what should be fixed here. Yes, the value of JCIdent/JCFieldAccess.sym has changed, and I see that might affect Checker Framework via e.g.: https://github.com/typetools/checker-framework/blob/61b9ee5c54d2ee70cec88e3b042b36909a9cbee9/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java#L235 but, that is, I believe, not a problem in javac. The TreeInfo class is not an API (and so are not the JCTrees), and any method in that class may start to produce different answers, or cease to exist at any time. I believe that when you decide to use an implementation, non-API class or method, you must be ready to do any adjustments needed. (Calling .baseSymbol() on the result here, for example.) I believe the API counterpart, Trees(.getElement): https://github.com/openjdk/jdk/blob/218104247e2ae26ad8221f4dd78be1170b952be0/src/jdk.compiler/share/classes/com/sun/source/util/Trees.java#L190 has been modified to produce the same results as before, and hence there should not be any behavioral change. It is possible I've missed some API method, and some other API method should be fixed. But, from the report so far, it is not clear which method. Overall - if there's a behavioral change to an API method, I'll by all means look into that, and do my best to alleviate the problem if I can. If the issue is only observable using non-API means, then I don't think I can fix this particular problem, and I would suggest to use an API method (better), or adjust to the code to call .baseSymbol(), and be prepared that there may be other shifts like this in the internal implementation in the future. Thanks.
21-10-2022

Unfortunately, I no longer have the exact example at hand. (At the time, I didn't interpret the "I wonder" comment as a request for a reproduction.) I encountered this in the Checker Framework, which is a javac annotation processor. Tests failed when running it under Java 18, so it was a real behavior change, but I have since worked around the problem.
21-10-2022

@mernst - any input on how to reproduce? Please see the above comment. Thanks!
21-10-2022

I wonder what was the way to get the ExecutableElement. I've tried this: ``` import com.sun.source.tree.MethodInvocationTree; import com.sun.source.util.JavacTask; import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import javax.lang.model.element.Element; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.ToolProvider; public class JDK_8293890 { public static void main(String[] args) throws URISyntaxException, IOException { var compiler = ToolProvider.getSystemJavaCompiler(); var task = (JavacTask) compiler.getTask(null, null, null, null, null, List.of(new SimpleJavaFileObject(new URI("mem://CallInDefaultImplementation.java"), JavaFileObject.Kind.SOURCE) { @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return """ interface CallInDefaultImplementation { public default String className() { return getClass().getSimpleName(); } } """; } })); var trees = Trees.instance(task); var cut = task.parse(); task.analyze(); new TreePathScanner<>() { @Override public Object visitMethodInvocation(MethodInvocationTree node, Object p) { Element el = trees.getElement(getCurrentPath()); if (!el.getEnclosingElement().getEnclosedElements().contains(el)) { System.err.println("a method's enclosing element does not contain the method! tree: " + node); } return super.visitMethodInvocation(node, p); } }.scan(cut, null); } } ``` And it seems to return a reasonable Element. When referring to a method originally from Object as a member of an interface (that does not declare or inherit it), javac will create a duplicate of the MethodSymbol with a new owner: https://github.com/openjdk/jdk/blob/29c70f1ab7df3b386d326509db48acf91dd124ab/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java#L1904 But then, when Trees.getElement(TreePath) is reading the attributed MethodSymbol, it will call baseSymbol(), reverting back to the original method: https://github.com/openjdk/jdk/blob/29c70f1ab7df3b386d326509db48acf91dd124ab/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java#L866 So, I wonder what was the path to get to the ExecutableElement, as we may be missing a baseSymbol() call there. Thanks.
29-09-2022

Side effect of JDK-8272564?
16-09-2022