Skip to content

8376045: [lworld] Treat @NullRestricted fields as ACC_STRICT_INIT on load#1951

Closed
liach wants to merge 9 commits intoopenjdk:lworldfrom
liach:feature/cfp-nr-auto-strict
Closed

8376045: [lworld] Treat @NullRestricted fields as ACC_STRICT_INIT on load#1951
liach wants to merge 9 commits intoopenjdk:lworldfrom
liach:feature/cfp-nr-auto-strict

Conversation

@liach
Copy link
Copy Markdown
Member

@liach liach commented Jan 22, 2026

This is the 2nd PR in the strict removal series. 1st PR is #1952, and 3rd PR is #1959.

Automatically inject strict for NR annotations, and remove tests that previously kept them separate. Also add behavior where NR is only accepted for preview class files.


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (1 review required, with at least 1 Committer)

Issue

  • JDK-8376045: [lworld] Treat @NullRestricted fields as ACC_STRICT_INIT on load (Bug - P4)

Reviewers

Contributors

  • Frederic Parain <fparain@openjdk.org>

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/valhalla.git pull/1951/head:pull/1951
$ git checkout pull/1951

Update a local copy of the PR:
$ git checkout pull/1951
$ git pull https://git.openjdk.org/valhalla.git pull/1951/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1951

View PR using the GUI difftool:
$ git pr show -t 1951

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/valhalla/pull/1951.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link
Copy Markdown

bridgekeeper Bot commented Jan 22, 2026

👋 Welcome back liach! A progress list of the required criteria for merging this PR into lworld will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Jan 22, 2026

@liach This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8376045: [lworld] Treat @NullRestricted fields as ACC_STRICT_INIT on load

Co-authored-by: Frederic Parain <fparain@openjdk.org>
Reviewed-by: fparain

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 11 new commits pushed to the lworld branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the lworld branch, type /integrate in a new comment.

@openjdk openjdk Bot added the rfr Pull request is ready for review label Jan 22, 2026
@mlbridge
Copy link
Copy Markdown

mlbridge Bot commented Jan 22, 2026

@liach
Copy link
Copy Markdown
Member Author

liach commented Jan 22, 2026

Testing: tier1 clear on Linux.

@dansmithcode
Copy link
Copy Markdown
Collaborator

I want to confirm that this works on a non-preview class file, and that nothing downstream is going to break. (Hard to prove that second point definitively, but looking for some confirmation from a runtime expert.)

@liach
Copy link
Copy Markdown
Member Author

liach commented Jan 22, 2026

Currently @NullRestricted requires annotated field type to be a value class, which means the source code always uses preview features and would be marked as such, at least in our code base.

The non-preview class scenario is possible with users annotating a migrated value class field plus opening up jdk internals, don't know what we should do in this case - Given this annotation is expected to be revisited in bworld, wonder if it's worth adding extra checks.

@fparain
Copy link
Copy Markdown
Collaborator

fparain commented Jan 23, 2026

With this change, the double annotation @strict + @NullRestricted can be replaced by a single @NullRestricted annotation in all the tests. Is there a reason why this simplification is not part or this PR?

@liach
Copy link
Copy Markdown
Member Author

liach commented Jan 23, 2026

The annotation @Strict actually has a javac side effect - it makes all initializer statements and constructor bodies early-construction by default. This is not compatible with the planned language evolution with ! types. The PR #1952 fixes that javac side effect.

After both #1951 and #1952 are merged, I plan to move on to to the simplification you mentioned.

@fparain
Copy link
Copy Markdown
Collaborator

fparain commented Jan 23, 2026

I want to confirm that this works on a non-preview class file, and that nothing downstream is going to break. (Hard to prove that second point definitively, but looking for some confirmation from a runtime expert.)

The annotation is currently parsed and processed without checking if the class file has a preview version number or not. Looks like an issue that can easily be fixed.

When a field is annotated with @NullRestricted, it is automatically marked as a null-free value field, which will trigger two actions later in the loading of the declaring class:

  1. the JVM will try to load the type of the field with circularity detection
  2. the JVM will check that the loaded type is a concrete value class
    This is a remain of the previous lworld model, when strict fields didn't exist yet. This is not the behavior we want with null-restricted fields being strict. But this code is not part of JEP 401, so we haven't updated it yet.

For the JEP 401 preview, the simplest solution would be to simply reject or ignore the annotation when the class file hasn't a preview version number.

@dansmithcode
Copy link
Copy Markdown
Collaborator

dansmithcode commented Jan 23, 2026

My worry is that if the annotation gets ignored in a non-preview class file, we'll have to be sure javac is setting the preview version number for the relevant tests. But Chen is right, I think, that a value-class-typed field should already trigger LoadableDescriptors and a preview version number, so maybe there's nothing to worry about here.

Alternatively, we can interpret the annotation in all class file versions. That's what I thought we wanted, and I was just confirming that nothing later is going to break because of an unexpected version number. Sounds like probably not.

So it seems good either way.

@liach
Copy link
Copy Markdown
Member Author

liach commented Jan 23, 2026

Per Fred's suggestion I have added a filter to block NR on non-preview classes. This is experimental; Running tier 1-2 to see whether our "already preview" assumption stands.

@liach
Copy link
Copy Markdown
Member Author

liach commented Jan 25, 2026

