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