Only intercept JSIExceptions when constructing JSError

Summary:
Constructing a `JSError` currently catches all exceptions that occur,
regardless of their type, and embeds the information from them in the
newly created `JSError`. This means that the type of exceptions can be
silently changed.

This results in a bug if Hermes is built with
`HERMESVM_EXCEPTION_ON_OOM` enabled. The OOM exception will get
converted to a `JSError`, which in turn can get converted into a
regular JS exception. The regular JS exception can then be caught by JS
and ignored. This leads to the VM continuing to execute in a bad
state.

I considered three ways to fix this:
1. Add a new type of exception to JSI that is intentionally meant to be
ignored, and the Hermes OOM exception then subclasses it.
2. Propagate all exceptions that happen when constructing `JSError`.
3. Propagate all exceptions except `JSIException`s when constructing
`JSError`.

The first is technically the most surgical, but it adds complexity to
JSI, and will require some extra machinery to implement since we
wouldn't be able to throw it directly from inside the VM. I'm also not
comfortable with the idea of JSI suppressing exceptions that are
completely unrelated to JSI.

The second is the simplest, and seems to match what JS would do if
`throw new Error()` itself threw, but there is some possibility that it
is a change in behaviour for existing code.

So the third approach, which is implemented in this diff, tries to
compromise between the two. Hermes can only ever throw `JSIException`s
from regular JS operations, so there should be no change for existing
code (except code that uses exceptions on OOM, which is what we're
trying to fix). Any other exception gets passed through to the caller.

Changelog:
[Internal][Fixed] - Fixed handling of Hermes OOM exceptions in JSI.

Reviewed By: jpporto

Differential Revision: D41831616

fbshipit-source-id: 42e0dde1c4acc016ab19533941c58fc1797ba1c2
This commit is contained in:
Neil Dhar 2023-01-22 00:25:49 -08:00 коммит произвёл Facebook GitHub Bot
Родитель 8fbcb5cf94
Коммит cd5a8c48c5
1 изменённых файлов: 51 добавлений и 61 удалений

Просмотреть файл

@ -427,11 +427,9 @@ JSError::JSError(Runtime& rt, std::string msg) : message_(std::move(msg)) {
setValue(
rt,
callGlobalFunction(rt, "Error", String::createFromUtf8(rt, message_)));
} catch (const std::exception& ex) {
} catch (const JSIException& ex) {
message_ = std::string(ex.what()) + " (while raising " + message_ + ")";
setValue(rt, String::createFromUtf8(rt, message_));
} catch (...) {
setValue(rt, Value());
}
}
@ -442,10 +440,8 @@ JSError::JSError(Runtime& rt, std::string msg, std::string stack)
e.setProperty(rt, "message", String::createFromUtf8(rt, message_));
e.setProperty(rt, "stack", String::createFromUtf8(rt, stack_));
setValue(rt, std::move(e));
} catch (const std::exception& ex) {
} catch (const JSIException& ex) {
setValue(rt, String::createFromUtf8(rt, ex.what()));
} catch (...) {
setValue(rt, Value());
}
}
@ -457,74 +453,68 @@ JSError::JSError(std::string what, Runtime& rt, Value&& value)
void JSError::setValue(Runtime& rt, Value&& value) {
value_ = std::make_shared<Value>(std::move(value));
try {
if ((message_.empty() || stack_.empty()) && value_->isObject()) {
auto obj = value_->getObject(rt);
if (message_.empty()) {
try {
Value message = obj.getProperty(rt, "message");
if (!message.isUndefined() && !message.isString()) {
message = callGlobalFunction(rt, "String", message);
}
if (message.isString()) {
message_ = message.getString(rt).utf8(rt);
} else if (!message.isUndefined()) {
message_ = "String(e.message) is a " + kindToString(message, &rt);
}
} catch (const std::exception& ex) {
message_ = std::string("[Exception while creating message string: ") +
ex.what() + "]";
}
}
if (stack_.empty()) {
try {
Value stack = obj.getProperty(rt, "stack");
if (!stack.isUndefined() && !stack.isString()) {
stack = callGlobalFunction(rt, "String", stack);
}
if (stack.isString()) {
stack_ = stack.getString(rt).utf8(rt);
} else if (!stack.isUndefined()) {
stack_ = "String(e.stack) is a " + kindToString(stack, &rt);
}
} catch (const std::exception& ex) {
message_ = std::string("[Exception while creating stack string: ") +
ex.what() + "]";
}
}
}
if ((message_.empty() || stack_.empty()) && value_->isObject()) {
auto obj = value_->getObject(rt);
if (message_.empty()) {
try {
if (value_->isString()) {
message_ = value_->getString(rt).utf8(rt);
} else {
Value message = callGlobalFunction(rt, "String", *value_);
if (message.isString()) {
message_ = message.getString(rt).utf8(rt);
} else {
message_ = "String(e) is a " + kindToString(message, &rt);
}
Value message = obj.getProperty(rt, "message");
if (!message.isUndefined() && !message.isString()) {
message = callGlobalFunction(rt, "String", message);
}
} catch (const std::exception& ex) {
if (message.isString()) {
message_ = message.getString(rt).utf8(rt);
} else if (!message.isUndefined()) {
message_ = "String(e.message) is a " + kindToString(message, &rt);
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating message string: ") +
ex.what() + "]";
}
}
if (stack_.empty()) {
stack_ = "no stack";
try {
Value stack = obj.getProperty(rt, "stack");
if (!stack.isUndefined() && !stack.isString()) {
stack = callGlobalFunction(rt, "String", stack);
}
if (stack.isString()) {
stack_ = stack.getString(rt).utf8(rt);
} else if (!stack.isUndefined()) {
stack_ = "String(e.stack) is a " + kindToString(stack, &rt);
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating stack string: ") +
ex.what() + "]";
}
}
}
if (what_.empty()) {
what_ = message_ + "\n\n" + stack_;
if (message_.empty()) {
try {
if (value_->isString()) {
message_ = value_->getString(rt).utf8(rt);
} else {
Value message = callGlobalFunction(rt, "String", *value_);
if (message.isString()) {
message_ = message.getString(rt).utf8(rt);
} else {
message_ = "String(e) is a " + kindToString(message, &rt);
}
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating message string: ") +
ex.what() + "]";
}
} catch (...) {
message_ = "[Exception caught creating message string]";
stack_ = "[Exception caught creating stack string]";
what_ = "[Exception caught getting value fields]";
}
if (stack_.empty()) {
stack_ = "no stack";
}
if (what_.empty()) {
what_ = message_ + "\n\n" + stack_;
}
}