I added !HAS_PENDING_EXCEPTION because otherwise it seems to run into resource leaks; early return seems to have resource leaks too.

class_name()->as_C_string(), name->as_C_string());
}
const bool is_strict = (flags & JVM_ACC_STRICT) != 0;
if (!is_strict && !HAS_PENDING_EXCEPTION) {
Copy link
Copy Markdown
Collaborator

@fparain fparain Jan 27, 2026

Choose a reason for hiding this comment

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

This test should not include exception checking.
There's a bug just above, the call to Exceptions::fthrow() should be followed by a return; statement.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I tried to add return; to both fthrow() occurrences. The problem is that they hit some weird assertions in runtime like when metaspace is being teared down. I couldn't diagnose them so I resorted to HAS_PENDING_EXCEPTION which runs successfully instead.

@fparain
Copy link
Copy Markdown
Collaborator

fparain commented Jan 27, 2026

Could you try the patch below that enabled early returns in the parse_fields() method by removing the assumption in Annotations deallocation code that the arrays are fully populated, and by ensuring the ownership transfer of field_annotations is completed before all return to prevent a double free.
It has been tested with only the two tests of this PR, so more testing is needed.

diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp
index 8af14a0d984..0cd354401e3 100644
--- a/src/hotspot/share/classfile/classFileParser.cpp
+++ b/src/hotspot/share/classfile/classFileParser.cpp
@@ -1461,6 +1461,7 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs,
                                              CHECK);
         }
         _fields_annotations->at_put(n, parsed_annotations.field_annotations());
+        parsed_annotations.set_field_annotations(nullptr);
         if (parsed_annotations.has_annotation(AnnotationCollector::_jdk_internal_NullRestricted)) {
           if (!Signature::has_envelope(sig)) {
             Exceptions::fthrow(
@@ -1468,6 +1469,7 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs,
               vmSymbols::java_lang_ClassFormatError(),
               "Illegal use of @jdk.internal.vm.annotation.NullRestricted annotation on field %s.%s with signature %s (primitive types can never be null)",
               class_name()->as_C_string(), name->as_C_string(), sig->as_C_string());
+              return;
           }
           if (!supports_inline_types()) {
             Exceptions::fthrow(
@@ -1475,9 +1477,10 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs,
               vmSymbols::java_lang_ClassFormatError(),
               "Illegal use of @jdk.internal.vm.annotation.NullRestricted annotation on field %s.%s in non-preview class file",
               class_name()->as_C_string(), name->as_C_string());
+              return;
           }
           const bool is_strict = (flags & JVM_ACC_STRICT) != 0;
-          if (!is_strict && !HAS_PENDING_EXCEPTION) {
+          if (!is_strict) {
             // Inject STRICT_INIT and validate in context
             const jint patched_flags = flags | JVM_ACC_STRICT;
             verify_legal_field_modifiers(patched_flags, class_access_flags, CHECK);
@@ -1485,7 +1488,6 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs,
           }
           is_null_restricted = true;
         }
-        parsed_annotations.set_field_annotations(nullptr);
       }
       if (parsed_annotations.field_type_annotations() != nullptr) {
         if (_fields_type_annotations == nullptr) {
diff --git a/src/hotspot/share/oops/annotations.cpp b/src/hotspot/share/oops/annotations.cpp
index c4e49d52383..8fb6f3b7824 100644
--- a/src/hotspot/share/oops/annotations.cpp
+++ b/src/hotspot/share/oops/annotations.cpp
@@ -41,7 +41,9 @@ Annotations* Annotations::allocate(ClassLoaderData* loader_data, TRAPS) {
 void Annotations::free_contents(ClassLoaderData* loader_data, Array<AnnotationArray*>* p) {
   if (p != nullptr) {
     for (int i = 0; i < p->length(); i++) {
-      MetadataFactory::free_array<u1>(loader_data, p->at(i));
+      if (p->at(i) != nullptr) {
+        MetadataFactory::free_array<u1>(loader_data, p->at(i));
+      }
     }
     MetadataFactory::free_array<AnnotationArray*>(loader_data, p);
   }

@liach
Copy link
Copy Markdown
Member Author

liach commented Jan 27, 2026

/contributor add @fparain

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Jan 27, 2026

@liach
Contributor Frederic Parain <fparain@openjdk.org> successfully added.

@liach
Copy link
Copy Markdown
Member Author

liach commented Jan 27, 2026

Ran tier 1-3 on the CI, no failure on linux-x64 platform. I think this patch is safe.

Copy link
Copy Markdown
Collaborator

@fparain fparain left a comment

Choose a reason for hiding this comment

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

Looks good to me.

@openjdk openjdk Bot added the ready Pull request is ready to be integrated label Jan 29, 2026
@liach
Copy link
Copy Markdown
Member Author

liach commented Jan 29, 2026

Thanks for the reviews. I will integrate now to facilitate testing for the subsequent test fixes.

/integrate

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Jan 29, 2026

Going to push as commit e7af4bd.
Since your change was applied there have been 11 commits pushed to the lworld branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk Bot added the integrated Pull request has been integrated label Jan 29, 2026
@openjdk openjdk Bot closed this Jan 29, 2026
@openjdk openjdk Bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jan 29, 2026
@openjdk
Copy link
Copy Markdown

openjdk Bot commented Jan 29, 2026

@liach Pushed as commit e7af4bd.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

3 participants