Coverage Report

Created: 2025-07-24 14:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/be/src/util/jni-util.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <butil/macros.h>
21
#include <jni.h>
22
#include <limits.h>
23
#include <stddef.h>
24
#include <stdint.h>
25
26
#include <string>
27
#include <unordered_map>
28
29
#include "common/status.h"
30
#include "jni_md.h"
31
#include "util/defer_op.h"
32
#include "util/thrift_util.h"
33
34
#ifdef USE_HADOOP_HDFS
35
// defined in hadoop/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/jni_helper.c
36
extern "C" JNIEnv* getJNIEnv(void);
37
#endif
38
39
namespace doris {
40
class JniUtil;
41
42
#define RETURN_ERROR_IF_EXC(env)                     \
43
211k
    do {                                             \
44
211k
        if (env->ExceptionCheck()) [[unlikely]]      \
45
8
            return JniUtil::GetJniExceptionMsg(env); \
46
211k
    } while (false)
47
48
#define JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF(type, result, env, func) \
49
18.3k
    type result = env->func;                                                \
50
18.3k
    DEFER(env->DeleteLocalRef(result));                                     \
51
18.3k
    RETURN_ERROR_IF_EXC(env)
52
53
#define JNI_CALL_METHOD_CHECK_EXCEPTION(type, result, env, func) \
54
6.57k
    type result = env->func;                                     \
55
6.57k
    RETURN_ERROR_IF_EXC(env)
56
57
//In order to reduce the potential risks caused by not handling exceptions,
58
// you need to refer to  https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html
59
// to confirm whether the jni method will throw an exception.
60
61
class JniUtil {
62
public:
63
    static Status Init() WARN_UNUSED_RESULT;
64
65
16
    static jmethodID throwable_to_string_id() { return throwable_to_string_id_; }
66
67
32.3k
    static Status GetJNIEnv(JNIEnv** env) {
68
32.3k
        if (tls_env_) {
69
31.9k
            *env = tls_env_;
70
31.9k
        } else {
71
442
            Status status = GetJNIEnvSlowPath(env);
72
442
            if (!status.ok()) {
73
0
                return status;
74
0
            }
75
442
        }
76
32.3k
        if (*env == nullptr) {
77
0
            return Status::RuntimeError("Failed to get JNIEnv: it is nullptr.");
78
0
        }
79
32.3k
        return Status::OK();
80
32.3k
    }
81
82
    //jclass is generally a local reference.
83
    //Method ID and field ID values are forever.
84
    //If you want to use the jclass across multiple threads or multiple calls into the JNI code you need
85
    // to create a global reference to it with GetGlobalClassRef().
86
    static Status GetGlobalClassRef(JNIEnv* env, const char* class_str,
87
                                    jclass* class_ref) WARN_UNUSED_RESULT;
88
89
    static Status LocalToGlobalRef(JNIEnv* env, jobject local_ref,
90
                                   jobject* global_ref) WARN_UNUSED_RESULT;
91
92
    static Status GetJniExceptionMsg(JNIEnv* env, bool log_stack = true,
93
                                     const std::string& prefix = "") WARN_UNUSED_RESULT;
94
95
16
    static jclass jni_util_class() { return jni_util_cl_; }
96
8
    static jmethodID throwable_to_stack_trace_id() { return throwable_to_stack_trace_id_; }
97
98
    static const int64_t INITIAL_RESERVED_BUFFER_SIZE = 1024;
99
    // TODO: we need a heuristic strategy to increase buffer size for variable-size output.
100
0
    static inline int64_t IncreaseReservedBufferSize(int n) {
101
0
        return INITIAL_RESERVED_BUFFER_SIZE << n;
102
0
    }
103
    static Status get_jni_scanner_class(JNIEnv* env, const char* classname, jclass* loaded_class);
104
    static Status convert_to_java_map(JNIEnv* env, const std::map<std::string, std::string>& map,
105
                                      jobject* hashmap_object);
106
    static Status convert_to_cpp_map(JNIEnv* env, jobject map,
107
                                     std::map<std::string, std::string>* resultMap);
108
    static size_t get_max_jni_heap_memory_size();
109
    static Status clean_udf_class_load_cache(const std::string& function_signature);
110
111
private:
112
    static void parse_max_heap_memory_size_from_jvm(JNIEnv* env);
113
    static Status GetJNIEnvSlowPath(JNIEnv** env);
114
    static Status init_jni_scanner_loader(JNIEnv* env);
115
116
    static bool jvm_inited_;
117
    static jclass internal_exc_cl_;
118
    static jclass jni_native_method_exc_cl_;
119
    static jclass jni_util_cl_;
120
    static jmethodID throwable_to_string_id_;
121
    static jmethodID throwable_to_stack_trace_id_;
122
    static jmethodID get_jvm_metrics_id_;
123
    static jmethodID get_jvm_threads_id_;
124
    static jmethodID get_jmx_json_;
125
    // JNI scanner loader
126
    static jobject jni_scanner_loader_obj_;
127
    static jmethodID jni_scanner_loader_method_;
128
    // Thread-local cache of the JNIEnv for this thread.
129
    static __thread JNIEnv* tls_env_;
130
    static jlong max_jvm_heap_memory_size_;
131
    static jmethodID _clean_udf_cache_method_id;
132
};
133
134
/// Helper class for lifetime management of chars from JNI, releasing JNI chars when
135
/// destructed
136
class JniUtfCharGuard {
137
public:
138
    /// Construct a JniUtfCharGuards holding nothing
139
16
    JniUtfCharGuard() : utf_chars(nullptr) {}
140
141
    /// Release the held char sequence if there is one.
142
16
    ~JniUtfCharGuard() {
143
16
        if (utf_chars != nullptr) env->ReleaseStringUTFChars(jstr, utf_chars);
144
16
    }
145
146
    /// Try to get chars from jstr. If error is returned, utf_chars and get() remain
147
    /// to be nullptr, otherwise they point to a valid char sequence. The char sequence
148
    /// lives as long as this guard. jstr should not be null.
149
    static Status create(JNIEnv* env, jstring jstr, JniUtfCharGuard* out);
150
151
    /// Get the char sequence. Returns nullptr if the guard does hold a char sequence.
152
16
    const char* get() { return utf_chars; }
153
154
private:
155
    JNIEnv* env = nullptr;
156
    jstring jstr;
157
    const char* utf_chars = nullptr;
158
    DISALLOW_COPY_AND_ASSIGN(JniUtfCharGuard);
159
};
160
161
class JniLocalFrame {
162
public:
163
11.9k
    JniLocalFrame() : env_(nullptr) {}
164
12.1k
    ~JniLocalFrame() {
165
12.1k
        if (env_ != nullptr) env_->PopLocalFrame(nullptr);
166
12.1k
    }
167
168
0
    JniLocalFrame(JniLocalFrame&& other) noexcept : env_(other.env_) { other.env_ = nullptr; }
169
170
    /// Pushes a new JNI local frame. The frame can support max_local_ref local references.
171
    /// The number of local references created inside the frame might exceed max_local_ref,
172
    /// but there is no guarantee that memory will be available.
173
    /// Push should be called at most once.
174
    Status push(JNIEnv* env, int max_local_ref = 10) WARN_UNUSED_RESULT;
175
176
private:
177
    DISALLOW_COPY_AND_ASSIGN(JniLocalFrame);
178
179
    JNIEnv* env_ = nullptr;
180
};
181
182
template <class T>
183
12.0k
Status SerializeThriftMsg(JNIEnv* env, T* msg, jbyteArray* serialized_msg) {
184
12.0k
    int buffer_size = 100 * 1024; // start out with 100KB
185
12.0k
    ThriftSerializer serializer(false, buffer_size);
186
187
12.0k
    uint8_t* buffer = nullptr;
188
12.0k
    uint32_t size = 0;
189
12.0k
    RETURN_IF_ERROR(serializer.serialize(msg, &size, &buffer));
190
191
    // Make sure that 'size' is within the limit of INT_MAX as the use of
192
    // 'size' below takes int.
193
12.0k
    if (size > INT_MAX) {
194
0
        return Status::InternalError(
195
0
                "The length of the serialization buffer ({} bytes) exceeds the limit of {} bytes",
196
0
                size, INT_MAX);
197
0
    }
198
199
    /// create jbyteArray given buffer
200
12.0k
    *serialized_msg = env->NewByteArray(size);
201
12.0k
    RETURN_ERROR_IF_EXC(env);
202
12.0k
    if (*serialized_msg == NULL) return Status::InternalError("couldn't construct jbyteArray");
203
12.0k
    env->SetByteArrayRegion(*serialized_msg, 0, size, reinterpret_cast<jbyte*>(buffer));
204
12.0k
    RETURN_ERROR_IF_EXC(env);
205
12.0k
    return Status::OK();
206
12.0k
}
_ZN5doris18SerializeThriftMsgINS_23TJdbcExecutorCtorParamsEEENS_6StatusEP7JNIEnv_PT_PP11_jbyteArray
Line
Count
Source
183
16
Status SerializeThriftMsg(JNIEnv* env, T* msg, jbyteArray* serialized_msg) {
184
16
    int buffer_size = 100 * 1024; // start out with 100KB
185
16
    ThriftSerializer serializer(false, buffer_size);
186
187
16
    uint8_t* buffer = nullptr;
188
16
    uint32_t size = 0;
189
16
    RETURN_IF_ERROR(serializer.serialize(msg, &size, &buffer));
190
191
    // Make sure that 'size' is within the limit of INT_MAX as the use of
192
    // 'size' below takes int.
193
16
    if (size > INT_MAX) {
194
0
        return Status::InternalError(
195
0
                "The length of the serialization buffer ({} bytes) exceeds the limit of {} bytes",
196
0
                size, INT_MAX);
197
0
    }
198
199
    /// create jbyteArray given buffer
200
16
    *serialized_msg = env->NewByteArray(size);
201
16
    RETURN_ERROR_IF_EXC(env);
202
16
    if (*serialized_msg == NULL) return Status::InternalError("couldn't construct jbyteArray");
203
16
    env->SetByteArrayRegion(*serialized_msg, 0, size, reinterpret_cast<jbyte*>(buffer));
204
16
    RETURN_ERROR_IF_EXC(env);
205
16
    return Status::OK();
206
16
}
_ZN5doris18SerializeThriftMsgINS_26TJavaUdfExecutorCtorParamsEEENS_6StatusEP7JNIEnv_PT_PP11_jbyteArray
Line
Count
Source
183
12.0k
Status SerializeThriftMsg(JNIEnv* env, T* msg, jbyteArray* serialized_msg) {
184
12.0k
    int buffer_size = 100 * 1024; // start out with 100KB
185
12.0k
    ThriftSerializer serializer(false, buffer_size);
186
187
12.0k
    uint8_t* buffer = nullptr;
188
12.0k
    uint32_t size = 0;
189
12.0k
    RETURN_IF_ERROR(serializer.serialize(msg, &size, &buffer));
190
191
    // Make sure that 'size' is within the limit of INT_MAX as the use of
192
    // 'size' below takes int.
193
12.0k
    if (size > INT_MAX) {
194
0
        return Status::InternalError(
195
0
                "The length of the serialization buffer ({} bytes) exceeds the limit of {} bytes",
196
0
                size, INT_MAX);
197
0
    }
198
199
    /// create jbyteArray given buffer
200
12.0k
    *serialized_msg = env->NewByteArray(size);
201
12.0k
    RETURN_ERROR_IF_EXC(env);
202
12.0k
    if (*serialized_msg == NULL) return Status::InternalError("couldn't construct jbyteArray");
203
12.0k
    env->SetByteArrayRegion(*serialized_msg, 0, size, reinterpret_cast<jbyte*>(buffer));
204
12.0k
    RETURN_ERROR_IF_EXC(env);
205
12.0k
    return Status::OK();
206
12.0k
}
207
208
} // namespace doris