WPILibC++ 2023.4.3-108-ge5452e3
jni_util.h
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#ifndef WPIUTIL_WPI_JNI_UTIL_H_
6#define WPIUTIL_WPI_JNI_UTIL_H_
7
8#include <jni.h>
9
10#include <concepts>
11#include <queue>
12#include <span>
13#include <string>
14#include <string_view>
15#include <utility>
16#include <vector>
17
18#include "wpi/ConvertUTF.h"
19#include "wpi/SafeThread.h"
20#include "wpi/SmallString.h"
21#include "wpi/SmallVector.h"
22#include "wpi/StringExtras.h"
23#include "wpi/mutex.h"
24#include "wpi/raw_ostream.h"
25
26/** Java Native Interface (JNI) utility functions */
27namespace wpi::java {
28
29/**
30 * Gets a Java stack trace.
31 *
32 * Also provides the last function in the stack trace not starting with
33 * excludeFuncPrefix (useful for e.g. finding the first user call to a series of
34 * library functions).
35 *
36 * @param env JRE environment.
37 * @param func Storage for last function in the stack trace not starting with
38 * excludeFuncPrefix.
39 * @param excludeFuncPrefix Prefix for functions to ignore in stack trace.
40 */
41std::string GetJavaStackTrace(JNIEnv* env, std::string* func = nullptr,
42 std::string_view excludeFuncPrefix = {});
43
44/**
45 * Finds a class and keeps it as a global reference.
46 *
47 * Use with caution, as the destructor does NOT call DeleteGlobalRef due to
48 * potential shutdown issues with doing so.
49 */
50class JClass {
51 public:
52 JClass() = default;
53
54 JClass(JNIEnv* env, const char* name) {
55 jclass local = env->FindClass(name);
56 if (!local) {
57 return;
58 }
59 m_cls = static_cast<jclass>(env->NewGlobalRef(local));
60 env->DeleteLocalRef(local);
61 }
62
63 void free(JNIEnv* env) {
64 if (m_cls) {
65 env->DeleteGlobalRef(m_cls);
66 }
67 m_cls = nullptr;
68 }
69
70 explicit operator bool() const { return m_cls; }
71
72 operator jclass() const { return m_cls; }
73
74 protected:
75 jclass m_cls = nullptr;
76};
77
78struct JClassInit {
79 const char* name;
81};
82
83template <typename T>
84class JGlobal {
85 public:
86 JGlobal() = default;
87
88 JGlobal(JNIEnv* env, T obj) {
89 m_cls = static_cast<T>(env->NewGlobalRef(obj));
90 }
91
92 void free(JNIEnv* env) {
93 if (m_cls) {
94 env->DeleteGlobalRef(m_cls);
95 }
96 m_cls = nullptr;
97 }
98
99 explicit operator bool() const { return m_cls; }
100
101 operator T() const { return m_cls; } // NOLINT
102
103 protected:
104 T m_cls = nullptr;
105};
106
107/**
108 * Container class for cleaning up Java local references.
109 *
110 * The destructor calls DeleteLocalRef.
111 */
112template <typename T>
113class JLocal {
114 public:
115 JLocal(JNIEnv* env, T obj) : m_env(env), m_obj(obj) {}
116 JLocal(const JLocal&) = delete;
117 JLocal(JLocal&& oth) : m_env(oth.m_env), m_obj(oth.m_obj) {
118 oth.m_obj = nullptr;
119 }
120 JLocal& operator=(const JLocal&) = delete;
122 m_env = oth.m_env;
123 m_obj = oth.m_obj;
124 oth.m_obj = nullptr;
125 return *this;
126 }
128 if (m_obj) {
129 m_env->DeleteLocalRef(m_obj);
130 }
131 }
132 operator T() { return m_obj; } // NOLINT
133 T obj() { return m_obj; }
134
135 private:
136 JNIEnv* m_env;
137 T m_obj;
138};
139
140//
141// Conversions from Java objects to C++
142//
143
144/**
145 * Java string (jstring) reference.
146 *
147 * The string is provided as UTF8. This is not actually a reference, as it makes
148 * a copy of the string characters, but it's named this way for consistency.
149 */
151 public:
152 JStringRef(JNIEnv* env, jstring str) {
153 if (str) {
154 jsize size = env->GetStringLength(str);
155 const jchar* chars = env->GetStringCritical(str, nullptr);
156 if (chars) {
157 convertUTF16ToUTF8String(std::span<const jchar>(chars, size), m_str);
158 env->ReleaseStringCritical(str, chars);
159 }
160 } else {
161 errs() << "JStringRef was passed a null pointer at \n"
162 << GetJavaStackTrace(env);
163 }
164 // Ensure str is null-terminated.
165 m_str.push_back('\0');
166 m_str.pop_back();
167 }
168
169 operator std::string_view() const { return m_str.str(); } // NOLINT
170 std::string_view str() const { return m_str.str(); }
171 const char* c_str() const { return m_str.data(); }
172 size_t size() const { return m_str.size(); }
173
174 private:
175 SmallString<128> m_str;
176};
177
178// Details for J*ArrayRef and CriticalJ*ArrayRef
179namespace detail {
180
181template <typename C, typename T>
183 public:
184 operator std::span<const T>() const { // NOLINT
185 return static_cast<const C*>(this)->array();
186 }
187};
188
189/**
190 * Specialization of JArrayRefBase to provide std::string_view conversion
191 * and span<const uint8_t> conversion.
192 */
193template <typename C>
194class JArrayRefInner<C, jbyte> {
195 public:
196 operator std::string_view() const { return str(); }
197
199 auto arr = static_cast<const C*>(this)->array();
200 if (arr.empty()) {
201 return {};
202 }
203 return {reinterpret_cast<const char*>(arr.data()), arr.size()};
204 }
205
206 std::span<const uint8_t> uarray() const {
207 auto arr = static_cast<const C*>(this)->array();
208 if (arr.empty()) {
209 return {};
210 }
211 return {reinterpret_cast<const uint8_t*>(arr.data()), arr.size()};
212 }
213};
214
215/**
216 * Specialization of JArrayRefBase to handle both "long long" and "long" on
217 * 64-bit systems.
218 */
219template <typename C>
220class JArrayRefInner<C, jlong> {
221 public:
222 template <typename U>
223 requires(sizeof(U) == sizeof(jlong) && std::integral<U>)
224 operator std::span<const U>() const { // NOLINT
225 auto arr = static_cast<const C*>(this)->array();
226 if (arr.empty()) {
227 return {};
228 }
229 return {reinterpret_cast<const U*>(arr.data()), arr.size()};
230 }
231};
232
233/**
234 * Base class for J*ArrayRef and CriticalJ*ArrayRef
235 */
236template <typename T>
237class JArrayRefBase : public JArrayRefInner<JArrayRefBase<T>, T> {
238 public:
239 explicit operator bool() const { return this->m_elements != nullptr; }
240
241 std::span<const T> array() const {
242 if (!this->m_elements) {
243 return {};
244 }
245 return {this->m_elements, this->m_size};
246 }
247
248 size_t size() const { return this->m_size; }
249 T& operator[](size_t i) { return this->m_elements[i]; }
250 const T& operator[](size_t i) const { return this->m_elements[i]; }
251
252 JArrayRefBase(const JArrayRefBase&) = delete;
254
256 : m_env(oth.m_env),
257 m_jarr(oth.m_jarr),
258 m_size(oth.m_size),
260 oth.m_jarr = nullptr;
261 oth.m_elements = nullptr;
262 }
263
265 this->m_env = oth.m_env;
266 this->m_jarr = oth.m_jarr;
267 this->m_size = oth.m_size;
268 this->m_elements = oth.m_elements;
269 oth.m_jarr = nullptr;
270 oth.m_elements = nullptr;
271 return *this;
272 }
273
274 protected:
275 JArrayRefBase(JNIEnv* env, T* elements, size_t size) {
276 this->m_env = env;
277 this->m_jarr = nullptr;
278 this->m_size = size;
279 this->m_elements = elements;
280 }
281
282 JArrayRefBase(JNIEnv* env, jarray jarr, size_t size) {
283 this->m_env = env;
284 this->m_jarr = jarr;
285 this->m_size = size;
286 this->m_elements = nullptr;
287 }
288
289 JArrayRefBase(JNIEnv* env, jarray jarr)
290 : JArrayRefBase(env, jarr, jarr ? env->GetArrayLength(jarr) : 0) {}
291
292 JNIEnv* m_env;
293 jarray m_jarr = nullptr;
294 size_t m_size;
296};
297
298} // namespace detail
299
300// Java array / DirectBuffer reference.
301
302#define WPI_JNI_JARRAYREF(T, F) \
303 class J##F##ArrayRef : public detail::JArrayRefBase<T> { \
304 public: \
305 J##F##ArrayRef(JNIEnv* env, jobject bb, int len) \
306 : detail::JArrayRefBase<T>( \
307 env, \
308 static_cast<T*>(bb ? env->GetDirectBufferAddress(bb) : nullptr), \
309 len) { \
310 if (!bb) { \
311 errs() << "JArrayRef was passed a null pointer at \n" \
312 << GetJavaStackTrace(env); \
313 } \
314 } \
315 J##F##ArrayRef(JNIEnv* env, T##Array jarr, int len) \
316 : detail::JArrayRefBase<T>(env, jarr, len) { \
317 if (jarr) { \
318 m_elements = env->Get##F##ArrayElements(jarr, nullptr); \
319 } else { \
320 errs() << "JArrayRef was passed a null pointer at \n" \
321 << GetJavaStackTrace(env); \
322 } \
323 } \
324 J##F##ArrayRef(JNIEnv* env, T##Array jarr) \
325 : detail::JArrayRefBase<T>(env, jarr) { \
326 if (jarr) { \
327 m_elements = env->Get##F##ArrayElements(jarr, nullptr); \
328 } else { \
329 errs() << "JArrayRef was passed a null pointer at \n" \
330 << GetJavaStackTrace(env); \
331 } \
332 } \
333 ~J##F##ArrayRef() { \
334 if (m_jarr && m_elements) { \
335 m_env->Release##F##ArrayElements(static_cast<T##Array>(m_jarr), \
336 m_elements, JNI_ABORT); \
337 } \
338 } \
339 }; \
340 \
341 class CriticalJ##F##ArrayRef : public detail::JArrayRefBase<T> { \
342 public: \
343 CriticalJ##F##ArrayRef(JNIEnv* env, T##Array jarr, int len) \
344 : detail::JArrayRefBase<T>(env, jarr, len) { \
345 if (jarr) { \
346 m_elements = \
347 static_cast<T*>(env->GetPrimitiveArrayCritical(jarr, nullptr)); \
348 } else { \
349 errs() << "JArrayRef was passed a null pointer at \n" \
350 << GetJavaStackTrace(env); \
351 } \
352 } \
353 CriticalJ##F##ArrayRef(JNIEnv* env, T##Array jarr) \
354 : detail::JArrayRefBase<T>(env, jarr) { \
355 if (jarr) { \
356 m_elements = \
357 static_cast<T*>(env->GetPrimitiveArrayCritical(jarr, nullptr)); \
358 } else { \
359 errs() << "JArrayRef was passed a null pointer at \n" \
360 << GetJavaStackTrace(env); \
361 } \
362 } \
363 ~CriticalJ##F##ArrayRef() { \
364 if (m_jarr && m_elements) { \
365 m_env->ReleasePrimitiveArrayCritical(m_jarr, m_elements, JNI_ABORT); \
366 } \
367 } \
368 };
369
370WPI_JNI_JARRAYREF(jboolean, Boolean)
371WPI_JNI_JARRAYREF(jbyte, Byte)
372WPI_JNI_JARRAYREF(jshort, Short)
373WPI_JNI_JARRAYREF(jint, Int)
374WPI_JNI_JARRAYREF(jlong, Long)
375WPI_JNI_JARRAYREF(jfloat, Float)
376WPI_JNI_JARRAYREF(jdouble, Double)
377
378#undef WPI_JNI_JARRAYREF
379
380//
381// Conversions from C++ to Java objects
382//
383
384/**
385 * Convert a UTF8 string into a jstring.
386 *
387 * @param env JRE environment.
388 * @param str String to convert.
389 */
390inline jstring MakeJString(JNIEnv* env, std::string_view str) {
392 convertUTF8ToUTF16String(str, chars);
393 return env->NewString(chars.begin(), chars.size());
394}
395
396// details for MakeJIntArray
397namespace detail {
398
399template <typename T>
401 static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
402 if constexpr (sizeof(T) == sizeof(jint) && std::integral<T>) {
403 // Fast path (use SetIntArrayRegion).
404 jintArray jarr = env->NewIntArray(arr.size());
405 if (!jarr) {
406 return nullptr;
407 }
408 env->SetIntArrayRegion(jarr, 0, arr.size(),
409 reinterpret_cast<const jint*>(arr.data()));
410 return jarr;
411 } else {
412 // Slow path (get primitive array and set individual elements).
413 //
414 // This is used if the input type is not an integer of the same size (note
415 // signed/unsigned is ignored).
416 jintArray jarr = env->NewIntArray(arr.size());
417 if (!jarr) {
418 return nullptr;
419 }
420 jint* elements =
421 static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
422 if (!elements) {
423 return nullptr;
424 }
425 for (size_t i = 0; i < arr.size(); ++i) {
426 elements[i] = static_cast<jint>(arr[i]);
427 }
428 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
429 return jarr;
430 }
431 }
432};
433
434} // namespace detail
435
436/**
437 * Convert a span to a jintArray.
438 *
439 * @param env JRE environment.
440 * @param arr Span to convert.
441 */
442template <typename T>
443inline jintArray MakeJIntArray(JNIEnv* env, std::span<const T> arr) {
444 return detail::ConvertIntArray<T>::ToJava(env, arr);
445}
446
447/**
448 * Convert a span to a jintArray.
449 *
450 * @param env JRE environment.
451 * @param arr Span to convert.
452 */
453template <typename T>
454inline jintArray MakeJIntArray(JNIEnv* env, std::span<T> arr) {
455 return detail::ConvertIntArray<T>::ToJava(env, arr);
456}
457
458/**
459 * Convert a SmallVector to a jintArray.
460 *
461 * This is required in addition to ArrayRef because template resolution occurs
462 * prior to implicit conversions.
463 *
464 * @param env JRE environment.
465 * @param arr SmallVector to convert.
466 */
467template <typename T>
468inline jintArray MakeJIntArray(JNIEnv* env, const SmallVectorImpl<T>& arr) {
469 return detail::ConvertIntArray<T>::ToJava(env, arr);
470}
471
472/**
473 * Convert a std::vector to a jintArray.
474 *
475 * This is required in addition to ArrayRef because template resolution occurs
476 * prior to implicit conversions.
477 *
478 * @param env JRE environment.
479 * @param arr SmallVector to convert.
480 */
481template <typename T>
482inline jintArray MakeJIntArray(JNIEnv* env, const std::vector<T>& arr) {
483 return detail::ConvertIntArray<T>::ToJava(env, arr);
484}
485
486/**
487 * Convert a span into a jbyteArray.
488 *
489 * @param env JRE environment.
490 * @param str span to convert.
491 */
492inline jbyteArray MakeJByteArray(JNIEnv* env, std::span<const uint8_t> str) {
493 jbyteArray jarr = env->NewByteArray(str.size());
494 if (!jarr) {
495 return nullptr;
496 }
497 env->SetByteArrayRegion(jarr, 0, str.size(),
498 reinterpret_cast<const jbyte*>(str.data()));
499 return jarr;
500}
501
502/**
503 * Convert an array of integers into a jbooleanArray.
504 *
505 * @param env JRE environment.
506 * @param arr Array to convert.
507 */
508inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const int> arr) {
509 jbooleanArray jarr = env->NewBooleanArray(arr.size());
510 if (!jarr) {
511 return nullptr;
512 }
513 jboolean* elements =
514 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
515 if (!elements) {
516 return nullptr;
517 }
518 for (size_t i = 0; i < arr.size(); ++i) {
519 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
520 }
521 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
522 return jarr;
523}
524
525/**
526 * Convert an array of booleans into a jbooleanArray.
527 *
528 * @param env JRE environment.
529 * @param arr Array to convert.
530 */
531inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const bool> arr) {
532 jbooleanArray jarr = env->NewBooleanArray(arr.size());
533 if (!jarr) {
534 return nullptr;
535 }
536 jboolean* elements =
537 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
538 if (!elements) {
539 return nullptr;
540 }
541 for (size_t i = 0; i < arr.size(); ++i) {
542 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
543 }
544 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
545 return jarr;
546}
547
548// Other MakeJ*Array conversions.
549
550#define WPI_JNI_MAKEJARRAY(T, F) \
551 inline T##Array MakeJ##F##Array(JNIEnv* env, std::span<const T> arr) { \
552 T##Array jarr = env->New##F##Array(arr.size()); \
553 if (!jarr) { \
554 return nullptr; \
555 } \
556 env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data()); \
557 return jarr; \
558 }
559
561WPI_JNI_MAKEJARRAY(jbyte, Byte)
562WPI_JNI_MAKEJARRAY(jshort, Short)
563WPI_JNI_MAKEJARRAY(jfloat, Float)
564WPI_JNI_MAKEJARRAY(jdouble, Double)
565
566#undef WPI_JNI_MAKEJARRAY
567
568template <class T>
569 requires(sizeof(typename T::value_type) == sizeof(jlong) &&
570 std::integral<typename T::value_type>)
571inline jlongArray MakeJLongArray(JNIEnv* env, const T& arr) {
572 jlongArray jarr = env->NewLongArray(arr.size());
573 if (!jarr) {
574 return nullptr;
575 }
576 env->SetLongArrayRegion(jarr, 0, arr.size(),
577 reinterpret_cast<const jlong*>(arr.data()));
578 return jarr;
579}
580
581/**
582 * Convert an array of std::string into a jarray of jstring.
583 *
584 * @param env JRE environment.
585 * @param arr Array to convert.
586 */
587inline jobjectArray MakeJStringArray(JNIEnv* env,
588 std::span<const std::string> arr) {
589 static JClass stringCls{env, "java/lang/String"};
590 if (!stringCls) {
591 return nullptr;
592 }
593 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
594 if (!jarr) {
595 return nullptr;
596 }
597 for (size_t i = 0; i < arr.size(); ++i) {
598 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
599 env->SetObjectArrayElement(jarr, i, elem.obj());
600 }
601 return jarr;
602}
603
604/**
605 * Convert an array of std::string into a jarray of jstring.
606 *
607 * @param env JRE environment.
608 * @param arr Array to convert.
609 */
610inline jobjectArray MakeJStringArray(JNIEnv* env,
611 std::span<std::string_view> arr) {
612 static JClass stringCls{env, "java/lang/String"};
613 if (!stringCls) {
614 return nullptr;
615 }
616 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
617 if (!jarr) {
618 return nullptr;
619 }
620 for (size_t i = 0; i < arr.size(); ++i) {
621 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
622 env->SetObjectArrayElement(jarr, i, elem.obj());
623 }
624 return jarr;
625}
626
627/**
628 * Generic callback thread implementation.
629 *
630 * JNI's AttachCurrentThread() creates a Java Thread object on every
631 * invocation, which is both time inefficient and causes issues with Eclipse
632 * (which tries to keep a thread list up-to-date and thus gets swamped).
633 *
634 * Instead, this class attaches just once. When a hardware notification
635 * occurs, a condition variable wakes up this thread and this thread actually
636 * makes the call into Java.
637 *
638 * The template parameter T is the message being passed to the callback, but
639 * also needs to provide the following functions:
640 * static JavaVM* GetJVM();
641 * static const char* GetName();
642 * void CallJava(JNIEnv *env, jobject func, jmethodID mid);
643 */
644template <typename T>
646 public:
647 void Main() override;
648
649 std::queue<T> m_queue;
650 jobject m_func = nullptr;
651 jmethodID m_mid;
652};
653
654template <typename T>
655class JCallbackManager : public SafeThreadOwner<JCallbackThread<T>> {
656 public:
657 JCallbackManager() { this->SetJoinAtExit(false); }
658 void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
659
660 template <typename... Args>
661 void Send(Args&&... args);
662};
663
664template <typename T>
665void JCallbackManager<T>::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
666 auto thr = this->GetThread();
667 if (!thr) {
668 return;
669 }
670 // free global reference
671 if (thr->m_func) {
672 env->DeleteGlobalRef(thr->m_func);
673 }
674 // create global reference
675 thr->m_func = env->NewGlobalRef(func);
676 thr->m_mid = mid;
677}
678
679template <typename T>
680template <typename... Args>
681void JCallbackManager<T>::Send(Args&&... args) {
682 auto thr = this->GetThread();
683 if (!thr) {
684 return;
685 }
686 thr->m_queue.emplace(std::forward<Args>(args)...);
687 thr->m_cond.notify_one();
688}
689
690template <typename T>
692 JNIEnv* env;
693 JavaVMAttachArgs args;
694 args.version = JNI_VERSION_1_2;
695 args.name = const_cast<char*>(T::GetName());
696 args.group = nullptr;
697 jint rs = T::GetJVM()->AttachCurrentThreadAsDaemon(
698 reinterpret_cast<void**>(&env), &args);
699 if (rs != JNI_OK) {
700 return;
701 }
702
703 std::unique_lock lock(m_mutex);
704 while (m_active) {
705 m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
706 if (!m_active) {
707 break;
708 }
709 while (!m_queue.empty()) {
710 if (!m_active) {
711 break;
712 }
713 auto item = std::move(m_queue.front());
714 m_queue.pop();
715 auto func = m_func;
716 auto mid = m_mid;
717 lock.unlock(); // don't hold mutex during callback execution
718 item.CallJava(env, func, mid);
719 if (env->ExceptionCheck()) {
720 env->ExceptionDescribe();
721 env->ExceptionClear();
722 }
723 lock.lock();
724 }
725 }
726
727 JavaVM* jvm = T::GetJVM();
728 if (jvm) {
729 jvm->DetachCurrentThread();
730 }
731}
732
733template <typename T>
735 public:
737 static JSingletonCallbackManager<T> instance;
738 return instance;
739 }
740};
741
742inline std::string GetJavaStackTrace(JNIEnv* env, std::string_view skipPrefix) {
743 // create a throwable
744 static JClass throwableCls(env, "java/lang/Throwable");
745 if (!throwableCls) {
746 return "";
747 }
748 static jmethodID constructorId = nullptr;
749 if (!constructorId) {
750 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
751 }
752 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
753
754 // retrieve information from the exception.
755 // get method id
756 // getStackTrace returns an array of StackTraceElement
757 static jmethodID getStackTraceId = nullptr;
758 if (!getStackTraceId) {
759 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
760 "()[Ljava/lang/StackTraceElement;");
761 }
762
763 // call getStackTrace
764 JLocal<jobjectArray> stackTrace(
765 env, static_cast<jobjectArray>(
766 env->CallObjectMethod(throwable, getStackTraceId)));
767
768 if (!stackTrace) {
769 return "";
770 }
771
772 // get length of the array
773 jsize stackTraceLength = env->GetArrayLength(stackTrace);
774
775 // get toString methodId of StackTraceElement class
776 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
777 if (!stackTraceElementCls) {
778 return "";
779 }
780 static jmethodID toStringId = nullptr;
781 if (!toStringId) {
782 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
783 "()Ljava/lang/String;");
784 }
785
786 bool foundFirst = false;
787 std::string buf;
788 raw_string_ostream oss(buf);
789 for (jsize i = 0; i < stackTraceLength; i++) {
790 // add the result of toString method of each element in the result
791 JLocal<jobject> curStackTraceElement(
792 env, env->GetObjectArrayElement(stackTrace, i));
793
794 // call to string on the object
795 JLocal<jstring> stackElementString(
796 env, static_cast<jstring>(
797 env->CallObjectMethod(curStackTraceElement, toStringId)));
798
799 if (!stackElementString) {
800 return "";
801 }
802
803 // add a line to res
804 JStringRef elem(env, stackElementString);
805 if (!foundFirst) {
806 if (wpi::starts_with(elem, skipPrefix)) {
807 continue;
808 }
809 foundFirst = true;
810 }
811 oss << "\tat " << elem << '\n';
812 }
813
814 return oss.str();
815}
816
817inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
818 std::string_view excludeFuncPrefix) {
819 // create a throwable
820 static JClass throwableCls(env, "java/lang/Throwable");
821 if (!throwableCls) {
822 return "";
823 }
824 static jmethodID constructorId = nullptr;
825 if (!constructorId) {
826 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
827 }
828 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
829
830 // retrieve information from the exception.
831 // get method id
832 // getStackTrace returns an array of StackTraceElement
833 static jmethodID getStackTraceId = nullptr;
834 if (!getStackTraceId) {
835 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
836 "()[Ljava/lang/StackTraceElement;");
837 }
838
839 // call getStackTrace
840 JLocal<jobjectArray> stackTrace(
841 env, static_cast<jobjectArray>(
842 env->CallObjectMethod(throwable, getStackTraceId)));
843
844 if (!stackTrace) {
845 return "";
846 }
847
848 // get length of the array
849 jsize stackTraceLength = env->GetArrayLength(stackTrace);
850
851 // get toString methodId of StackTraceElement class
852 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
853 if (!stackTraceElementCls) {
854 return "";
855 }
856 static jmethodID toStringId = nullptr;
857 if (!toStringId) {
858 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
859 "()Ljava/lang/String;");
860 }
861
862 bool haveLoc = false;
863 std::string buf;
864 raw_string_ostream oss(buf);
865 for (jsize i = 0; i < stackTraceLength; i++) {
866 // add the result of toString method of each element in the result
867 JLocal<jobject> curStackTraceElement(
868 env, env->GetObjectArrayElement(stackTrace, i));
869
870 // call to string on the object
871 JLocal<jstring> stackElementString(
872 env, static_cast<jstring>(
873 env->CallObjectMethod(curStackTraceElement, toStringId)));
874
875 if (!stackElementString) {
876 return "";
877 }
878
879 // add a line to res
880 JStringRef elem(env, stackElementString);
881 oss << elem << '\n';
882
883 if (func) {
884 // func is caller of immediate caller (if there was one)
885 // or, if we see it, the first user function
886 if (i == 1) {
887 *func = elem.str();
888 } else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
889 !wpi::starts_with(elem, excludeFuncPrefix)) {
890 *func = elem.str();
891 haveLoc = true;
892 }
893 }
894 }
895
896 return oss.str();
897}
898
899/**
900 * Finds an exception class and keep it as a global reference.
901 *
902 * Similar to JClass, but provides Throw methods. Use with caution, as the
903 * destructor does NOT call DeleteGlobalRef due to potential shutdown issues
904 * with doing so.
905 */
906class JException : public JClass {
907 public:
908 JException() = default;
909 JException(JNIEnv* env, const char* name) : JClass(env, name) {
910 if (m_cls) {
911 m_constructor =
912 env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
913 }
914 }
915
916 void Throw(JNIEnv* env, jstring msg) {
917 jobject exception = env->NewObject(m_cls, m_constructor, msg);
918 env->Throw(static_cast<jthrowable>(exception));
919 }
920
921 void Throw(JNIEnv* env, std::string_view msg) {
922 Throw(env, MakeJString(env, msg));
923 }
924
925 explicit operator bool() const { return m_constructor; }
926
927 private:
928 jmethodID m_constructor = nullptr;
929};
930
932 const char* name;
934};
935
936} // namespace wpi::java
937
938#endif // WPIUTIL_WPI_JNI_UTIL_H_
This file defines the SmallString class.
This file defines the SmallVector class.
you may not use this file except in compliance with the License You may obtain a copy of the License at software distributed under the License is distributed on an AS IS WITHOUT WARRANTIES OR CONDITIONS OF ANY either express or implied See the License for the specific language governing permissions and limitations under the License LLVM Exceptions to the Apache License As an exception
Definition: ThirdPartyNotices.txt:289
Definition: SafeThread.h:33
Definition: SafeThread.h:124
std::string_view str() const
Explicit conversion to std::string_view.
Definition: SmallString.h:181
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
Definition: SmallVector.h:1186
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
Definition: SmallVector.h:557
void pop_back()
Definition: SmallVector.h:415
void push_back(const T &Elt)
Definition: SmallVector.h:403
size_t size() const
Definition: SmallVector.h:78
pointer data()
Return a pointer to the vector's buffer, even if empty().
Definition: SmallVector.h:271
iterator begin()
Definition: SmallVector.h:252
This class is a wrapper around std::array that does compile time size checking.
Definition: array.h:26
void SetJoinAtExit(bool joinAtExit)
Definition: SafeThread.h:106
Definition: jni_util.h:655
JCallbackManager()
Definition: jni_util.h:657
void SetFunc(JNIEnv *env, jobject func, jmethodID mid)
Definition: jni_util.h:665
void Send(Args &&... args)
Definition: jni_util.h:681
Generic callback thread implementation.
Definition: jni_util.h:645
jmethodID m_mid
Definition: jni_util.h:651
jobject m_func
Definition: jni_util.h:650
std::queue< T > m_queue
Definition: jni_util.h:649
void Main() override
Definition: jni_util.h:691
Finds a class and keeps it as a global reference.
Definition: jni_util.h:50
JClass(JNIEnv *env, const char *name)
Definition: jni_util.h:54
jclass m_cls
Definition: jni_util.h:75
void free(JNIEnv *env)
Definition: jni_util.h:63
Finds an exception class and keep it as a global reference.
Definition: jni_util.h:906
void Throw(JNIEnv *env, std::string_view msg)
Definition: jni_util.h:921
JException(JNIEnv *env, const char *name)
Definition: jni_util.h:909
void Throw(JNIEnv *env, jstring msg)
Definition: jni_util.h:916
Definition: jni_util.h:84
JGlobal(JNIEnv *env, T obj)
Definition: jni_util.h:88
void free(JNIEnv *env)
Definition: jni_util.h:92
T m_cls
Definition: jni_util.h:104
Container class for cleaning up Java local references.
Definition: jni_util.h:113
JLocal & operator=(const JLocal &)=delete
~JLocal()
Definition: jni_util.h:127
JLocal(JLocal &&oth)
Definition: jni_util.h:117
JLocal & operator=(JLocal &&oth)
Definition: jni_util.h:121
T obj()
Definition: jni_util.h:133
JLocal(JNIEnv *env, T obj)
Definition: jni_util.h:115
JLocal(const JLocal &)=delete
Definition: jni_util.h:734
static JSingletonCallbackManager< T > & GetInstance()
Definition: jni_util.h:736
Java string (jstring) reference.
Definition: jni_util.h:150
std::string_view str() const
Definition: jni_util.h:170
const char * c_str() const
Definition: jni_util.h:171
size_t size() const
Definition: jni_util.h:172
JStringRef(JNIEnv *env, jstring str)
Definition: jni_util.h:152
Base class for J*ArrayRef and CriticalJ*ArrayRef.
Definition: jni_util.h:237
JArrayRefBase(JNIEnv *env, jarray jarr)
Definition: jni_util.h:289
size_t m_size
Definition: jni_util.h:294
size_t size() const
Definition: jni_util.h:248
std::span< const T > array() const
Definition: jni_util.h:241
JArrayRefBase & operator=(const JArrayRefBase &)=delete
T & operator[](size_t i)
Definition: jni_util.h:249
T * m_elements
Definition: jni_util.h:295
JArrayRefBase(JNIEnv *env, T *elements, size_t size)
Definition: jni_util.h:275
JArrayRefBase & operator=(JArrayRefBase &&oth)
Definition: jni_util.h:264
JArrayRefBase(JNIEnv *env, jarray jarr, size_t size)
Definition: jni_util.h:282
JNIEnv * m_env
Definition: jni_util.h:292
jarray m_jarr
Definition: jni_util.h:293
const T & operator[](size_t i) const
Definition: jni_util.h:250
JArrayRefBase(const JArrayRefBase &)=delete
JArrayRefBase(JArrayRefBase &&oth)
Definition: jni_util.h:255
std::string_view str() const
Definition: jni_util.h:198
std::span< const uint8_t > uarray() const
Definition: jni_util.h:206
Definition: jni_util.h:182
A raw_ostream that writes to an std::string.
Definition: raw_ostream.h:554
std::string & str()
Returns the string's reference.
Definition: raw_ostream.h:572
basic_string_view< char > string_view
Definition: core.h:520
#define WPI_JNI_MAKEJARRAY(T, F)
Definition: jni_util.h:550
#define WPI_JNI_JARRAYREF(T, F)
Definition: jni_util.h:302
::uint8_t uint8_t
Definition: Meta.h:52
Definition: format-inl.h:32
Java Native Interface (JNI) utility functions.
Definition: jni_util.h:27
jlongArray MakeJLongArray(JNIEnv *env, const T &arr)
Definition: jni_util.h:571
jintArray MakeJIntArray(JNIEnv *env, std::span< const T > arr)
Convert a span to a jintArray.
Definition: jni_util.h:443
jstring MakeJString(JNIEnv *env, std::string_view str)
Convert a UTF8 string into a jstring.
Definition: jni_util.h:390
std::string GetJavaStackTrace(JNIEnv *env, std::string *func=nullptr, std::string_view excludeFuncPrefix={})
Gets a Java stack trace.
Definition: jni_util.h:817
jbooleanArray MakeJBooleanArray(JNIEnv *env, std::span< const int > arr)
Convert an array of integers into a jbooleanArray.
Definition: jni_util.h:508
jobjectArray MakeJStringArray(JNIEnv *env, std::span< const std::string > arr)
Convert an array of std::string into a jarray of jstring.
Definition: jni_util.h:587
jbyteArray MakeJByteArray(JNIEnv *env, std::span< const uint8_t > str)
Convert a span into a jbyteArray.
Definition: jni_util.h:492
bool convertUTF8ToUTF16String(std::string_view SrcUTF8, SmallVectorImpl< UTF16 > &DstUTF16)
Converts a UTF-8 string into a UTF-16 string with native endianness.
bool Boolean
Definition: ConvertUTF.h:115
raw_fd_ostream & errs()
This returns a reference to a raw_ostream for standard error.
bool convertUTF16ToUTF8String(std::span< const char > SrcBytes, SmallVectorImpl< char > &Out)
Converts a stream of raw bytes assumed to be UTF16 into a UTF8 std::string.
constexpr bool starts_with(std::string_view str, std::string_view prefix) noexcept
Checks if str starts with the given prefix.
Definition: StringExtras.h:232
Definition: jni_util.h:78
const char * name
Definition: jni_util.h:79
JClass * cls
Definition: jni_util.h:80
Definition: jni_util.h:931
JException * cls
Definition: jni_util.h:933
const char * name
Definition: jni_util.h:932
Definition: jni_util.h:400
static jintArray ToJava(JNIEnv *env, std::span< const T > arr)
Definition: jni_util.h:401