Skip to content

Commit be21617

Browse files
committed
fix: address JNI bugs
1 parent 6312fa2 commit be21617

6 files changed

Lines changed: 115 additions & 7 deletions

File tree

ddprof-lib/src/main/cpp/jvmSupport.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,36 @@
1717
#include <jni.h>
1818

1919

20+
using JniFunction = void (JNICALL*)();
21+
using IsVirtualThreadFunction = jboolean (JNICALL*)(JNIEnv*, jobject);
22+
23+
static constexpr jint JNI_VERSION_21_VALUE = 0x00150000;
24+
static constexpr int IS_VIRTUAL_THREAD_INDEX = 234;
25+
26+
static_assert(sizeof(JniFunction) == sizeof(void*), "JNI function table entries must be pointer-sized");
27+
2028
volatile JVMSupport::JMethodIDLoadStats JVMSupport::jmethodID_load_state = JVMSupport::No_loaded;
2129
Mutex JVMSupport::_initialization_lock;
2230

2331
bool JVMSupport::isPlatformThread(JNIEnv* jni, jthread thread) {
2432
if (jni == nullptr || thread == nullptr) {
2533
return false;
2634
}
27-
int java_version = VM::java_version();
28-
return java_version > 0 &&
29-
(java_version < 21 || jni->IsVirtualThread(thread) == JNI_FALSE);
35+
36+
jint jni_version = jni->GetVersion();
37+
if (jni_version <= 0) {
38+
return false;
39+
}
40+
if (jni_version < JNI_VERSION_21_VALUE) {
41+
return true;
42+
}
43+
44+
// IsVirtualThread is slot 234 in the JNI 21 function table. Access the
45+
// standardized slot directly so this file also compiles with older headers.
46+
const JniFunction* functions = reinterpret_cast<const JniFunction*>(jni->functions);
47+
IsVirtualThreadFunction is_virtual_thread =
48+
reinterpret_cast<IsVirtualThreadFunction>(functions[IS_VIRTUAL_THREAD_INDEX]);
49+
return is_virtual_thread != nullptr && is_virtual_thread(jni, thread) == JNI_FALSE;
3050
}
3151

