Coverage Report

Created: 2025-08-27 05:46

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