JDK-8303624 : The java.lang.Thread.FieldHolder can be null for JNI attaching threads
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 19,20,21
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2023-03-06
  • Updated: 2023-03-16
  • Resolved: 2023-03-10
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.
JDK 21
21 b14Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
With the introduction of virtual threads (JDK-8284161) a number of fields in java.lang.Thread were moved into a separate data structure, the FieldHolder class, so that only platform threads would have these. Unfortunately the logic in the VM to create the java.lang.Thread instance when attaching a native thread doesn't account for this properly as it can try to use these fields even if the Thread constructor threw an exception and failed to create the FieldHolder:

void JavaThread::allocate_threadObj(Handle thread_group, const char* thread_name,
                                    bool daemon, TRAPS) {
...
if (thread_name != nullptr) {
    Handle name = java_lang_String::create_from_str(thread_name, CHECK);
    // Thread gets assigned specified name and null target
    JavaCalls::call_special(&result,
...
                            THREAD);
  } else {
    JavaCalls::call_special(&result,
...
                            THREAD);
  }

<= Exception could be pending at this point due to use of THREAD not CHECK
  os::set_priority(this, NormPriority);

  if (daemon) {
    java_lang_Thread::set_daemon(thread_oop()); <== Could try to set null FieldHolder here
  }

Further, java_lang_Thread::is_daemon() also expects the FieldHolder to be set if you have a non-null Thread oop, but that is not guaranteed.

This problem manifests as a failed assertion in a debug build, when calling set_daemon, as the FieldHolder field is null. Crash reporting then calls is-daemon which fails the same assert and so we get a secondary crash.
Comments
Changeset: e26cc526 Author: David Holmes <dholmes@openjdk.org> Date: 2023-03-10 03:08:26 +0000 URL: https://git.openjdk.org/jdk/commit/e26cc526006b16765510e72bd085de069dfae419
10-03-2023

See JDK-6404306 for the general problem of JNI-attaching threads partial initialization. JVMTI exposes such threads so in theory could call any Thread methods on them. This would even "work" before the introduction of the FieldHolder because we have successfully allocated (but not initialized) the Thread instance and so can set the fields. Now however any "set" operation would have to be a no-op. EDIT: correction. Prompted by a query from [~alanb] I re-examined the setter methods and none of them can be called on a JNI attaching thread whilst it is partially constructed. Such a thread could have regular Java methods called on it, but they would throw NPE when trying to access the FieldHolder. So it suffices to keep the assertion that holder!=nullptr in the setter methods.
07-03-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/12892 Date: 2023-03-06 21:52:47 +0000
06-03-2023

Marked as noreg-hard as we would need to be able to attach a thread and force the Thread constructor to throw an exception before initializing the FieldHolder, and then switch to another thread that invoked JVMTI to get access to the attaching thread and then invoke methods in it whilst still attaching. There is no way to do that.
06-03-2023

Unfortunately there are cases where attaching threads are not ignored. In such cases it is not just that we have to avoid trying to access a null holder field but we also have to return the default values for these un-set holder fields.
06-03-2023

This potentially impacts access to all these fields: private static class FieldHolder { final ThreadGroup group; final Runnable task; final long stackSize; volatile int priority; volatile boolean daemon; volatile int threadStatus; fortunately attaching threads are ignored in most cases until the attaching is complete. So it is really on the attach code itself, and crash reporting that needs to be resilient to this problem.
06-03-2023