3252
bool JVMSupport::initialize() {

ddprof-lib/src/test/cpp/jvmSupport_ut.cpp

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,87 @@ class JvmSupportGlobalSetup {
3737
};
3838
static JvmSupportGlobalSetup jvm_support_global_setup;
3939

40-
TEST(JvmSupportThreadClassificationTest, NullInputsFailClosed) {
41-
EXPECT_FALSE(JVMSupport::isPlatformThread(nullptr, nullptr));
40+
class JvmSupportThreadClassificationTest : public ::testing::Test {
41+
protected:
42+
using JniFunction = void (JNICALL*)();
43+
44+
static constexpr int GET_VERSION_INDEX = 4;
45+
static constexpr int IS_VIRTUAL_THREAD_INDEX = 234;
46+
static constexpr int FUNCTION_TABLE_SIZE = IS_VIRTUAL_THREAD_INDEX + 1;
47+
48+
inline static jint jni_version;
49+
inline static jboolean virtual_thread;
50+
inline static int is_virtual_thread_calls;
51+
inline static jobject last_thread;
52+
53+
JniFunction function_table[FUNCTION_TABLE_SIZE]{};
54+
JNIEnv jni{};
55+
_jobject thread_object;
56+
jthread thread = &thread_object;
57+
58+
static jint JNICALL getVersion(JNIEnv*) {
59+
return jni_version;
60+
}
61+
62+
static jboolean JNICALL isVirtualThread(JNIEnv*, jobject candidate) {
63+
is_virtual_thread_calls++;
64+
last_thread = candidate;
65+
return virtual_thread;
66+
}
67+
68+
void SetUp() override {
69+
jni_version = 0x00150000;
70+
virtual_thread = JNI_FALSE;
71+
is_virtual_thread_calls = 0;
72+
last_thread = nullptr;
73+
74+
function_table[GET_VERSION_INDEX] = reinterpret_cast<JniFunction>(&getVersion);
75+
function_table[IS_VIRTUAL_THREAD_INDEX] = reinterpret_cast<JniFunction>(&isVirtualThread);
76+
jni.functions = reinterpret_cast<const JNINativeInterface_*>(function_table);
77+
}
78+
};
79+
80+
TEST_F(JvmSupportThreadClassificationTest, NullInputsFailClosed) {
81+
EXPECT_FALSE(JVMSupport::isPlatformThread(nullptr, thread));
82+
EXPECT_FALSE(JVMSupport::isPlatformThread(&jni, nullptr));
83+
}
84+
85+
TEST_F(JvmSupportThreadClassificationTest, InvalidJniVersionFailsClosed) {
86+
jni_version = 0;
87+
88+
EXPECT_FALSE(JVMSupport::isPlatformThread(&jni, thread));
89+
EXPECT_EQ(0, is_virtual_thread_calls);
90+
}
91+
92+
TEST_F(JvmSupportThreadClassificationTest, PreJni21ThreadIsPlatform) {
93+
jni_version = 0x000a0000;
94+
function_table[IS_VIRTUAL_THREAD_INDEX] = nullptr;
95+
96+
EXPECT_TRUE(JVMSupport::isPlatformThread(&jni, thread));
97+
EXPECT_EQ(0, is_virtual_thread_calls);
98+
}
99+
100+
TEST_F(JvmSupportThreadClassificationTest, Jni21PlatformThreadIsAccepted) {
101+
virtual_thread = JNI_FALSE;
102+
103+
EXPECT_TRUE(JVMSupport::isPlatformThread(&jni, thread));
104+
EXPECT_EQ(1, is_virtual_thread_calls);
105+
EXPECT_EQ(thread, last_thread);
106+
}
107+
108+
TEST_F(JvmSupportThreadClassificationTest, Jni21VirtualThreadIsRejected) {
109+
virtual_thread = JNI_TRUE;
110+
111+
EXPECT_FALSE(JVMSupport::isPlatformThread(&jni, thread));
112+
EXPECT_EQ(1, is_virtual_thread_calls);
113+
EXPECT_EQ(thread, last_thread);
114+
}
115+
116+
TEST_F(JvmSupportThreadClassificationTest, MissingJni21FunctionFailsClosed) {
117+
function_table[IS_VIRTUAL_THREAD_INDEX] = nullptr;
118+
119+
EXPECT_FALSE(JVMSupport::isPlatformThread(&jni, thread));
120+
EXPECT_EQ(0, is_virtual_thread_calls);
42121
}
43122

44123
// ---------------------------------------------------------------------------

ddprof-lib/src/test/cpp/nativeSocketInterposer_ut.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,9 +592,10 @@ TEST_F(NativeSocketInterposerHookTest, CloseForwardsAndPreservesErrno) {
592592
int fds[2];
593593
ASSERT_EQ(0, pipe(fds));
594594
g_close_ret = 0;
595+
g_close_errno = E2BIG;
595596

596597
LibraryPatcher::_socket_active.store(true, std::memory_order_release);
597-
errno = E2BIG;
598+
errno = ERANGE;
598599
int ret = NativeSocketInterposer::close_hook(fds[0]);
599600

600601
EXPECT_EQ(0, ret);

ddprof-test/src/test/java/com/datadoghq/profiler/AbstractProfilerTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*
2+
* Copyright 2026, Datadog, Inc.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
16
package com.datadoghq.profiler;
27

38
import java.nio.file.Files;
@@ -329,6 +334,7 @@ protected void runTests(Runnable... runnables) throws InterruptedException {
329334

330335
public final void stopProfiler() {
331336
if (!stopped) {
337+
profiler.clearTraceContext();
332338
profiler.stop();
333339
profiler.resetThreadContext();
334340
stopped = true;
@@ -504,4 +510,4 @@ public long getRecordedCounterValue(String counterName) {
504510
}
505511
return -1;
506512
}
507-
}
513+
}

ddprof-test/src/test/java/com/datadoghq/profiler/context/AllNativeContextTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public static void setup() throws IOException {
6363
@AfterEach
6464
public void cleanup() {
6565
if (profilerStarted) {
66+
profiler.clearTraceContext();
6667
profiler.stop();
6768
profiler.resetThreadContext();
6869
profilerStarted = false;

ddprof-test/src/test/java/com/datadoghq/profiler/context/OtelContextStorageModeTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public static void setup() throws IOException {
4545
@AfterEach
4646
public void cleanup() {
4747
if (profilerStarted) {
48+
profiler.clearTraceContext();
4849
profiler.stop();
4950
profiler.resetThreadContext();
5051
profilerStarted = false;

0 commit comments

Comments
 (0)