xref: /aosp_15_r20/external/fbjni/cxx/fbjni/detail/Exceptions.cpp (revision 65c59e023c5336bbd4a23be7af78407e3d80e7e7)
1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "CoreClasses.h"
18 #include "Log.h"
19 
20 #ifndef FBJNI_NO_EXCEPTION_PTR
21 #include <lyra/lyra.h>
22 #include <lyra/lyra_exceptions.h>
23 #endif
24 
25 #include <stdio.h>
26 #include <cstdlib>
27 #include <ios>
28 #include <stdexcept>
29 #include <string>
30 #include <system_error>
31 
32 #include <jni.h>
33 
34 #ifndef _WIN32
35 #include <cxxabi.h>
36 #endif
37 
38 namespace facebook {
39 namespace jni {
40 
41 namespace {
42 class JRuntimeException : public JavaClass<JRuntimeException, JThrowable> {
43  public:
44   static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;";
45 
create(const char * str)46   static local_ref<JRuntimeException> create(const char* str) {
47     return newInstance(make_jstring(str));
48   }
49 
create()50   static local_ref<JRuntimeException> create() {
51     return newInstance();
52   }
53 };
54 
55 class JIOException : public JavaClass<JIOException, JThrowable> {
56  public:
57   static auto constexpr kJavaDescriptor = "Ljava/io/IOException;";
58 
create(const char * str)59   static local_ref<JIOException> create(const char* str) {
60     return newInstance(make_jstring(str));
61   }
62 };
63 
64 class JOutOfMemoryError : public JavaClass<JOutOfMemoryError, JThrowable> {
65  public:
66   static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;";
67 
create(const char * str)68   static local_ref<JOutOfMemoryError> create(const char* str) {
69     return newInstance(make_jstring(str));
70   }
71 };
72 
73 class JArrayIndexOutOfBoundsException
74     : public JavaClass<JArrayIndexOutOfBoundsException, JThrowable> {
75  public:
76   static auto constexpr kJavaDescriptor =
77       "Ljava/lang/ArrayIndexOutOfBoundsException;";
78 
create(const char * str)79   static local_ref<JArrayIndexOutOfBoundsException> create(const char* str) {
80     return newInstance(make_jstring(str));
81   }
82 };
83 
84 class JUnknownCppException
85     : public JavaClass<JUnknownCppException, JThrowable> {
86  public:
87   static auto constexpr kJavaDescriptor =
88       "Lcom/facebook/jni/UnknownCppException;";
89 
create()90   static local_ref<JUnknownCppException> create() {
91     return newInstance();
92   }
93 
create(const char * str)94   static local_ref<JUnknownCppException> create(const char* str) {
95     return newInstance(make_jstring(str));
96   }
97 };
98 
99 class JCppSystemErrorException
100     : public JavaClass<JCppSystemErrorException, JThrowable> {
101  public:
102   static auto constexpr kJavaDescriptor =
103       "Lcom/facebook/jni/CppSystemErrorException;";
104 
create(const std::system_error & e)105   static local_ref<JCppSystemErrorException> create(
106       const std::system_error& e) {
107     return newInstance(make_jstring(e.what()), e.code().value());
108   }
109 };
110 
111 // Exception throwing & translating functions
112 // //////////////////////////////////////////////////////
113 
114 // Functions that throw Java exceptions
115 
setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable)116 void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) {
117   auto env = Environment::current();
118   if (throwable) {
119     env->Throw(throwable.get());
120   }
121   if (env->ExceptionCheck() != JNI_TRUE) {
122     FBJNI_LOGF("Failed to set Java exception");
123   }
124 }
125 
126 } // namespace
127 
128 // Functions that throw C++ exceptions
129 
130 // TODO(T6618159) Inject the c++ stack into the exception's stack trace. One
131 // issue: when a java exception is created, it captures the full java stack
132 // across jni boundaries. lyra will only capture the c++ stack to the jni
133 // boundary. So, as we pass the java exception up to c++, we need to capture
134 // the c++ stack and then insert it into the correct place in the java stack
135 // trace. Then, as the exception propagates across the boundaries, we will
136 // slowly fill in the c++ parts of the trace.
throwPendingJniExceptionAsCppException()137 void throwPendingJniExceptionAsCppException() {
138   JNIEnv* env = Environment::current();
139   if (env->ExceptionCheck() == JNI_FALSE) {
140     return;
141   }
142 
143   auto throwable = env->ExceptionOccurred();
144   if (!throwable) {
145     throw std::runtime_error("Unable to get pending JNI exception.");
146   }
147   env->ExceptionClear();
148 
149   throw JniException(adopt_local(throwable));
150 }
151 
throwCppExceptionIf(bool condition)152 void throwCppExceptionIf(bool condition) {
153   if (!condition) {
154     return;
155   }
156 
157   auto env = Environment::current();
158   if (env->ExceptionCheck() == JNI_TRUE) {
159     throwPendingJniExceptionAsCppException();
160     return;
161   }
162 
163   throw JniException();
164 }
165 
throwNewJavaException(jthrowable throwable)166 void throwNewJavaException(jthrowable throwable) {
167   throw JniException(wrap_alias(throwable));
168 }
169 
throwNewJavaException(const char * throwableName,const char * msg)170 void throwNewJavaException(const char* throwableName, const char* msg) {
171   // If anything of the fbjni calls fail, an exception of a suitable
172   // form will be thrown, which is what we want.
173   auto throwableClass = findClassLocal(throwableName);
174   auto throwable = throwableClass->newObject(
175       throwableClass->getConstructor<jthrowable(jstring)>(),
176       make_jstring(msg).release());
177   throwNewJavaException(throwable.get());
178 }
179 
180 // jthrowable
181 // //////////////////////////////////////////////////////////////////////////////////////
182 
initCause(alias_ref<JThrowable> cause)183 local_ref<JThrowable> JThrowable::initCause(alias_ref<JThrowable> cause) {
184   static auto meth =
185       javaClassStatic()->getMethod<javaobject(alias_ref<javaobject>)>(
186           "initCause");
187   return meth(self(), cause);
188 }
189 
getStackTrace()190 auto JThrowable::getStackTrace() -> local_ref<JStackTrace> {
191   static auto meth =
192       javaClassStatic()->getMethod<JStackTrace::javaobject()>("getStackTrace");
193   return meth(self());
194 }
195 
setStackTrace(alias_ref<JStackTrace> stack)196 void JThrowable::setStackTrace(alias_ref<JStackTrace> stack) {
197   static auto meth = javaClassStatic()->getMethod<void(alias_ref<JStackTrace>)>(
198       "setStackTrace");
199   return meth(self(), stack);
200 }
201 
getMessage()202 auto JThrowable::getMessage() -> local_ref<JString> {
203   static auto meth =
204       javaClassStatic()->getMethod<JString::javaobject()>("getMessage");
205   return meth(self());
206 }
207 
create(const std::string & declaringClass,const std::string & methodName,const std::string & file,int line)208 auto JStackTraceElement::create(
209     const std::string& declaringClass,
210     const std::string& methodName,
211     const std::string& file,
212     int line) -> local_ref<javaobject> {
213   return newInstance(declaringClass, methodName, file, line);
214 }
215 
getClassName() const216 std::string JStackTraceElement::getClassName() const {
217   static auto meth =
218       javaClassStatic()->getMethod<local_ref<JString>()>("getClassName");
219   return meth(self())->toStdString();
220 }
221 
getMethodName() const222 std::string JStackTraceElement::getMethodName() const {
223   static auto meth =
224       javaClassStatic()->getMethod<local_ref<JString>()>("getMethodName");
225   return meth(self())->toStdString();
226 }
227 
getFileName() const228 std::string JStackTraceElement::getFileName() const {
229   static auto meth =
230       javaClassStatic()->getMethod<local_ref<JString>()>("getFileName");
231   return meth(self())->toStdString();
232 }
233 
getLineNumber() const234 int JStackTraceElement::getLineNumber() const {
235   static auto meth = javaClassStatic()->getMethod<jint()>("getLineNumber");
236   return meth(self());
237 }
238 
239 // Translate C++ to Java Exception
240 
241 namespace {
242 
243 // For each exception in the chain of the exception_ptr argument, func
244 // will be called with that exception (in reverse order, i.e. innermost first).
245 #ifndef FBJNI_NO_EXCEPTION_PTR
denest(const std::function<void (std::exception_ptr)> & func,std::exception_ptr ptr)246 void denest(
247     const std::function<void(std::exception_ptr)>& func,
248     std::exception_ptr ptr) {
249   FBJNI_ASSERT(ptr);
250   try {
251     std::rethrow_exception(ptr);
252   } catch (const std::nested_exception& e) {
253     denest(func, e.nested_ptr());
254   } catch (...) {
255     // ignored.
256   }
257   func(ptr);
258 }
259 #endif
260 
261 } // namespace
262 
263 #ifndef FBJNI_NO_EXCEPTION_PTR
createJStackTraceElement(const lyra::StackTraceElement & cpp)264 local_ref<JStackTraceElement> createJStackTraceElement(
265     const lyra::StackTraceElement& cpp) {
266   return JStackTraceElement::create(
267       "|lyra|{" + cpp.libraryName() + "}",
268       cpp.functionName(),
269       cpp.buildId(),
270       cpp.libraryOffset());
271 }
272 
addCppStacktraceToJavaException(alias_ref<JThrowable> java,std::exception_ptr cpp)273 void addCppStacktraceToJavaException(
274     alias_ref<JThrowable> java,
275     std::exception_ptr cpp) {
276   auto cppStack = lyra::getStackTraceSymbols(
277       (cpp == nullptr) ? lyra::getStackTrace() : lyra::getExceptionTrace(cpp));
278 
279   auto javaStack = java->getStackTrace();
280   auto newStack =
281       JThrowable::JStackTrace::newArray(javaStack->size() + cppStack.size());
282   size_t i = 0;
283   for (size_t j = 0; j < cppStack.size(); j++, i++) {
284     (*newStack)[i] = createJStackTraceElement(cppStack[j]);
285   }
286   for (size_t j = 0; j < javaStack->size(); j++, i++) {
287     (*newStack)[i] = (*javaStack)[j];
288   }
289   java->setStackTrace(newStack);
290 }
291 
convertCppExceptionToJavaException(std::exception_ptr ptr)292 local_ref<JThrowable> convertCppExceptionToJavaException(
293     std::exception_ptr ptr) {
294   FBJNI_ASSERT(ptr);
295   local_ref<JThrowable> current;
296   bool addCppStack = true;
297   try {
298     std::rethrow_exception(ptr);
299     addCppStack = false;
300   } catch (const JniException& ex) {
301     current = ex.getThrowable();
302   } catch (const std::ios_base::failure& ex) {
303     current = JIOException::create(ex.what());
304   } catch (const std::bad_alloc& ex) {
305     current = JOutOfMemoryError::create(ex.what());
306   } catch (const std::out_of_range& ex) {
307     current = JArrayIndexOutOfBoundsException::create(ex.what());
308   } catch (const std::system_error& ex) {
309     current = JCppSystemErrorException::create(ex);
310   } catch (const std::runtime_error& ex) {
311     current = JRuntimeException::create(ex.what());
312   } catch (const std::exception& ex) {
313     current = JCppException::create(ex.what());
314   } catch (const char* msg) {
315     current = JUnknownCppException::create(msg);
316   } catch (...) {
317 #ifdef _WIN32
318     current = JUnknownCppException::create();
319 #else
320     const std::type_info* tinfo = abi::__cxa_current_exception_type();
321     if (tinfo) {
322       std::string msg = std::string("Unknown: ") + tinfo->name();
323       current = JUnknownCppException::create(msg.c_str());
324     } else {
325       current = JUnknownCppException::create();
326     }
327 #endif
328   }
329 
330   if (addCppStack) {
331     addCppStacktraceToJavaException(current, ptr);
332   }
333   return current;
334 }
335 #endif
336 
getJavaExceptionForCppBackTrace()337 local_ref<JThrowable> getJavaExceptionForCppBackTrace() {
338   return getJavaExceptionForCppBackTrace(nullptr);
339 }
340 
getJavaExceptionForCppBackTrace(const char * msg)341 local_ref<JThrowable> getJavaExceptionForCppBackTrace(const char* msg) {
342   local_ref<JThrowable> current =
343       msg ? JUnknownCppException::create(msg) : JUnknownCppException::create();
344 #ifndef FBJNI_NO_EXCEPTION_PTR
345   addCppStacktraceToJavaException(current, nullptr);
346 #endif
347   return current;
348 }
349 
350 #ifndef FBJNI_NO_EXCEPTION_PTR
getJavaExceptionForCppException(std::exception_ptr ptr)351 local_ref<JThrowable> getJavaExceptionForCppException(std::exception_ptr ptr) {
352   FBJNI_ASSERT(ptr);
353   local_ref<JThrowable> previous;
354   auto func = [&previous](std::exception_ptr ptr) {
355     auto current = convertCppExceptionToJavaException(ptr);
356     if (previous) {
357       current->initCause(previous);
358     }
359     previous = current;
360   };
361   denest(func, ptr);
362   return previous;
363 }
364 #endif
365 
translatePendingCppExceptionToJavaException()366 void translatePendingCppExceptionToJavaException() {
367   try {
368 #ifndef FBJNI_NO_EXCEPTION_PTR
369     auto exc = getJavaExceptionForCppException(std::current_exception());
370 #else
371     auto exc = JUnknownCppException::create();
372 #endif
373     setJavaExceptionAndAbortOnFailure(exc);
374   } catch (...) {
375 #ifndef FBJNI_NO_EXCEPTION_PTR
376     FBJNI_LOGE(
377         "Unexpected error in translatePendingCppExceptionToJavaException(): %s",
378         lyra::toString(std::current_exception()).c_str());
379 #else
380     FBJNI_LOGE(
381         "Unexpected error in translatePendingCppExceptionToJavaException()");
382 #endif
383     std::terminate();
384   }
385 }
386 
387 // JniException
388 // ////////////////////////////////////////////////////////////////////////////////////
389 
390 namespace {
391 constexpr const char* kExceptionMessageFailure =
392     "Unable to get exception message.";
393 }
394 
JniException()395 JniException::JniException() : JniException(JRuntimeException::create()) {}
396 
JniException(alias_ref<jthrowable> throwable)397 JniException::JniException(alias_ref<jthrowable> throwable)
398     : isMessageExtracted_(false) {
399   throwable_ = make_global(throwable);
400 }
401 
JniException(JniException && rhs)402 JniException::JniException(JniException&& rhs)
403     : throwable_(std::move(rhs.throwable_)),
404       what_(std::move(rhs.what_)),
405       isMessageExtracted_(rhs.isMessageExtracted_) {}
406 
JniException(const JniException & rhs)407 JniException::JniException(const JniException& rhs)
408     : what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) {
409   throwable_ = make_global(rhs.throwable_);
410 }
411 
~JniException()412 JniException::~JniException() {
413   try {
414     ThreadScope ts;
415     throwable_.reset();
416   } catch (...) {
417     FBJNI_LOGE("Exception in ~JniException()");
418     std::terminate();
419   }
420 }
421 
getThrowable() const422 local_ref<JThrowable> JniException::getThrowable() const noexcept {
423   return make_local(throwable_);
424 }
425 
426 // TODO 6900503: consider making this thread-safe.
populateWhat() const427 void JniException::populateWhat() const noexcept {
428   try {
429     ThreadScope ts;
430     static auto exceptionHelperClass =
431         findClassStatic("com/facebook/jni/ExceptionHelper");
432     static auto getErrorDescriptionMethod =
433         exceptionHelperClass->getStaticMethod<std::string(jthrowable)>(
434             "getErrorDescription");
435     what_ = getErrorDescriptionMethod(exceptionHelperClass, throwable_.get())
436                 ->toStdString();
437     isMessageExtracted_ = true;
438   } catch (...) {
439     what_ = kExceptionMessageFailure;
440   }
441 }
442 
what() const443 const char* JniException::what() const noexcept {
444   if (!isMessageExtracted_) {
445     populateWhat();
446   }
447   return what_.c_str();
448 }
449 
setJavaException() const450 void JniException::setJavaException() const noexcept {
451   setJavaExceptionAndAbortOnFailure(throwable_);
452 }
453 
454 } // namespace jni
455 } // namespace facebook
456