WPILibC++  unspecified
jni_util.h
1 /*----------------------------------------------------------------------------*/
2 /* Copyright (c) 2016-2018 FIRST. 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 <jni.h>
12 
13 #include <queue>
14 #include <string>
15 #include <type_traits>
16 #include <utility>
17 #include <vector>
18 
19 #include "llvm/ArrayRef.h"
20 #include "llvm/ConvertUTF.h"
21 #include "llvm/SmallString.h"
22 #include "llvm/SmallVector.h"
23 #include "llvm/StringRef.h"
24 #include "llvm/raw_ostream.h"
25 #include "support/SafeThread.h"
26 #include "support/atomic_static.h"
27 #include "support/deprecated.h"
28 #include "support/mutex.h"
29 
30 namespace wpi {
31 namespace java {
32 
33 // Gets a Java stack trace. Also provides the last function
34 // in the stack trace not starting with excludeFuncPrefix (useful for e.g.
35 // finding the first user call to a series of library functions).
36 std::string GetJavaStackTrace(
37  JNIEnv* env, std::string* func = nullptr,
38  llvm::StringRef excludeFuncPrefix = llvm::StringRef());
39 
40 // Shim for backwards compatibility
41 template <const char* excludeFuncPrefix>
42 WPI_DEPRECATED("use StringRef function instead")
43 std::string GetJavaStackTrace(JNIEnv* env, std::string* func) {
44  return GetJavaStackTrace(
45  env, func,
46  excludeFuncPrefix == nullptr ? llvm::StringRef() : excludeFuncPrefix);
47 }
48 
49 // Finds a class and keep it as a global reference.
50 // Use with caution, as the destructor does NOT call DeleteGlobalRef due
51 // to potential shutdown issues with doing so.
52 class JClass {
53  public:
54  JClass() = default;
55 
56  JClass(JNIEnv* env, const char* name) {
57  jclass local = env->FindClass(name);
58  if (!local) return;
59  m_cls = static_cast<jclass>(env->NewGlobalRef(local));
60  env->DeleteLocalRef(local);
61  }
62 
63  void free(JNIEnv* env) {
64  if (m_cls) env->DeleteGlobalRef(m_cls);
65  m_cls = nullptr;
66  }
67 
68  explicit operator bool() const { return m_cls; }
69 
70  operator jclass() const { return m_cls; }
71 
72  protected:
73  jclass m_cls = nullptr;
74 };
75 
76 // Container class for cleaning up Java local references.
77 // The destructor calls DeleteLocalRef.
78 template <typename T>
79 class JLocal {
80  public:
81  JLocal(JNIEnv* env, T obj) : m_env(env), m_obj(obj) {}
82  JLocal(const JLocal&) = delete;
83  JLocal(JLocal&& oth) : m_env(oth.m_env), m_obj(oth.m_obj) {
84  oth.m_obj = nullptr;
85  }
86  JLocal& operator=(const JLocal&) = delete;
87  JLocal& operator=(JLocal&& oth) {
88  m_env = oth.m_env;
89  m_obj = oth.m_obj;
90  oth.m_obj = nullptr;
91  return *this;
92  }
93  ~JLocal() {
94  if (m_obj) m_env->DeleteLocalRef(m_obj);
95  }
96  operator T() { return m_obj; }
97  T obj() { return m_obj; }
98 
99  private:
100  JNIEnv* m_env;
101  T m_obj;
102 };
103 
104 //
105 // Conversions from Java objects to C++
106 //
107 
108 // Java string (jstring) reference. The string is provided as UTF8.
109 // This is not actually a reference, as it makes a copy of the string
110 // characters, but it's named this way for consistency.
111 class JStringRef {
112  public:
113  JStringRef(JNIEnv* env, jstring str) {
114  if (str) {
115  jsize size = env->GetStringLength(str);
116  const jchar* chars = env->GetStringCritical(str, nullptr);
117  if (chars) {
118  llvm::convertUTF16ToUTF8String(llvm::makeArrayRef(chars, size), m_str);
119  env->ReleaseStringCritical(str, chars);
120  }
121  } else {
122  llvm::errs() << "JStringRef was passed a null pointer at \n"
123  << GetJavaStackTrace(env);
124  }
125  // Ensure str is null-terminated.
126  m_str.push_back('\0');
127  m_str.pop_back();
128  }
129 
130  operator llvm::StringRef() const { return m_str; }
131  llvm::StringRef str() const { return m_str; }
132  const char* c_str() const { return m_str.data(); }
133  size_t size() const { return m_str.size(); }
134 
135  private:
137 };
138 
139 // Details for J*ArrayRef and CriticalJ*ArrayRef
140 namespace detail {
141 
142 template <typename C, typename T>
143 class JArrayRefInner {};
144 
145 // Specialization of JArrayRefBase to provide StringRef conversion.
146 template <typename C>
147 class JArrayRefInner<C, jbyte> {
148  public:
149  operator llvm::StringRef() const { return str(); }
150 
151  llvm::StringRef str() const {
152  auto arr = static_cast<const C*>(this)->array();
153  if (arr.empty()) return llvm::StringRef{};
154  return llvm::StringRef{reinterpret_cast<const char*>(arr.data()),
155  arr.size()};
156  }
157 };
158 
159 // Base class for J*ArrayRef and CriticalJ*ArrayRef
160 template <typename T>
161 class JArrayRefBase : public JArrayRefInner<JArrayRefBase<T>, T> {
162  public:
163  explicit operator bool() const { return this->m_elements != nullptr; }
164 
165  operator llvm::ArrayRef<T>() const { return array(); }
166 
167  llvm::ArrayRef<T> array() const {
168  if (!this->m_elements) return llvm::ArrayRef<T>{};
169  return llvm::ArrayRef<T>{this->m_elements, this->m_size};
170  }
171 
172  JArrayRefBase(const JArrayRefBase&) = delete;
173  JArrayRefBase& operator=(const JArrayRefBase&) = delete;
174 
176  : m_env(oth.m_env),
177  m_jarr(oth.m_jarr),
178  m_size(oth.m_size),
179  m_elements(oth.m_elements) {
180  oth.m_jarr = nullptr;
181  oth.m_elements = nullptr;
182  }
183 
184  JArrayRefBase& operator=(JArrayRefBase&& oth) {
185  this->m_env = oth.m_env;
186  this->m_jarr = oth.m_jarr;
187  this->m_size = oth.m_size;
188  this->m_elements = oth.m_elements;
189  oth.m_jarr = nullptr;
190  oth.m_elements = nullptr;
191  return *this;
192  }
193 
194  protected:
195  JArrayRefBase(JNIEnv* env, T* elements, size_t size) {
196  this->m_env = env;
197  this->m_jarr = nullptr;
198  this->m_size = size;
199  this->m_elements = elements;
200  }
201 
202  JArrayRefBase(JNIEnv* env, jarray jarr) {
203  this->m_env = env;
204  this->m_jarr = jarr;
205  this->m_size = jarr ? env->GetArrayLength(jarr) : 0;
206  this->m_elements = nullptr;
207  }
208 
209  JNIEnv* m_env;
210  jarray m_jarr = nullptr;
211  size_t m_size;
212  T* m_elements;
213 };
214 
215 } // namespace detail
216 
217 // Java array / DirectBuffer reference.
218 
219 #define WPI_JNI_JARRAYREF(T, F) \
220  class J##F##ArrayRef : public detail::JArrayRefBase<T> { \
221  public: \
222  J##F##ArrayRef(JNIEnv* env, jobject bb, int len) \
223  : detail::JArrayRefBase<T>( \
224  env, \
225  static_cast<T*>(bb ? env->GetDirectBufferAddress(bb) : nullptr), \
226  len) { \
227  if (!bb) \
228  llvm::errs() << "JArrayRef was passed a null pointer at \n" \
229  << GetJavaStackTrace(env); \
230  } \
231  J##F##ArrayRef(JNIEnv* env, T##Array jarr) \
232  : detail::JArrayRefBase<T>(env, jarr) { \
233  if (jarr) \
234  m_elements = env->Get##F##ArrayElements(jarr, nullptr); \
235  else \
236  llvm::errs() << "JArrayRef was passed a null pointer at \n" \
237  << GetJavaStackTrace(env); \
238  } \
239  ~J##F##ArrayRef() { \
240  if (m_jarr && m_elements) \
241  m_env->Release##F##ArrayElements(static_cast<T##Array>(m_jarr), \
242  m_elements, JNI_ABORT); \
243  } \
244  }; \
245  \
246  class CriticalJ##F##ArrayRef : public detail::JArrayRefBase<T> { \
247  public: \
248  CriticalJ##F##ArrayRef(JNIEnv* env, T##Array jarr) \
249  : detail::JArrayRefBase<T>(env, jarr) { \
250  if (jarr) \
251  m_elements = \
252  static_cast<T*>(env->GetPrimitiveArrayCritical(jarr, nullptr)); \
253  else \
254  llvm::errs() << "JArrayRef was passed a null pointer at \n" \
255  << GetJavaStackTrace(env); \
256  } \
257  ~CriticalJ##F##ArrayRef() { \
258  if (m_jarr && m_elements) \
259  m_env->ReleasePrimitiveArrayCritical(m_jarr, m_elements, JNI_ABORT); \
260  } \
261  };
262 
263 WPI_JNI_JARRAYREF(jboolean, Boolean)
264 WPI_JNI_JARRAYREF(jbyte, Byte)
265 WPI_JNI_JARRAYREF(jshort, Short)
266 WPI_JNI_JARRAYREF(jint, Int)
267 WPI_JNI_JARRAYREF(jlong, Long)
268 WPI_JNI_JARRAYREF(jfloat, Float)
269 WPI_JNI_JARRAYREF(jdouble, Double)
270 
271 #undef WPI_JNI_JARRAYREF
272 
273 //
274 // Conversions from C++ to Java objects
275 //
276 
277 // Convert a UTF8 string into a jstring.
278 inline jstring MakeJString(JNIEnv* env, llvm::StringRef str) {
280  llvm::convertUTF8ToUTF16String(str, chars);
281  return env->NewString(chars.begin(), chars.size());
282 }
283 
284 // details for MakeJIntArray
285 namespace detail {
286 
287 // Slow path (get primitive array and set individual elements). This
288 // is used if the input type is not an integer of the same size (note
289 // signed/unsigned is ignored).
290 template <typename T,
291  bool = (std::is_integral<T>::value && sizeof(jint) == sizeof(T))>
293  static jintArray ToJava(JNIEnv* env, llvm::ArrayRef<T> arr) {
294  jintArray jarr = env->NewIntArray(arr.size());
295  if (!jarr) return nullptr;
296  jint* elements =
297  static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
298  if (!elements) return nullptr;
299  for (size_t i = 0; i < arr.size(); ++i)
300  elements[i] = static_cast<jint>(arr[i]);
301  env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
302  return jarr;
303  }
304 };
305 
306 // Fast path (use SetIntArrayRegion)
307 template <typename T>
309  static jintArray ToJava(JNIEnv* env, llvm::ArrayRef<T> arr) {
310  jintArray jarr = env->NewIntArray(arr.size());
311  if (!jarr) return nullptr;
312  env->SetIntArrayRegion(jarr, 0, arr.size(),
313  reinterpret_cast<const jint*>(arr.data()));
314  return jarr;
315  }
316 };
317 
318 } // namespace detail
319 
320 // Convert an ArrayRef to a jintArray.
321 template <typename T>
322 inline jintArray MakeJIntArray(JNIEnv* env, llvm::ArrayRef<T> arr) {
323  return detail::ConvertIntArray<T>::ToJava(env, arr);
324 }
325 
326 // Convert a SmallVector to a jintArray. This is required in addition to
327 // ArrayRef because template resolution occurs prior to implicit conversions.
328 template <typename T>
329 inline jintArray MakeJIntArray(JNIEnv* env,
330  const llvm::SmallVectorImpl<T>& arr) {
331  return detail::ConvertIntArray<T>::ToJava(env, arr);
332 }
333 
334 // Convert a std::vector to a jintArray. This is required in addition to
335 // ArrayRef because template resolution occurs prior to implicit conversions.
336 template <typename T>
337 inline jintArray MakeJIntArray(JNIEnv* env, const std::vector<T>& arr) {
338  return detail::ConvertIntArray<T>::ToJava(env, arr);
339 }
340 
341 // Convert a StringRef into a jbyteArray.
342 inline jbyteArray MakeJByteArray(JNIEnv* env, llvm::StringRef str) {
343  jbyteArray jarr = env->NewByteArray(str.size());
344  if (!jarr) return nullptr;
345  env->SetByteArrayRegion(jarr, 0, str.size(),
346  reinterpret_cast<const jbyte*>(str.data()));
347  return jarr;
348 }
349 
350 // Convert an array of integers into a jbooleanArray.
351 inline jbooleanArray MakeJBooleanArray(JNIEnv* env, llvm::ArrayRef<int> arr) {
352  jbooleanArray jarr = env->NewBooleanArray(arr.size());
353  if (!jarr) return nullptr;
354  jboolean* elements =
355  static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
356  if (!elements) return nullptr;
357  for (size_t i = 0; i < arr.size(); ++i)
358  elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
359  env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
360  return jarr;
361 }
362 
363 // Convert an array of booleans into a jbooleanArray.
364 inline jbooleanArray MakeJBooleanArray(JNIEnv* env, llvm::ArrayRef<bool> arr) {
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 (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(
474  reinterpret_cast<void**>(&env), &args);
475  if (rs != JNI_OK) return;
476 
477  std::unique_lock<wpi::mutex> lock(m_mutex);
478  while (m_active) {
479  m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
480  if (!m_active) break;
481  while (!m_queue.empty()) {
482  if (!m_active) break;
483  auto item = std::move(m_queue.front());
484  m_queue.pop();
485  auto func = m_func;
486  auto mid = m_mid;
487  lock.unlock(); // don't hold mutex during callback execution
488  item.CallJava(env, func, mid);
489  if (env->ExceptionCheck()) {
490  env->ExceptionDescribe();
491  env->ExceptionClear();
492  }
493  lock.lock();
494  }
495  }
496 
497  JavaVM* jvm = T::GetJVM();
498  if (jvm) jvm->DetachCurrentThread();
499 }
500 
501 template <typename T>
503  public:
504  static JSingletonCallbackManager<T>& GetInstance() {
505  ATOMIC_STATIC(JSingletonCallbackManager<T>, instance);
506  return instance;
507  }
508 
509  private:
510  ATOMIC_STATIC_DECL(JSingletonCallbackManager<T>)
511 };
512 
513 inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
514  llvm::StringRef excludeFuncPrefix) {
515  // create a throwable
516  static JClass throwableCls(env, "java/lang/Throwable");
517  if (!throwableCls) return "";
518  static jmethodID constructorId = nullptr;
519  if (!constructorId)
520  constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
521  JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
522 
523  // retrieve information from the exception.
524  // get method id
525  // getStackTrace returns an array of StackTraceElement
526  static jmethodID getStackTraceId = nullptr;
527  if (!getStackTraceId)
528  getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
529  "()[Ljava/lang/StackTraceElement;");
530 
531  // call getStackTrace
532  JLocal<jobjectArray> stackTrace(
533  env, static_cast<jobjectArray>(
534  env->CallObjectMethod(throwable, getStackTraceId)));
535 
536  if (!stackTrace) return "";
537 
538  // get length of the array
539  jsize stackTraceLength = env->GetArrayLength(stackTrace);
540 
541  // get toString methodId of StackTraceElement class
542  static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
543  if (!stackTraceElementCls) return "";
544  static jmethodID toStringId = nullptr;
545  if (!toStringId)
546  toStringId = env->GetMethodID(stackTraceElementCls, "toString",
547  "()Ljava/lang/String;");
548 
549  bool haveLoc = false;
550  std::string buf;
551  llvm::raw_string_ostream oss(buf);
552  for (jsize i = 0; i < stackTraceLength; i++) {
553  // add the result of toString method of each element in the result
554  JLocal<jobject> curStackTraceElement(
555  env, env->GetObjectArrayElement(stackTrace, i));
556 
557  // call to string on the object
558  JLocal<jstring> stackElementString(
559  env, static_cast<jstring>(
560  env->CallObjectMethod(curStackTraceElement, toStringId)));
561 
562  if (!stackElementString) return "";
563 
564  // add a line to res
565  JStringRef elem(env, stackElementString);
566  oss << elem << '\n';
567 
568  if (func) {
569  // func is caller of immediate caller (if there was one)
570  // or, if we see it, the first user function
571  if (i == 1) {
572  *func = elem.str();
573  } else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
574  !elem.str().startswith(excludeFuncPrefix)) {
575  *func = elem.str();
576  haveLoc = true;
577  }
578  }
579  }
580 
581  return oss.str();
582 }
583 
584 // Finds an exception class and keep it as a global reference.
585 // Similar to JClass, but provides Throw methods.
586 // Use with caution, as the destructor does NOT call DeleteGlobalRef due
587 // to potential shutdown issues with doing so.
588 class JException : public JClass {
589  public:
590  JException() = default;
591  JException(JNIEnv* env, const char* name) : JClass(env, name) {
592  if (m_cls)
593  m_constructor =
594  env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
595  }
596 
597  void Throw(JNIEnv* env, jstring msg) {
598  jobject exception = env->NewObject(m_cls, m_constructor, msg);
599  env->Throw(static_cast<jthrowable>(exception));
600  }
601 
602  void Throw(JNIEnv* env, llvm::StringRef msg) {
603  Throw(env, MakeJString(env, msg));
604  }
605 
606  explicit operator bool() const { return m_constructor; }
607 
608  private:
609  jmethodID m_constructor = nullptr;
610 };
611 
612 } // namespace java
613 } // namespace wpi
614 
615 #endif // WPIUTIL_SUPPORT_JNI_UTIL_H_
size_t size() const
size - Get the string size.
Definition: StringRef.h:149
Definition: SafeThread.h:20
Definition: jni_util.h:161
std::string str() const
str - Get the contents as an std::string.
Definition: StringRef.h:214
Definition: jni_util.h:143
Definition: jni_util.h:52
Definition: json.cpp:1170
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
Definition: WindowsSupport.h:184
Definition: SafeThread.h:120
Definition: SocketError.cpp:17
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:292
Definition: jni_util.h:502
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:111
Definition: jni_util.h:79
Definition: jni_util.h:438
A raw_ostream that writes to an std::string.
Definition: raw_ostream.h:435
Definition: jni_util.h:588
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