WPILibC++  unspecified
jni_util.h
1 /*----------------------------------------------------------------------------*/
2 /* Copyright (c) FIRST 2016. All Rights Reserved. */
3 /* Open Source Software - may be modified and shared by FRC teams. The code */
4 /* must be accompanied by the FIRST BSD license file in the root directory of */
5 /* the project. */
6 /*----------------------------------------------------------------------------*/
7 
8 #ifndef WPIUTIL_SUPPORT_JNI_UTIL_H_
9 #define WPIUTIL_SUPPORT_JNI_UTIL_H_
10 
11 #include <mutex>
12 #include <string>
13 #include <type_traits>
14 #include <queue>
15 #include <vector>
16 
17 #include <jni.h>
18 
19 #include "llvm/ArrayRef.h"
20 #include "llvm/ConvertUTF.h"
21 #include "llvm/raw_ostream.h"
22 #include "llvm/SmallString.h"
23 #include "llvm/SmallVector.h"
24 #include "llvm/StringRef.h"
25 #include "support/atomic_static.h"
26 #include "support/deprecated.h"
27 #include "support/SafeThread.h"
28 
29 namespace wpi {
30 namespace java {
31 
32 // Gets a Java stack trace. Also provides the last function
33 // in the stack trace not starting with excludeFuncPrefix (useful for e.g.
34 // finding the first user call to a series of library functions).
35 std::string GetJavaStackTrace(
36  JNIEnv* env, std::string* func = nullptr,
37  llvm::StringRef excludeFuncPrefix = llvm::StringRef());
38 
39 // Shim for backwards compatibility
40 template<const char* excludeFuncPrefix>
41 WPI_DEPRECATED("use StringRef function instead")
42 std::string GetJavaStackTrace(JNIEnv* env, std::string* func) {
43  return GetJavaStackTrace(env, func, excludeFuncPrefix == nullptr
44  ? llvm::StringRef()
45  : excludeFuncPrefix);
46 }
47 
48 // Finds a class and keep it as a global reference.
49 // Use with caution, as the destructor does NOT call DeleteGlobalRef due
50 // to potential shutdown issues with doing so.
51 class JClass {
52  public:
53  JClass() = default;
54 
55  JClass(JNIEnv* env, const char* name) {
56  jclass local = env->FindClass(name);
57  if (!local) return;
58  m_cls = static_cast<jclass>(env->NewGlobalRef(local));
59  env->DeleteLocalRef(local);
60  }
61 
62  void free(JNIEnv *env) {
63  if (m_cls) env->DeleteGlobalRef(m_cls);
64  m_cls = nullptr;
65  }
66 
67  explicit operator bool() const { return m_cls; }
68 
69  operator jclass() const { return m_cls; }
70 
71  protected:
72  jclass m_cls = nullptr;
73 };
74 
75 // Container class for cleaning up Java local references.
76 // The destructor calls DeleteLocalRef.
77 template <typename T>
78 class JLocal {
79  public:
80  JLocal(JNIEnv *env, T obj) : m_env(env), m_obj(obj) {}
81  JLocal(const JLocal&) = delete;
82  JLocal(JLocal&& oth) : m_env(oth.m_env), m_obj(oth.m_obj) {
83  oth.m_obj = nullptr;
84  }
85  JLocal& operator=(const JLocal&) = delete;
86  JLocal& operator=(JLocal&& oth) {
87  m_env = oth.m_env;
88  m_obj = oth.m_obj;
89  oth.m_obj = nullptr;
90  return *this;
91  }
92  ~JLocal() {
93  if (m_obj) m_env->DeleteLocalRef(m_obj);
94  }
95  operator T() { return m_obj; }
96  T obj() { return m_obj; }
97 
98  private:
99  JNIEnv *m_env;
100  T m_obj;
101 };
102 
103 //
104 // Conversions from Java objects to C++
105 //
106 
107 // Java string (jstring) reference. The string is provided as UTF8.
108 // This is not actually a reference, as it makes a copy of the string
109 // characters, but it's named this way for consistency.
110 class JStringRef {
111  public:
112  JStringRef(JNIEnv *env, jstring str) {
113  if (str) {
114  jsize size = env->GetStringLength(str);
115  const jchar *chars = env->GetStringCritical(str, nullptr);
116  if (chars) {
117  llvm::convertUTF16ToUTF8String(llvm::makeArrayRef(chars, size), m_str);
118  env->ReleaseStringCritical(str, chars);
119  }
120  } else {
121  llvm::errs() << "JStringRef was passed a null pointer at \n"
122  << GetJavaStackTrace(env);
123  }
124  // Ensure str is null-terminated.
125  m_str.push_back('\0');
126  m_str.pop_back();
127  }
128 
129  operator llvm::StringRef() const { return m_str; }
130  llvm::StringRef str() const { return m_str; }
131  const char* c_str() const { return m_str.data(); }
132  size_t size() const { return m_str.size(); }
133 
134  private:
136 };
137 
138 // Details for J*ArrayRef and CriticalJ*ArrayRef
139 namespace detail {
140 
141 template <typename C, typename T>
142 class JArrayRefInner {};
143 
144 // Specialization of JArrayRefBase to provide StringRef conversion.
145 template <typename C>
146 class JArrayRefInner<C, jbyte> {
147  public:
148  operator llvm::StringRef() const { return str(); }
149 
150  llvm::StringRef str() const {
151  auto arr = static_cast<const C *>(this)->array();
152  if (arr.empty()) return llvm::StringRef{};
153  return llvm::StringRef{reinterpret_cast<const char *>(arr.data()),
154  arr.size()};
155  }
156 };
157 
158 // Base class for J*ArrayRef and CriticalJ*ArrayRef
159 template <typename T>
160 class JArrayRefBase : public JArrayRefInner<JArrayRefBase<T>, T> {
161  public:
162  explicit operator bool() const { return this->m_elements != nullptr; }
163 
164  operator llvm::ArrayRef<T>() const { return array(); }
165 
166  llvm::ArrayRef<T> array() const {
167  if (!this->m_elements) return llvm::ArrayRef<T>{};
168  return llvm::ArrayRef<T>{this->m_elements, this->m_size};
169  }
170 
171  JArrayRefBase(const JArrayRefBase&) = delete;
172  JArrayRefBase& operator=(const JArrayRefBase&) = delete;
173 
175  : m_env(oth.m_env),
176  m_jarr(oth.m_jarr),
177  m_size(oth.m_size),
178  m_elements(oth.m_elements) {
179  oth.m_jarr = nullptr;
180  oth.m_elements = nullptr;
181  }
182 
183  JArrayRefBase& operator=(JArrayRefBase&& oth) {
184  this->m_env = oth.m_env;
185  this->m_jarr = oth.m_jarr;
186  this->m_size = oth.m_size;
187  this->m_elements = oth.m_elements;
188  oth.m_jarr = nullptr;
189  oth.m_elements = nullptr;
190  }
191 
192  protected:
193  JArrayRefBase(JNIEnv *env, T* elements, size_t size) {
194  this->m_env = env;
195  this->m_jarr = nullptr;
196  this->m_size = size;
197  this->m_elements = elements;
198  }
199 
200  JArrayRefBase(JNIEnv *env, jarray jarr) {
201  this->m_env = env;
202  this->m_jarr = jarr;
203  this->m_size = jarr ? env->GetArrayLength(jarr) : 0;
204  this->m_elements = nullptr;
205  }
206 
207  JNIEnv *m_env;
208  jarray m_jarr = nullptr;
209  size_t m_size;
210  T *m_elements;
211 };
212 
213 } // namespace detail
214 
215 // Java array / DirectBuffer reference.
216 
217 #define WPI_JNI_JARRAYREF(T, F) \
218  class J##F##ArrayRef : public detail::JArrayRefBase<T> { \
219  public: \
220  J##F##ArrayRef(JNIEnv* env, jobject bb, int len) \
221  : detail::JArrayRefBase<T>( \
222  env, \
223  static_cast<T*>(bb ? env->GetDirectBufferAddress(bb) : nullptr), \
224  len) { \
225  if (!bb) \
226  llvm::errs() << "JArrayRef was passed a null pointer at \n" \
227  << GetJavaStackTrace(env); \
228  } \
229  J##F##ArrayRef(JNIEnv* env, T##Array jarr) \
230  : detail::JArrayRefBase<T>(env, jarr) { \
231  if (jarr) \
232  m_elements = env->Get##F##ArrayElements(jarr, nullptr); \
233  else \
234  llvm::errs() << "JArrayRef was passed a null pointer at \n" \
235  << GetJavaStackTrace(env); \
236  } \
237  ~J##F##ArrayRef() { \
238  if (m_jarr && m_elements) \
239  m_env->Release##F##ArrayElements(static_cast<T##Array>(m_jarr), \
240  m_elements, JNI_ABORT); \
241  } \
242  }; \
243  \
244  class CriticalJ##F##ArrayRef : public detail::JArrayRefBase<T> { \
245  public: \
246  CriticalJ##F##ArrayRef(JNIEnv* env, T##Array jarr) \
247  : detail::JArrayRefBase<T>(env, jarr) { \
248  if (jarr) \
249  m_elements = \
250  static_cast<T*>(env->GetPrimitiveArrayCritical(jarr, nullptr)); \
251  else \
252  llvm::errs() << "JArrayRef was passed a null pointer at \n" \
253  << GetJavaStackTrace(env); \
254  } \
255  ~CriticalJ##F##ArrayRef() { \
256  if (m_jarr && m_elements) \
257  m_env->ReleasePrimitiveArrayCritical(m_jarr, m_elements, JNI_ABORT); \
258  } \
259  };
260 
261 WPI_JNI_JARRAYREF(jboolean, Boolean)
262 WPI_JNI_JARRAYREF(jbyte, Byte)
263 WPI_JNI_JARRAYREF(jshort, Short)
264 WPI_JNI_JARRAYREF(jint, Int)
265 WPI_JNI_JARRAYREF(jlong, Long)
266 WPI_JNI_JARRAYREF(jfloat, Float)
267 WPI_JNI_JARRAYREF(jdouble, Double)
268 
269 #undef WPI_JNI_JARRAYREF
270 
271 //
272 // Conversions from C++ to Java objects
273 //
274 
275 // Convert a UTF8 string into a jstring.
276 inline jstring MakeJString(JNIEnv *env, llvm::StringRef str) {
278  llvm::convertUTF8ToUTF16String(str, chars);
279  return env->NewString(chars.begin(), chars.size());
280 }
281 
282 // details for MakeJIntArray
283 namespace detail {
284 
285 // Slow path (get primitive array and set individual elements). This
286 // is used if the input type is not an integer of the same size (note
287 // signed/unsigned is ignored).
288 template <typename T,
289  bool = (std::is_integral<T>::value && sizeof(jint) == sizeof(T))>
291  static jintArray ToJava(JNIEnv *env, llvm::ArrayRef<T> arr) {
292  jintArray jarr = env->NewIntArray(arr.size());
293  if (!jarr) return nullptr;
294  jint *elements =
295  static_cast<jint *>(env->GetPrimitiveArrayCritical(jarr, nullptr));
296  if (!elements) return nullptr;
297  for (size_t i = 0; i < arr.size(); ++i)
298  elements[i] = static_cast<jint>(arr[i]);
299  env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
300  return jarr;
301  }
302 };
303 
304 // Fast path (use SetIntArrayRegion)
305 template <typename T>
307  static jintArray ToJava(JNIEnv *env, llvm::ArrayRef<T> arr) {
308  jintArray jarr = env->NewIntArray(arr.size());
309  if (!jarr) return nullptr;
310  env->SetIntArrayRegion(jarr, 0, arr.size(),
311  reinterpret_cast<const jint*>(arr.data()));
312  return jarr;
313  }
314 };
315 
316 } // namespace detail
317 
318 // Convert an ArrayRef to a jintArray.
319 template <typename T>
320 inline jintArray MakeJIntArray(JNIEnv *env, llvm::ArrayRef<T> arr) {
321  return detail::ConvertIntArray<T>::ToJava(env, arr);
322 }
323 
324 // Convert a SmallVector to a jintArray. This is required in addition to
325 // ArrayRef because template resolution occurs prior to implicit conversions.
326 template <typename T>
327 inline jintArray MakeJIntArray(JNIEnv *env,
328  const llvm::SmallVectorImpl<T> &arr) {
329  return detail::ConvertIntArray<T>::ToJava(env, arr);
330 }
331 
332 // Convert a std::vector to a jintArray. This is required in addition to
333 // ArrayRef because template resolution occurs prior to implicit conversions.
334 template <typename T>
335 inline jintArray MakeJIntArray(JNIEnv *env, const std::vector<T> &arr) {
336  return detail::ConvertIntArray<T>::ToJava(env, arr);
337 }
338 
339 // Convert a StringRef into a jbyteArray.
340 inline jbyteArray MakeJByteArray(JNIEnv *env, llvm::StringRef str) {
341  jbyteArray jarr = env->NewByteArray(str.size());
342  if (!jarr) return nullptr;
343  env->SetByteArrayRegion(jarr, 0, str.size(),
344  reinterpret_cast<const jbyte *>(str.data()));
345  return jarr;
346 }
347 
348 // Convert an array of integers into a jbooleanArray.
349 inline jbooleanArray MakeJBooleanArray(JNIEnv *env, llvm::ArrayRef<int> arr)
350 {
351  jbooleanArray jarr = env->NewBooleanArray(arr.size());
352  if (!jarr) return nullptr;
353  jboolean *elements =
354  static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
355  if (!elements) return nullptr;
356  for (size_t i = 0; i < arr.size(); ++i)
357  elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
358  env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
359  return jarr;
360 }
361 
362 // Convert an array of booleans into a jbooleanArray.
363 inline jbooleanArray MakeJBooleanArray(JNIEnv *env, llvm::ArrayRef<bool> arr)
364 {
365  jbooleanArray jarr = env->NewBooleanArray(arr.size());
366  if (!jarr) return nullptr;
367  jboolean *elements =
368  static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
369  if (!elements) return nullptr;
370  for (size_t i = 0; i < arr.size(); ++i)
371  elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
372  env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
373  return jarr;
374 }
375 
376 // Other MakeJ*Array conversions.
377 
378 #define WPI_JNI_MAKEJARRAY(T, F) \
379  inline T##Array MakeJ##F##Array(JNIEnv *env, llvm::ArrayRef<T> arr) { \
380  T##Array jarr = env->New##F##Array(arr.size()); \
381  if (!jarr) return nullptr; \
382  env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data()); \
383  return jarr; \
384  }
385 
386 WPI_JNI_MAKEJARRAY(jboolean, Boolean)
387 WPI_JNI_MAKEJARRAY(jbyte, Byte)
388 WPI_JNI_MAKEJARRAY(jshort, Short)
389 WPI_JNI_MAKEJARRAY(jlong, Long)
390 WPI_JNI_MAKEJARRAY(jfloat, Float)
391 WPI_JNI_MAKEJARRAY(jdouble, Double)
392 
393 #undef WPI_JNI_MAKEJARRAY
394 
395 // Convert an array of std::string into a jarray of jstring.
396 inline jobjectArray MakeJStringArray(JNIEnv *env,
398  static JClass stringCls{env, "java/lang/String"};
399  if (!stringCls) return nullptr;
400  jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
401  if (!jarr) return nullptr;
402  for (std::size_t i = 0; i < arr.size(); ++i) {
403  JLocal<jstring> elem{env, MakeJString(env, arr[i])};
404  env->SetObjectArrayElement(jarr, i, elem.obj());
405  }
406  return jarr;
407 }
408 
409 // Generic callback thread implementation.
410 //
411 // JNI's AttachCurrentThread() creates a Java Thread object on every
412 // invocation, which is both time inefficient and causes issues with Eclipse
413 // (which tries to keep a thread list up-to-date and thus gets swamped).
414 //
415 // Instead, this class attaches just once. When a hardware notification
416 // occurs, a condition variable wakes up this thread and this thread actually
417 // makes the call into Java.
418 //
419 // The template parameter T is the message being passed to the callback, but
420 // also needs to provide the following functions:
421 // static JavaVM* GetJVM();
422 // static const char* GetName();
423 // void CallJava(JNIEnv *env, jobject func, jmethodID mid);
424 //
425 // When creating this, ATOMIC_STATIC_INIT() needs to be performed on the
426 // templated class as well.
427 template <typename T>
428 class JCallbackThread : public SafeThread {
429  public:
430  void Main();
431 
432  std::queue<T> m_queue;
433  jobject m_func = nullptr;
434  jmethodID m_mid;
435 };
436 
437 template <typename T>
439  public:
440  void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
441 
442  template <typename... Args>
443  void Send(Args&&... args);
444 };
445 
446 template <typename T>
447 void JCallbackManager<T>::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
448  auto thr = this->GetThread();
449  if (!thr) return;
450  // free global reference
451  if (thr->m_func) env->DeleteGlobalRef(thr->m_func);
452  // create global reference
453  thr->m_func = env->NewGlobalRef(func);
454  thr->m_mid = mid;
455 }
456 
457 template <typename T>
458 template <typename... Args>
459 void JCallbackManager<T>::Send(Args&&... args) {
460  auto thr = this->GetThread();
461  if (!thr) return;
462  thr->m_queue.emplace(std::forward<Args>(args)...);
463  thr->m_cond.notify_one();
464 }
465 
466 template <typename T>
468  JNIEnv *env;
469  JavaVMAttachArgs args;
470  args.version = JNI_VERSION_1_2;
471  args.name = const_cast<char*>(T::GetName());
472  args.group = nullptr;
473  jint rs = T::GetJVM()->AttachCurrentThreadAsDaemon((void**)&env, &args);
474  if (rs != JNI_OK) return;
475 
476  std::unique_lock<std::mutex> lock(m_mutex);
477  while (m_active) {
478  m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
479  if (!m_active) break;
480  while (!m_queue.empty()) {
481  if (!m_active) break;
482  auto item = std::move(m_queue.front());
483  m_queue.pop();
484  auto func = m_func;
485  auto mid = m_mid;
486  lock.unlock(); // don't hold mutex during callback execution
487  item.CallJava(env, func, mid);
488  if (env->ExceptionCheck()) {
489  env->ExceptionDescribe();
490  env->ExceptionClear();
491  }
492  lock.lock();
493  }
494  }
495 
496  JavaVM* jvm = T::GetJVM();
497  if (jvm) jvm->DetachCurrentThread();
498 }
499 
500 template <typename T>
502  public:
503  static JSingletonCallbackManager<T>& GetInstance() {
504  ATOMIC_STATIC(JSingletonCallbackManager<T>, instance);
505  return instance;
506  }
507 
508  private:
509  ATOMIC_STATIC_DECL(JSingletonCallbackManager<T>)
510 };
511 
512 inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
513  llvm::StringRef excludeFuncPrefix) {
514  // create a throwable
515  static JClass throwableCls(env, "java/lang/Throwable");
516  if (!throwableCls) return "";
517  static jmethodID constructorId = nullptr;
518  if (!constructorId)
519  constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
520  JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
521 
522  // retrieve information from the exception.
523  // get method id
524  // getStackTrace returns an array of StackTraceElement
525  static jmethodID getStackTraceId = nullptr;
526  if (!getStackTraceId)
527  getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
528  "()[Ljava/lang/StackTraceElement;");
529 
530  // call getStackTrace
531  JLocal<jobjectArray> stackTrace(
532  env, static_cast<jobjectArray>(
533  env->CallObjectMethod(throwable, getStackTraceId)));
534 
535  if (!stackTrace) return "";
536 
537  // get length of the array
538  jsize stackTraceLength = env->GetArrayLength(stackTrace);
539 
540  // get toString methodId of StackTraceElement class
541  static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
542  if (!stackTraceElementCls) return "";
543  static jmethodID toStringId = nullptr;
544  if (!toStringId)
545  toStringId = env->GetMethodID(stackTraceElementCls, "toString",
546  "()Ljava/lang/String;");
547 
548  bool haveLoc = false;
549  std::string buf;
550  llvm::raw_string_ostream oss(buf);
551  for (jsize i = 0; i < stackTraceLength; i++) {
552  // add the result of toString method of each element in the result
553  JLocal<jobject> curStackTraceElement(
554  env, env->GetObjectArrayElement(stackTrace, i));
555 
556  // call to string on the object
557  JLocal<jstring> stackElementString(
558  env, static_cast<jstring>(
559  env->CallObjectMethod(curStackTraceElement, toStringId)));
560 
561  if (!stackElementString) return "";
562 
563  // add a line to res
564  JStringRef elem(env, stackElementString);
565  oss << elem << '\n';
566 
567  if (func) {
568  // func is caller of immediate caller (if there was one)
569  // or, if we see it, the first user function
570  if (i == 1)
571  *func = elem.str();
572  else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
573  !elem.str().startswith(excludeFuncPrefix)) {
574  *func = elem.str();
575  haveLoc = true;
576  }
577  }
578  }
579 
580  return oss.str();
581 }
582 
583 // Finds an exception class and keep it as a global reference.
584 // Similar to JClass, but provides Throw methods.
585 // Use with caution, as the destructor does NOT call DeleteGlobalRef due
586 // to potential shutdown issues with doing so.
587 class JException : public JClass {
588  public:
589  JException() = default;
590  JException(JNIEnv* env, const char* name) : JClass(env, name) {
591  if (m_cls)
592  m_constructor =
593  env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
594  }
595 
596  void Throw(JNIEnv* env, jstring msg) {
597  jobject exception = env->NewObject(m_cls, m_constructor, msg);
598  env->Throw(static_cast<jthrowable>(exception));
599  }
600 
601  void Throw(JNIEnv* env, llvm::StringRef msg) {
602  Throw(env, MakeJString(env, msg));
603  }
604 
605  explicit operator bool() const { return m_constructor; }
606 
607  private:
608  jmethodID m_constructor = nullptr;
609 };
610 
611 } // namespace java
612 } // namespace wpi
613 
614 #endif // WPIUTIL_SUPPORT_JNI_UTIL_H_
size_t size() const
size - Get the string size.
Definition: StringRef.h:149
Definition: SafeThread.h:19
Definition: jni_util.h:160
std::string str() const
str - Get the contents as an std::string.
Definition: StringRef.h:214
Definition: jni_util.h:142
Definition: jni_util.h:51
Definition: json.cpp:1170
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
Definition: StringExtras.h:22
Definition: SafeThread.h:119
Definition: SocketError.cpp:18
const char * data() const
data - Get a pointer to the start of the string (which may not be null terminated).
Definition: StringRef.h:139
ArrayRef - Represent a constant reference to an array (0 or more elements consecutively in memory)...
Definition: ArrayRef.h:32
size_t size() const
size - Get the array size.
Definition: ArrayRef.h:136
Definition: jni_util.h:428
std::string & str()
Flushes the stream contents to the target string and returns the string&#39;s reference.
Definition: raw_ostream.h:451
Definition: jni_util.h:290
Definition: jni_util.h:501
This is a &#39;vector&#39; (really, a variable-sized array), optimized for the case when the array is small...
Definition: SmallVector.h:834
Definition: jni_util.h:110
Definition: jni_util.h:78
Definition: jni_util.h:438
A raw_ostream that writes to an std::string.
Definition: raw_ostream.h:435
Definition: jni_util.h:587
StringRef - Represent a constant reference to a string, i.e.
Definition: StringRef.h:42
bool empty() const
empty - Check if the string is empty.
Definition: StringRef.h:146