WPILibC++ 2023.4.3
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 <queue>
11#include <span>
12#include <string>
13#include <string_view>
14#include <type_traits>
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 typename = std::enable_if_t<sizeof(U) == sizeof(jlong) &&
224 std::is_integral_v<U>>>
225 operator std::span<const U>() const { // NOLINT
226 auto arr = static_cast<const C*>(this)->array();
227 if (arr.empty()) {
228 return {};
229 }
230 return {reinterpret_cast<const U*>(arr.data()), arr.size()};
231 }
232};
233
234/**
235 * Base class for J*ArrayRef and CriticalJ*ArrayRef
236 */
237template <typename T>
238class JArrayRefBase : public JArrayRefInner<JArrayRefBase<T>, T> {
239 public:
240 explicit operator bool() const { return this->m_elements != nullptr; }
241
242 std::span<const T> array() const {
243 if (!this->m_elements) {
244 return {};
245 }
246 return {this->m_elements, this->m_size};
247 }
248
249 size_t size() const { return this->m_size; }
250 T& operator[](size_t i) { return this->m_elements[i]; }
251 const T& operator[](size_t i) const { return this->m_elements[i]; }
252
253 JArrayRefBase(const JArrayRefBase&) = delete;
255
257 : m_env(oth.m_env),
258 m_jarr(oth.m_jarr),
259 m_size(oth.m_size),
261 oth.m_jarr = nullptr;
262 oth.m_elements = nullptr;
263 }
264
266 this->m_env = oth.m_env;
267 this->m_jarr = oth.m_jarr;
268 this->m_size = oth.m_size;
269 this->m_elements = oth.m_elements;
270 oth.m_jarr = nullptr;
271 oth.m_elements = nullptr;
272 return *this;
273 }
274
275 protected:
276 JArrayRefBase(JNIEnv* env, T* elements, size_t size) {
277 this->m_env = env;
278 this->m_jarr = nullptr;
279 this->m_size = size;
280 this->m_elements = elements;
281 }
282
283 JArrayRefBase(JNIEnv* env, jarray jarr, size_t size) {
284 this->m_env = env;
285 this->m_jarr = jarr;
286 this->m_size = size;
287 this->m_elements = nullptr;
288 }
289
290 JArrayRefBase(JNIEnv* env, jarray jarr)
291 : JArrayRefBase(env, jarr, jarr ? env->GetArrayLength(jarr) : 0) {}
292
293 JNIEnv* m_env;
294 jarray m_jarr = nullptr;
295 size_t m_size;
297};
298
299} // namespace detail
300
301// Java array / DirectBuffer reference.
302
303#define WPI_JNI_JARRAYREF(T, F) \
304 class J##F##ArrayRef : public detail::JArrayRefBase<T> { \
305 public: \
306 J##F##ArrayRef(JNIEnv* env, jobject bb, int len) \
307 : detail::JArrayRefBase<T>( \
308 env, \
309 static_cast<T*>(bb ? env->GetDirectBufferAddress(bb) : nullptr), \
310 len) { \
311 if (!bb) { \
312 errs() << "JArrayRef was passed a null pointer at \n" \
313 << GetJavaStackTrace(env); \
314 } \
315 } \
316 J##F##ArrayRef(JNIEnv* env, T##Array jarr, int len) \
317 : detail::JArrayRefBase<T>(env, jarr, len) { \
318 if (jarr) { \
319 m_elements = env->Get##F##ArrayElements(jarr, nullptr); \
320 } else { \
321 errs() << "JArrayRef was passed a null pointer at \n" \
322 << GetJavaStackTrace(env); \
323 } \
324 } \
325 J##F##ArrayRef(JNIEnv* env, T##Array jarr) \
326 : detail::JArrayRefBase<T>(env, jarr) { \
327 if (jarr) { \
328 m_elements = env->Get##F##ArrayElements(jarr, nullptr); \
329 } else { \
330 errs() << "JArrayRef was passed a null pointer at \n" \
331 << GetJavaStackTrace(env); \
332 } \
333 } \
334 ~J##F##ArrayRef() { \
335 if (m_jarr && m_elements) { \
336 m_env->Release##F##ArrayElements(static_cast<T##Array>(m_jarr), \
337 m_elements, JNI_ABORT); \
338 } \
339 } \
340 }; \
341 \
342 class CriticalJ##F##ArrayRef : public detail::JArrayRefBase<T> { \
343 public: \
344 CriticalJ##F##ArrayRef(JNIEnv* env, T##Array jarr, int len) \
345 : detail::JArrayRefBase<T>(env, jarr, len) { \
346 if (jarr) { \
347 m_elements = \
348 static_cast<T*>(env->GetPrimitiveArrayCritical(jarr, nullptr)); \
349 } else { \
350 errs() << "JArrayRef was passed a null pointer at \n" \
351 << GetJavaStackTrace(env); \
352 } \
353 } \
354 CriticalJ##F##ArrayRef(JNIEnv* env, T##Array jarr) \
355 : detail::JArrayRefBase<T>(env, jarr) { \
356 if (jarr) { \
357 m_elements = \
358 static_cast<T*>(env->GetPrimitiveArrayCritical(jarr, nullptr)); \
359 } else { \
360 errs() << "JArrayRef was passed a null pointer at \n" \
361 << GetJavaStackTrace(env); \
362 } \
363 } \
364 ~CriticalJ##F##ArrayRef() { \
365 if (m_jarr && m_elements) { \
366 m_env->ReleasePrimitiveArrayCritical(m_jarr, m_elements, JNI_ABORT); \
367 } \
368 } \
369 };
370
371WPI_JNI_JARRAYREF(jboolean, Boolean)
372WPI_JNI_JARRAYREF(jbyte, Byte)
373WPI_JNI_JARRAYREF(jshort, Short)
374WPI_JNI_JARRAYREF(jint, Int)
375WPI_JNI_JARRAYREF(jlong, Long)
376WPI_JNI_JARRAYREF(jfloat, Float)
377WPI_JNI_JARRAYREF(jdouble, Double)
378
379#undef WPI_JNI_JARRAYREF
380
381//
382// Conversions from C++ to Java objects
383//
384
385/**
386 * Convert a UTF8 string into a jstring.
387 *
388 * @param env JRE environment.
389 * @param str String to convert.
390 */
391inline jstring MakeJString(JNIEnv* env, std::string_view str) {
393 convertUTF8ToUTF16String(str, chars);
394 return env->NewString(chars.begin(), chars.size());
395}
396
397// details for MakeJIntArray
398namespace detail {
399
400/**
401 * Slow path (get primitive array and set individual elements).
402 *
403 * This is used if the input type is not an integer of the same size (note
404 * signed/unsigned is ignored).
405 */
406template <typename T,
407 bool = (std::is_integral<T>::value && sizeof(jint) == sizeof(T))>
409 static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
410 jintArray jarr = env->NewIntArray(arr.size());
411 if (!jarr) {
412 return nullptr;
413 }
414 jint* elements =
415 static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
416 if (!elements) {
417 return nullptr;
418 }
419 for (size_t i = 0; i < arr.size(); ++i) {
420 elements[i] = static_cast<jint>(arr[i]);
421 }
422 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
423 return jarr;
424 }
425};
426
427/**
428 * Fast path (use SetIntArrayRegion).
429 */
430template <typename T>
432 static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
433 jintArray jarr = env->NewIntArray(arr.size());
434 if (!jarr) {
435 return nullptr;
436 }
437 env->SetIntArrayRegion(jarr, 0, arr.size(),
438 reinterpret_cast<const jint*>(arr.data()));
439 return jarr;
440 }
441};
442
443} // namespace detail
444
445/**
446 * Convert a span to a jintArray.
447 *
448 * @param env JRE environment.
449 * @param arr Span to convert.
450 */
451template <typename T>
452inline jintArray MakeJIntArray(JNIEnv* env, std::span<const T> arr) {
453 return detail::ConvertIntArray<T>::ToJava(env, arr);
454}
455
456/**
457 * Convert a span to a jintArray.
458 *
459 * @param env JRE environment.
460 * @param arr Span to convert.
461 */
462template <typename T>
463inline jintArray MakeJIntArray(JNIEnv* env, std::span<T> arr) {
464 return detail::ConvertIntArray<T>::ToJava(env, arr);
465}
466
467/**
468 * Convert a SmallVector to a jintArray.
469 *
470 * This is required in addition to ArrayRef because template resolution occurs
471 * prior to implicit conversions.
472 *
473 * @param env JRE environment.
474 * @param arr SmallVector to convert.
475 */
476template <typename T>
477inline jintArray MakeJIntArray(JNIEnv* env, const SmallVectorImpl<T>& arr) {
478 return detail::ConvertIntArray<T>::ToJava(env, arr);
479}
480
481/**
482 * Convert a std::vector to a jintArray.
483 *
484 * This is required in addition to ArrayRef because template resolution occurs
485 * prior to implicit conversions.
486 *
487 * @param env JRE environment.
488 * @param arr SmallVector to convert.
489 */
490template <typename T>
491inline jintArray MakeJIntArray(JNIEnv* env, const std::vector<T>& arr) {
492 return detail::ConvertIntArray<T>::ToJava(env, arr);
493}
494
495/**
496 * Convert a span into a jbyteArray.
497 *
498 * @param env JRE environment.
499 * @param str span to convert.
500 */
501inline jbyteArray MakeJByteArray(JNIEnv* env, std::span<const uint8_t> str) {
502 jbyteArray jarr = env->NewByteArray(str.size());
503 if (!jarr) {
504 return nullptr;
505 }
506 env->SetByteArrayRegion(jarr, 0, str.size(),
507 reinterpret_cast<const jbyte*>(str.data()));
508 return jarr;
509}
510
511/**
512 * Convert an array of integers into a jbooleanArray.
513 *
514 * @param env JRE environment.
515 * @param arr Array to convert.
516 */
517inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const int> arr) {
518 jbooleanArray jarr = env->NewBooleanArray(arr.size());
519 if (!jarr) {
520 return nullptr;
521 }
522 jboolean* elements =
523 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
524 if (!elements) {
525 return nullptr;
526 }
527 for (size_t i = 0; i < arr.size(); ++i) {
528 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
529 }
530 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
531 return jarr;
532}
533
534/**
535 * Convert an array of booleans into a jbooleanArray.
536 *
537 * @param env JRE environment.
538 * @param arr Array to convert.
539 */
540inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const bool> arr) {
541 jbooleanArray jarr = env->NewBooleanArray(arr.size());
542 if (!jarr) {
543 return nullptr;
544 }
545 jboolean* elements =
546 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
547 if (!elements) {
548 return nullptr;
549 }
550 for (size_t i = 0; i < arr.size(); ++i) {
551 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
552 }
553 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
554 return jarr;
555}
556
557// Other MakeJ*Array conversions.
558
559#define WPI_JNI_MAKEJARRAY(T, F) \
560 inline T##Array MakeJ##F##Array(JNIEnv* env, std::span<const T> arr) { \
561 T##Array jarr = env->New##F##Array(arr.size()); \
562 if (!jarr) { \
563 return nullptr; \
564 } \
565 env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data()); \
566 return jarr; \
567 }
568
570WPI_JNI_MAKEJARRAY(jbyte, Byte)
571WPI_JNI_MAKEJARRAY(jshort, Short)
572WPI_JNI_MAKEJARRAY(jfloat, Float)
573WPI_JNI_MAKEJARRAY(jdouble, Double)
574
575#undef WPI_JNI_MAKEJARRAY
576
577template <class T, typename = std::enable_if_t<
578 sizeof(typename T::value_type) == sizeof(jlong) &&
579 std::is_integral_v<typename T::value_type>>>
580inline jlongArray MakeJLongArray(JNIEnv* env, const T& arr) {
581 jlongArray jarr = env->NewLongArray(arr.size());
582 if (!jarr) {
583 return nullptr;
584 }
585 env->SetLongArrayRegion(jarr, 0, arr.size(),
586 reinterpret_cast<const jlong*>(arr.data()));
587 return jarr;
588}
589
590/**
591 * Convert an array of std::string into a jarray of jstring.
592 *
593 * @param env JRE environment.
594 * @param arr Array to convert.
595 */
596inline jobjectArray MakeJStringArray(JNIEnv* env,
597 std::span<const std::string> arr) {
598 static JClass stringCls{env, "java/lang/String"};
599 if (!stringCls) {
600 return nullptr;
601 }
602 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
603 if (!jarr) {
604 return nullptr;
605 }
606 for (size_t i = 0; i < arr.size(); ++i) {
607 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
608 env->SetObjectArrayElement(jarr, i, elem.obj());
609 }
610 return jarr;
611}
612
613/**
614 * Convert an array of std::string into a jarray of jstring.
615 *
616 * @param env JRE environment.
617 * @param arr Array to convert.
618 */
619inline jobjectArray MakeJStringArray(JNIEnv* env,
620 std::span<std::string_view> arr) {
621 static JClass stringCls{env, "java/lang/String"};
622 if (!stringCls) {
623 return nullptr;
624 }
625 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
626 if (!jarr) {
627 return nullptr;
628 }
629 for (size_t i = 0; i < arr.size(); ++i) {
630 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
631 env->SetObjectArrayElement(jarr, i, elem.obj());
632 }
633 return jarr;
634}
635
636/**
637 * Generic callback thread implementation.
638 *
639 * JNI's AttachCurrentThread() creates a Java Thread object on every
640 * invocation, which is both time inefficient and causes issues with Eclipse
641 * (which tries to keep a thread list up-to-date and thus gets swamped).
642 *
643 * Instead, this class attaches just once. When a hardware notification
644 * occurs, a condition variable wakes up this thread and this thread actually
645 * makes the call into Java.
646 *
647 * The template parameter T is the message being passed to the callback, but
648 * also needs to provide the following functions:
649 * static JavaVM* GetJVM();
650 * static const char* GetName();
651 * void CallJava(JNIEnv *env, jobject func, jmethodID mid);
652 */
653template <typename T>
655 public:
656 void Main() override;
657
658 std::queue<T> m_queue;
659 jobject m_func = nullptr;
660 jmethodID m_mid;
661};
662
663template <typename T>
664class JCallbackManager : public SafeThreadOwner<JCallbackThread<T>> {
665 public:
666 JCallbackManager() { this->SetJoinAtExit(false); }
667 void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
668
669 template <typename... Args>
670 void Send(Args&&... args);
671};
672
673template <typename T>
674void JCallbackManager<T>::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
675 auto thr = this->GetThread();
676 if (!thr) {
677 return;
678 }
679 // free global reference
680 if (thr->m_func) {
681 env->DeleteGlobalRef(thr->m_func);
682 }
683 // create global reference
684 thr->m_func = env->NewGlobalRef(func);
685 thr->m_mid = mid;
686}
687
688template <typename T>
689template <typename... Args>
690void JCallbackManager<T>::Send(Args&&... args) {
691 auto thr = this->GetThread();
692 if (!thr) {
693 return;
694 }
695 thr->m_queue.emplace(std::forward<Args>(args)...);
696 thr->m_cond.notify_one();
697}
698
699template <typename T>
701 JNIEnv* env;
702 JavaVMAttachArgs args;
703 args.version = JNI_VERSION_1_2;
704 args.name = const_cast<char*>(T::GetName());
705 args.group = nullptr;
706 jint rs = T::GetJVM()->AttachCurrentThreadAsDaemon(
707 reinterpret_cast<void**>(&env), &args);
708 if (rs != JNI_OK) {
709 return;
710 }
711
712 std::unique_lock lock(m_mutex);
713 while (m_active) {
714 m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
715 if (!m_active) {
716 break;
717 }
718 while (!m_queue.empty()) {
719 if (!m_active) {
720 break;
721 }
722 auto item = std::move(m_queue.front());
723 m_queue.pop();
724 auto func = m_func;
725 auto mid = m_mid;
726 lock.unlock(); // don't hold mutex during callback execution
727 item.CallJava(env, func, mid);
728 if (env->ExceptionCheck()) {
729 env->ExceptionDescribe();
730 env->ExceptionClear();
731 }
732 lock.lock();
733 }
734 }
735
736 JavaVM* jvm = T::GetJVM();
737 if (jvm) {
738 jvm->DetachCurrentThread();
739 }
740}
741
742template <typename T>
744 public:
746 static JSingletonCallbackManager<T> instance;
747 return instance;
748 }
749};
750
751inline std::string GetJavaStackTrace(JNIEnv* env, std::string_view skipPrefix) {
752 // create a throwable
753 static JClass throwableCls(env, "java/lang/Throwable");
754 if (!throwableCls) {
755 return "";
756 }
757 static jmethodID constructorId = nullptr;
758 if (!constructorId) {
759 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
760 }
761 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
762
763 // retrieve information from the exception.
764 // get method id
765 // getStackTrace returns an array of StackTraceElement
766 static jmethodID getStackTraceId = nullptr;
767 if (!getStackTraceId) {
768 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
769 "()[Ljava/lang/StackTraceElement;");
770 }
771
772 // call getStackTrace
773 JLocal<jobjectArray> stackTrace(
774 env, static_cast<jobjectArray>(
775 env->CallObjectMethod(throwable, getStackTraceId)));
776
777 if (!stackTrace) {
778 return "";
779 }
780
781 // get length of the array
782 jsize stackTraceLength = env->GetArrayLength(stackTrace);
783
784 // get toString methodId of StackTraceElement class
785 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
786 if (!stackTraceElementCls) {
787 return "";
788 }
789 static jmethodID toStringId = nullptr;
790 if (!toStringId) {
791 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
792 "()Ljava/lang/String;");
793 }
794
795 bool foundFirst = false;
796 std::string buf;
797 raw_string_ostream oss(buf);
798 for (jsize i = 0; i < stackTraceLength; i++) {
799 // add the result of toString method of each element in the result
800 JLocal<jobject> curStackTraceElement(
801 env, env->GetObjectArrayElement(stackTrace, i));
802
803 // call to string on the object
804 JLocal<jstring> stackElementString(
805 env, static_cast<jstring>(
806 env->CallObjectMethod(curStackTraceElement, toStringId)));
807
808 if (!stackElementString) {
809 return "";
810 }
811
812 // add a line to res
813 JStringRef elem(env, stackElementString);
814 if (!foundFirst) {
815 if (wpi::starts_with(elem, skipPrefix)) {
816 continue;
817 }
818 foundFirst = true;
819 }
820 oss << "\tat " << elem << '\n';
821 }
822
823 return oss.str();
824}
825
826inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
827 std::string_view excludeFuncPrefix) {
828 // create a throwable
829 static JClass throwableCls(env, "java/lang/Throwable");
830 if (!throwableCls) {
831 return "";
832 }
833 static jmethodID constructorId = nullptr;
834 if (!constructorId) {
835 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
836 }
837 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
838
839 // retrieve information from the exception.
840 // get method id
841 // getStackTrace returns an array of StackTraceElement
842 static jmethodID getStackTraceId = nullptr;
843 if (!getStackTraceId) {
844 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
845 "()[Ljava/lang/StackTraceElement;");
846 }
847
848 // call getStackTrace
849 JLocal<jobjectArray> stackTrace(
850 env, static_cast<jobjectArray>(
851 env->CallObjectMethod(throwable, getStackTraceId)));
852
853 if (!stackTrace) {
854 return "";
855 }
856
857 // get length of the array
858 jsize stackTraceLength = env->GetArrayLength(stackTrace);
859
860 // get toString methodId of StackTraceElement class
861 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
862 if (!stackTraceElementCls) {
863 return "";
864 }
865 static jmethodID toStringId = nullptr;
866 if (!toStringId) {
867 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
868 "()Ljava/lang/String;");
869 }
870
871 bool haveLoc = false;
872 std::string buf;
873 raw_string_ostream oss(buf);
874 for (jsize i = 0; i < stackTraceLength; i++) {
875 // add the result of toString method of each element in the result
876 JLocal<jobject> curStackTraceElement(
877 env, env->GetObjectArrayElement(stackTrace, i));
878
879 // call to string on the object
880 JLocal<jstring> stackElementString(
881 env, static_cast<jstring>(
882 env->CallObjectMethod(curStackTraceElement, toStringId)));
883
884 if (!stackElementString) {
885 return "";
886 }
887
888 // add a line to res
889 JStringRef elem(env, stackElementString);
890 oss << elem << '\n';
891
892 if (func) {
893 // func is caller of immediate caller (if there was one)
894 // or, if we see it, the first user function
895 if (i == 1) {
896 *func = elem.str();
897 } else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
898 !wpi::starts_with(elem, excludeFuncPrefix)) {
899 *func = elem.str();
900 haveLoc = true;
901 }
902 }
903 }
904
905 return oss.str();
906}
907
908/**
909 * Finds an exception class and keep it as a global reference.
910 *
911 * Similar to JClass, but provides Throw methods. Use with caution, as the
912 * destructor does NOT call DeleteGlobalRef due to potential shutdown issues
913 * with doing so.
914 */
915class JException : public JClass {
916 public:
917 JException() = default;
918 JException(JNIEnv* env, const char* name) : JClass(env, name) {
919 if (m_cls) {
920 m_constructor =
921 env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
922 }
923 }
924
925 void Throw(JNIEnv* env, jstring msg) {
926 jobject exception = env->NewObject(m_cls, m_constructor, msg);
927 env->Throw(static_cast<jthrowable>(exception));
928 }
929
930 void Throw(JNIEnv* env, std::string_view msg) {
931 Throw(env, MakeJString(env, msg));
932 }
933
934 explicit operator bool() const { return m_constructor; }
935
936 private:
937 jmethodID m_constructor = nullptr;
938};
939
941 const char* name;
943};
944
945} // namespace wpi::java
946
947#endif // WPIUTIL_WPI_JNI_UTIL_H_
This file defines the SmallString 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:292
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:25
void SetJoinAtExit(bool joinAtExit)
Definition: SafeThread.h:106
Definition: jni_util.h:664
JCallbackManager()
Definition: jni_util.h:666
void SetFunc(JNIEnv *env, jobject func, jmethodID mid)
Definition: jni_util.h:674
void Send(Args &&... args)
Definition: jni_util.h:690
Generic callback thread implementation.
Definition: jni_util.h:654
jmethodID m_mid
Definition: jni_util.h:660
jobject m_func
Definition: jni_util.h:659
std::queue< T > m_queue
Definition: jni_util.h:658
void Main() override
Definition: jni_util.h:700
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:915
void Throw(JNIEnv *env, std::string_view msg)
Definition: jni_util.h:930
JException(JNIEnv *env, const char *name)
Definition: jni_util.h:918
void Throw(JNIEnv *env, jstring msg)
Definition: jni_util.h:925
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:743
static JSingletonCallbackManager< T > & GetInstance()
Definition: jni_util.h:745
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:238
JArrayRefBase(JNIEnv *env, jarray jarr)
Definition: jni_util.h:290
size_t m_size
Definition: jni_util.h:295
size_t size() const
Definition: jni_util.h:249
std::span< const T > array() const
Definition: jni_util.h:242
JArrayRefBase & operator=(const JArrayRefBase &)=delete
T & operator[](size_t i)
Definition: jni_util.h:250
T * m_elements
Definition: jni_util.h:296
JArrayRefBase(JNIEnv *env, T *elements, size_t size)
Definition: jni_util.h:276
JArrayRefBase & operator=(JArrayRefBase &&oth)
Definition: jni_util.h:265
JArrayRefBase(JNIEnv *env, jarray jarr, size_t size)
Definition: jni_util.h:283
JNIEnv * m_env
Definition: jni_util.h:293
jarray m_jarr
Definition: jni_util.h:294
const T & operator[](size_t i) const
Definition: jni_util.h:251
JArrayRefBase(const JArrayRefBase &)=delete
JArrayRefBase(JArrayRefBase &&oth)
Definition: jni_util.h:256
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
typename std::enable_if< B, T >::type enable_if_t
Definition: core.h:298
basic_string_view< char > string_view
Definition: core.h:520
#define WPI_JNI_MAKEJARRAY(T, F)
Definition: jni_util.h:559
#define WPI_JNI_JARRAYREF(T, F)
Definition: jni_util.h:303
::uint8_t uint8_t
Definition: Meta.h:52
Definition: chrono.h:303
Java Native Interface (JNI) utility functions.
Definition: jni_util.h:27
jintArray MakeJIntArray(JNIEnv *env, std::span< const T > arr)
Convert a span to a jintArray.
Definition: jni_util.h:452
jstring MakeJString(JNIEnv *env, std::string_view str)
Convert a UTF8 string into a jstring.
Definition: jni_util.h:391
jlongArray MakeJLongArray(JNIEnv *env, const T &arr)
Definition: jni_util.h:580
std::string GetJavaStackTrace(JNIEnv *env, std::string *func=nullptr, std::string_view excludeFuncPrefix={})
Gets a Java stack trace.
Definition: jni_util.h:826
jbooleanArray MakeJBooleanArray(JNIEnv *env, std::span< const int > arr)
Convert an array of integers into a jbooleanArray.
Definition: jni_util.h:517
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:596
jbyteArray MakeJByteArray(JNIEnv *env, std::span< const uint8_t > str)
Convert a span into a jbyteArray.
Definition: jni_util.h:501
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:940
JException * cls
Definition: jni_util.h:942
const char * name
Definition: jni_util.h:941
Fast path (use SetIntArrayRegion).
Definition: jni_util.h:431
static jintArray ToJava(JNIEnv *env, std::span< const T > arr)
Definition: jni_util.h:432
Slow path (get primitive array and set individual elements).
Definition: jni_util.h:408
static jintArray ToJava(JNIEnv *env, std::span< const T > arr)
Definition: jni_util.h:409