From 73facf01eec331c2242aaf12788dddad552a581f Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 24 Jun 2026 19:22:55 +0000 Subject: [PATCH 1/5] v0 --- .../src/main/cpp/hotspot/hotspotSupport.cpp | 23 +- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 8 +- .../test/cpp/hotspot_crash_protection_ut.cpp | 254 ++++++++++++++++++ .../cpu/MonitorDeflationThreadSafetyTest.java | 89 ++++++ 4 files changed, 367 insertions(+), 7 deletions(-) create mode 100644 ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp create mode 100644 ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index b0c034233..596fd6330 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -167,7 +167,15 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex } else { Counters::increment(WALKVM_VMTHREAD_OK); } - void* saved_exception = vm_thread != NULL ? vm_thread->exception() : NULL; + // Only write _exception_file for real Java application threads. + // JVM-internal threads like MonitorDeflationThread are JavaThread subclasses + // in JDK 25+ (different vtable → isJavaThread() returns false) but they are + // not regular application threads. Writing _exception_file for them disturbs + // JDK 25's ObjectMonitorDeflationSafepointer, which reads thread state at + // safepoint boundaries and can crash in deflate_monitor_list when the field + // holds a stale jmp_buf address instead of a C-string or NULL. + bool setup_crash_protection = vm_thread != NULL && VMThread::isJavaThread(vm_thread); + void* saved_exception = setup_crash_protection ? vm_thread->exception() : nullptr; // Should be preserved across setjmp/longjmp volatile int depth = 0; @@ -183,11 +191,13 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (anchor == NULL) { Counters::increment(WALKVM_ANCHOR_NULL); } - vm_thread->exception() = &crash_protection_ctx; - if (profiled_thread != nullptr) { + if (setup_crash_protection) { + vm_thread->exception() = &crash_protection_ctx; + } + if (profiled_thread != nullptr && setup_crash_protection) { profiled_thread->setCrashProtectionActive(true); } - if (setjmp(crash_protection_ctx) != 0) { + if (setup_crash_protection && setjmp(crash_protection_ctx) != 0) { // checkFault() does a longjmp from inside segvHandler, bypassing // segvHandler's SignalHandlerScope destructor. Compensate. SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP(); @@ -534,7 +544,8 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (features.vtable_target && nm->isVTableStub() && depth == 0) { uintptr_t receiver = frame.jarg0(); if (receiver != 0) { - VMSymbol* symbol = VMKlass::fromOop(receiver)->name(); + VMKlass* klass = VMKlass::fromOop(receiver); + VMSymbol* symbol = klass != nullptr ? klass->name() : nullptr; // Store the raw VMSymbol* in the frame's method_id // slot. BCI_VTABLE_RECEIVER (vmEntry.h) repurposes // method_id for this pointer — same precedent as @@ -842,7 +853,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (profiled_thread != nullptr) { profiled_thread->setCrashProtectionActive(false); } - if (vm_thread != NULL) { + if (setup_crash_protection) { vm_thread->exception() = saved_exception; } diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 7459aef40..eb0c9de10 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -654,7 +654,13 @@ DECLARE(VMKlass) if (_compact_object_headers) { uintptr_t mark = *(uintptr_t*)oop; if (mark & MONITOR_BIT) { - mark = *(uintptr_t*)(mark ^ MONITOR_BIT); + // TOCTOU: MonitorDeflationThread may free the ObjectMonitor between + // reading the mark word and dereferencing the monitor pointer. Use + // safeFetch64 so a concurrent deflation/free does not crash here. + mark = (uintptr_t)SafeAccess::safeFetch64((int64_t*)(mark ^ MONITOR_BIT), 0); + if (mark == 0) { + return nullptr; + } } narrow_klass = mark >> _markWord_klass_shift; } else { diff --git a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp new file mode 100644 index 000000000..18cc49e42 --- /dev/null +++ b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp @@ -0,0 +1,254 @@ +/* + * Copyright 2026 Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + * + * Unit tests for the MonitorDeflationThread crash-protection gate. + * + * Root cause (JDK 25.0.2): walkVM() unconditionally wrote a jmp_buf address + * into ThreadShadow::_exception_file for every non-null VMThread, including + * JVM-internal threads like MonitorDeflationThread. In JDK 25, + * ObjectMonitorDeflationSafepointer reads _exception_file at safepoint + * boundaries; a stale jmp_buf address caused a crash in deflate_monitor_list. + * + * Fix: gate the write on `VMThread::isJavaThread(vm_thread)`. + * + * `isJavaThread()` has two paths: + * 1. Fast path — reads the cached ProfiledThread::ThreadType set by the + * JVMTI ThreadStart callback (TYPE_JAVA_THREAD / + * TYPE_NOT_JAVA_THREAD). + * 2. Slow path — falls back to the vtable majority vote in + * VMThread::hasJavaThreadVtable(): at least 2 of the 3 + * selected vtable entries (indices 1, 3, 5) must match + * those of a known JavaThread captured at profiler start. + * + * Tests here cover: + * A. ProfiledThread classification (fast path) — setJavaThread / threadType. + * B. Vtable majority-vote logic (slow path) — replicated inline as a + * whitebox test because hasJavaThreadVtable() is private. + * C. setup_crash_protection gate condition — the boolean that walkVM + * uses to decide whether to touch _exception_file. + */ + +#include +#include "thread.h" + +#ifdef __linux__ + +#include + +// --------------------------------------------------------------------------- +// A. ProfiledThread thread-type classification (fast path of isJavaThread) +// --------------------------------------------------------------------------- + +class ProfiledThreadTypeTest : public ::testing::Test { +protected: + void SetUp() override { + ProfiledThread::initCurrentThread(); + _pt = ProfiledThread::currentSignalSafe(); + ASSERT_NE(nullptr, _pt); + } + + void TearDown() override { + ProfiledThread::release(); + } + + ProfiledThread* _pt = nullptr; +}; + +// A fresh ProfiledThread carries TYPE_UNKNOWN until explicitly classified. +TEST_F(ProfiledThreadTypeTest, InitialStateIsUnknown) { + EXPECT_EQ(ProfiledThread::TYPE_UNKNOWN, _pt->threadType()); +} + +// After being marked as a Java thread the type is TYPE_JAVA_THREAD. +TEST_F(ProfiledThreadTypeTest, MarkAsJavaThreadSetsCorrectType) { + _pt->setJavaThread(true); + EXPECT_EQ(ProfiledThread::TYPE_JAVA_THREAD, _pt->threadType()); +} + +// After being marked as a non-Java thread (e.g. MonitorDeflationThread) the +// type is TYPE_NOT_JAVA_THREAD, which makes isJavaThread() return false and +// prevents walkVM from writing _exception_file. +TEST_F(ProfiledThreadTypeTest, MarkAsNonJavaThreadSetsCorrectType) { + _pt->setJavaThread(false); + EXPECT_EQ(ProfiledThread::TYPE_NOT_JAVA_THREAD, _pt->threadType()); +} + +// Reclassification works: a thread initially marked Java can be reclassified. +TEST_F(ProfiledThreadTypeTest, ReclassificationFromJavaToNonJava) { + _pt->setJavaThread(true); + EXPECT_EQ(ProfiledThread::TYPE_JAVA_THREAD, _pt->threadType()); + + _pt->setJavaThread(false); + EXPECT_EQ(ProfiledThread::TYPE_NOT_JAVA_THREAD, _pt->threadType()); +} + +// The fast-path short-circuit: if threadType() != TYPE_UNKNOWN, +// isJavaThread() returns it directly without consulting the vtable. +// Verify the logic isJavaThread() uses: +// type != TYPE_UNKNOWN → return type == TYPE_JAVA_THREAD +TEST_F(ProfiledThreadTypeTest, FastPathLogicForJavaThread) { + _pt->setJavaThread(true); + ProfiledThread::ThreadType type = _pt->threadType(); + bool fast_path_result = (type != ProfiledThread::TYPE_UNKNOWN) + && (type == ProfiledThread::TYPE_JAVA_THREAD); + EXPECT_TRUE(fast_path_result); +} + +TEST_F(ProfiledThreadTypeTest, FastPathLogicForNonJavaThread) { + _pt->setJavaThread(false); + ProfiledThread::ThreadType type = _pt->threadType(); + bool fast_path_result = (type != ProfiledThread::TYPE_UNKNOWN) + && (type == ProfiledThread::TYPE_JAVA_THREAD); + EXPECT_FALSE(fast_path_result); +} + +// --------------------------------------------------------------------------- +// B. Vtable majority-vote logic (slow path of hasJavaThreadVtable) +// +// VMThread::hasJavaThreadVtable() compares vtable entries at indices 1, 3, 5 +// against _java_thread_vtbl[1/3/5] and returns true when >= 2 match. +// This ensures a MonitorDeflationThread (which is a JavaThread subclass but +// has its own vtable) is correctly classified as a non-Java thread. +// +// We replicate the vote logic here as a whitebox unit test, keeping it in +// sync with the comment in vmStructs.inline.h. +// --------------------------------------------------------------------------- + +namespace { + +// Simulate the 2-of-3 majority vote performed by hasJavaThreadVtable(). +// Arguments: +// thread_vtbl – vtable pointer array of the candidate thread +// known_vtbl – _java_thread_vtbl captured from a real JavaThread +static bool vtableVote(void** thread_vtbl, void** known_vtbl) { + int matches = (thread_vtbl[1] == known_vtbl[1]) + + (thread_vtbl[3] == known_vtbl[3]) + + (thread_vtbl[5] == known_vtbl[5]); + return matches >= 2; +} + +} // namespace + +class VtableVoteTest : public ::testing::Test { +protected: + // Sentinel vtable entries used as the "known JavaThread vtable". + void* known[8] = { + (void*)0x1000, (void*)0x1001, (void*)0x1002, + (void*)0x1003, (void*)0x1004, (void*)0x1005, + (void*)0x1006, (void*)0x1007 + }; + + // A thread vtable that exactly matches `known`. + void* same[8] = { + (void*)0x1000, (void*)0x1001, (void*)0x1002, + (void*)0x1003, (void*)0x1004, (void*)0x1005, + (void*)0x1006, (void*)0x1007 + }; + + // A thread vtable that shares no entries with `known` + // (simulates MonitorDeflationThread or another JVM-internal thread). + void* different[8] = { + (void*)0x2000, (void*)0x2001, (void*)0x2002, + (void*)0x2003, (void*)0x2004, (void*)0x2005, + (void*)0x2006, (void*)0x2007 + }; +}; + +// Exact match (3/3) → java thread. +TEST_F(VtableVoteTest, ExactMatchIsJavaThread) { + EXPECT_TRUE(vtableVote(same, known)); +} + +// Zero matches → not a java thread (MonitorDeflationThread case). +TEST_F(VtableVoteTest, NoMatchIsNotJavaThread) { + EXPECT_FALSE(vtableVote(different, known)); +} + +// Exactly 2 matches → still a java thread (handles product vs. debug JVM). +TEST_F(VtableVoteTest, TwoMatchesIsJavaThread) { + void* partial[8]; + memcpy(partial, same, sizeof(partial)); + // Corrupt one of the three checked entries (index 1). + partial[1] = (void*)0xDEAD; + EXPECT_TRUE(vtableVote(partial, known)); +} + +// Exactly 1 match → not a java thread. +TEST_F(VtableVoteTest, OneMatchIsNotJavaThread) { + void* partial[8]; + memcpy(partial, different, sizeof(partial)); + // Give it one real entry (index 3) to stay below the threshold. + partial[3] = known[3]; + EXPECT_FALSE(vtableVote(partial, known)); +} + +// All zeros (uninitialized _java_thread_vtbl before profiler attaches) → +// a thread whose own vtable is also all-zero scores 3/3 but that cannot +// happen in practice because NULL vtable entries dereference to nothing. +// More practically: a non-zero vtable against an all-zero reference → 0/3. +TEST_F(VtableVoteTest, UninitializedReferenceVtableGivesNoMatches) { + void* zero_ref[8] = {}; // _java_thread_vtbl not yet initialized + EXPECT_FALSE(vtableVote(same, zero_ref)); +} + +// --------------------------------------------------------------------------- +// C. setup_crash_protection gate condition +// +// walkVM computes: setup_crash_protection = (vm_thread != NULL) && isJavaThread() +// Verify that the gate is false for TYPE_NOT_JAVA_THREAD threads (fast path). +// --------------------------------------------------------------------------- + +class CrashProtectionGateTest : public ::testing::Test { +protected: + void SetUp() override { + ProfiledThread::initCurrentThread(); + _pt = ProfiledThread::currentSignalSafe(); + ASSERT_NE(nullptr, _pt); + } + + void TearDown() override { + ProfiledThread::release(); + } + + ProfiledThread* _pt = nullptr; + + // Replicate the fast-path of isJavaThread() for gating purposes. + static bool fastPathIsJavaThread(ProfiledThread* pt) { + if (pt == nullptr) return false; + ProfiledThread::ThreadType type = pt->threadType(); + if (type != ProfiledThread::TYPE_UNKNOWN) { + return type == ProfiledThread::TYPE_JAVA_THREAD; + } + return false; // TYPE_UNKNOWN → slow vtable path (not tested here) + } +}; + +// Gate is off for a JVM-internal thread (TYPE_NOT_JAVA_THREAD): +// _exception_file must NOT be written. +TEST_F(CrashProtectionGateTest, GateOffForNonJavaThread) { + _pt->setJavaThread(false); + // Simulate: vm_thread != NULL (non-null pointer means JVM thread struct exists) + bool vm_thread_non_null = true; + bool setup_crash_protection = vm_thread_non_null && fastPathIsJavaThread(_pt); + EXPECT_FALSE(setup_crash_protection); +} + +// Gate is on for a real Java application thread (TYPE_JAVA_THREAD): +// _exception_file IS written so crash recovery via longjmp works. +TEST_F(CrashProtectionGateTest, GateOnForJavaThread) { + _pt->setJavaThread(true); + bool vm_thread_non_null = true; + bool setup_crash_protection = vm_thread_non_null && fastPathIsJavaThread(_pt); + EXPECT_TRUE(setup_crash_protection); +} + +// Gate is always off when vm_thread is NULL (no JVM thread struct). +TEST_F(CrashProtectionGateTest, GateOffWhenNoVmThread) { + _pt->setJavaThread(true); // would be a Java thread, but vm_thread is NULL + bool vm_thread_non_null = false; + bool setup_crash_protection = vm_thread_non_null && fastPathIsJavaThread(_pt); + EXPECT_FALSE(setup_crash_protection); +} + +#endif // __linux__ diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java new file mode 100644 index 000000000..b4b14520f --- /dev/null +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java @@ -0,0 +1,89 @@ +package com.datadoghq.profiler.cpu; + +import com.datadoghq.profiler.AbstractProfilerTest; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.RetryingTest; + +/** + * Regression test for the MonitorDeflationThread crash in JDK 25.0.2+. + * + * Root cause: walkVM() unconditionally wrote a jmp_buf address into + * ThreadShadow::_exception_file for every non-null VMThread, including + * JVM-internal threads such as MonitorDeflationThread. In JDK 25, + * ObjectMonitorDeflationSafepointer reads thread state (including + * _exception_file) at safepoint boundaries; finding a stale jmp_buf + * address instead of NULL or a C-string caused a crash in + * deflate_monitor_list. + * + * Fix: gate the _exception_file write on VMThread::isJavaThread(), which + * uses vtable comparison to distinguish regular Java application threads + * from JVM-internal threads that are JavaThread subclasses in JDK 25+. + * + * This test forces ObjectMonitor inflation/deflation to race with CPU + * profiler signal delivery. If the fix regresses, the JVM will crash + * with a SIGSEGV in deflate_monitor_list before the test completes. + */ +public class MonitorDeflationThreadSafetyTest extends AbstractProfilerTest { + + // Number of objects to synchronize on each wave — enough to ensure + // MonitorDeflationThread has work to do between waves. + private static final int MONITOR_COUNT = 500; + + // Total duration of monitor churn while the profiler is running (ms). + private static final int CHURN_DURATION_MS = 3000; + + // Sleep between waves to give MonitorDeflationThread time to deflate + // the monitors we released (it runs every ~250 ms by default). + private static final int WAVE_SLEEP_MS = 300; + + @RetryingTest(3) + public void monitorDeflationDoesNotCrashProfiler() throws Exception { + // The profiler is already started by AbstractProfilerTest.setupProfiler(). + // Run the monitor churn on the test thread itself so the CPU profiler + // definitely samples it, confirming signals are being delivered during + // the deflation window. + inflateAndDeflateMonitors(CHURN_DURATION_MS, WAVE_SLEEP_MS); + stopProfiler(); + + // If we reach this line the JVM survived — no SIGSEGV in + // deflate_monitor_list. Verify the profiler actually produced samples + // to confirm signals were delivered while monitors were being deflated. + verifyEvents("datadog.ExecutionSample"); + } + + /** + * Repeatedly inflates then releases a batch of ObjectMonitors, sleeping + * between waves so MonitorDeflationThread can reclaim them. + * + * Each synchronized block on a freshly-allocated Object causes the JVM to + * inflate that object's mark word into a full ObjectMonitor. Releasing + * the lock makes the monitor eligible for deflation. + */ + private static void inflateAndDeflateMonitors(long durationMs, long waveSleepMs) + throws InterruptedException { + Object[] monitors = new Object[MONITOR_COUNT]; + for (int i = 0; i < monitors.length; i++) { + monitors[i] = new Object(); + } + + long deadline = System.currentTimeMillis() + durationMs; + while (System.currentTimeMillis() < deadline) { + // Inflate: synchronize on every object to force monitor creation. + for (Object mon : monitors) { + synchronized (mon) { + // brief critical section — just enough to inflate the monitor + Thread.yield(); + } + } + + // Sleep so MonitorDeflationThread can observe and deflate the + // now-idle monitors while the CPU profiler is still sending signals. + Thread.sleep(waveSleepMs); + } + } + + @Override + protected String getProfilerCommand() { + return "cpu=10ms"; + } +} From ce7118ff61fe0f6e11ba0b8babcd53d1c6f6c0c5 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 24 Jun 2026 16:58:57 -0400 Subject: [PATCH 2/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../profiler/cpu/MonitorDeflationThreadSafetyTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java index b4b14520f..48d365594 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java @@ -68,11 +68,10 @@ private static void inflateAndDeflateMonitors(long durationMs, long waveSleepMs) long deadline = System.currentTimeMillis() + durationMs; while (System.currentTimeMillis() < deadline) { - // Inflate: synchronize on every object to force monitor creation. for (Object mon : monitors) { synchronized (mon) { - // brief critical section — just enough to inflate the monitor - Thread.yield(); + // wait() forces inflation to an ObjectMonitor and makes it eligible for deflation + mon.wait(1); } } From cb46ffa32553f5468f055d3a79090f9d4e5d6d82 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 24 Jun 2026 16:59:06 -0400 Subject: [PATCH 3/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../profiler/cpu/MonitorDeflationThreadSafetyTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java index 48d365594..ba6bea189 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java @@ -55,9 +55,9 @@ public void monitorDeflationDoesNotCrashProfiler() throws Exception { * Repeatedly inflates then releases a batch of ObjectMonitors, sleeping * between waves so MonitorDeflationThread can reclaim them. * - * Each synchronized block on a freshly-allocated Object causes the JVM to - * inflate that object's mark word into a full ObjectMonitor. Releasing - * the lock makes the monitor eligible for deflation. + * Using an operation like Object.wait(timeout) while holding the lock forces + * monitor inflation to a full ObjectMonitor. Releasing the lock then makes + * the monitor eligible for deflation. */ private static void inflateAndDeflateMonitors(long durationMs, long waveSleepMs) throws InterruptedException { From ba279d0b194ad50d34f937cc32a463fc4ae6d625 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 24 Jun 2026 20:42:13 -0400 Subject: [PATCH 4/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java index ba6bea189..f7edaacc5 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/MonitorDeflationThreadSafetyTest.java @@ -1,7 +1,6 @@ package com.datadoghq.profiler.cpu; import com.datadoghq.profiler.AbstractProfilerTest; -import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.RetryingTest; /** From c6936b5d551df4c1ab9cda24ef62234ad226e008 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 24 Jun 2026 20:42:33 -0400 Subject: [PATCH 5/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp index 18cc49e42..773c52445 100644 --- a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp +++ b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp @@ -35,6 +35,7 @@ #ifdef __linux__ #include +#include // --------------------------------------------------------------------------- // A. ProfiledThread thread-type classification (fast path of isJavaThread)