Skip to content

Commit a195447

Browse files
mhorowitzfacebook-github-bot
authored andcommitted
Handle errors building JSErrors better
Summary: If building a JSError causes a JSError, this would lead to infinite recursion. This changes the error handling path so the problem is visible to JS. There's also a few related cleanups included, and a new test case. Changelog: [General] [Fixed] - If Error global is not callable when building an error, jsi will throw a JS exception back to JS code. #158 Reviewed By: tmikov, dulinriley Differential Revision: D18796269 fbshipit-source-id: 57a45d144fa2ea5e78d18c27d3130611737dda96
1 parent d07b164 commit a195447

File tree

2 files changed

+94
-10
lines changed

2 files changed

+94
-10
lines changed

ReactCommon/jsi/jsi/jsi.cpp

+42-7
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ std::string kindToString(const Value& v, Runtime* rt = nullptr) {
3737
}
3838
}
3939

40+
// getPropertyAsFunction() will try to create a JSError. If the
41+
// failure is in building a JSError, this will lead to infinite
42+
// recursion. This function is used in place of getPropertyAsFunction
43+
// when building JSError, to avoid that infinite recursion.
44+
Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) {
45+
Value v = runtime.global().getProperty(runtime, name);
46+
if (!v.isObject()) {
47+
throw JSINativeException(
48+
std::string("callGlobalFunction: JS global property '") + name +
49+
"' is " + kindToString(v, &runtime) + ", expected a Function");
50+
}
51+
Object o = v.getObject(runtime);
52+
if (!o.isFunction(runtime)) {
53+
throw JSINativeException(
54+
std::string("callGlobalFunction: JS global property '") + name +
55+
"' is a non-callable Object, expected a Function");
56+
}
57+
Function f = std::move(o).getFunction(runtime);
58+
return f.call(runtime, arg);
59+
}
60+
4061
} // namespace
4162

4263
namespace detail {
@@ -142,9 +163,7 @@ Function Object::getPropertyAsFunction(Runtime& runtime, const char* name)
142163
kindToString(std::move(obj), &runtime) + ", expected a Function");
143164
};
144165

145-
Runtime::PointerValue* value = obj.ptr_;
146-
obj.ptr_ = nullptr;
147-
return Function(value);
166+
return std::move(obj).getFunction(runtime);
148167
}
149168

150169
Array Object::asArray(Runtime& runtime) const& {
@@ -347,7 +366,11 @@ JSError::JSError(Runtime& rt, Value&& value) {
347366
JSError::JSError(Runtime& rt, std::string msg) : message_(std::move(msg)) {
348367
try {
349368
setValue(
350-
rt, rt.global().getPropertyAsFunction(rt, "Error").call(rt, message_));
369+
rt,
370+
callGlobalFunction(rt, "Error", String::createFromUtf8(rt, message_)));
371+
} catch (const std::exception& ex) {
372+
message_ = std::string(ex.what()) + " (while raising " + message_ + ")";
373+
setValue(rt, String::createFromUtf8(rt, message_));
351374
} catch (...) {
352375
setValue(rt, Value());
353376
}
@@ -360,6 +383,8 @@ JSError::JSError(Runtime& rt, std::string msg, std::string stack)
360383
e.setProperty(rt, "message", String::createFromUtf8(rt, message_));
361384
e.setProperty(rt, "stack", String::createFromUtf8(rt, stack_));
362385
setValue(rt, std::move(e));
386+
} catch (const std::exception& ex) {
387+
setValue(rt, String::createFromUtf8(rt, ex.what()));
363388
} catch (...) {
364389
setValue(rt, Value());
365390
}
@@ -380,20 +405,23 @@ void JSError::setValue(Runtime& rt, Value&& value) {
380405
if (message_.empty()) {
381406
jsi::Value message = obj.getProperty(rt, "message");
382407
if (!message.isUndefined()) {
383-
message_ = message.toString(rt).utf8(rt);
408+
message_ =
409+
callGlobalFunction(rt, "String", message).getString(rt).utf8(rt);
384410
}
385411
}
386412

387413
if (stack_.empty()) {
388414
jsi::Value stack = obj.getProperty(rt, "stack");
389415
if (!stack.isUndefined()) {
390-
stack_ = stack.toString(rt).utf8(rt);
416+
stack_ =
417+
callGlobalFunction(rt, "String", stack).getString(rt).utf8(rt);
391418
}
392419
}
393420
}
394421

395422
if (message_.empty()) {
396-
message_ = value_->toString(rt).utf8(rt);
423+
message_ =
424+
callGlobalFunction(rt, "String", *value_).getString(rt).utf8(rt);
397425
}
398426

399427
if (stack_.empty()) {
@@ -403,6 +431,13 @@ void JSError::setValue(Runtime& rt, Value&& value) {
403431
if (what_.empty()) {
404432
what_ = message_ + "\n\n" + stack_;
405433
}
434+
} catch (const std::exception& ex) {
435+
message_ = std::string("[Exception while creating message string: ") +
436+
ex.what() + "]";
437+
stack_ = std::string("Exception while creating stack string: ") +
438+
ex.what() + "]";
439+
what_ =
440+
std::string("Exception while getting value fields: ") + ex.what() + "]";
406441
} catch (...) {
407442
message_ = "[Exception caught creating message string]";
408443
stack_ = "[Exception caught creating stack string]";

ReactCommon/jsi/jsi/test/testlib.cpp

+52-3
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,7 @@ unsigned countOccurences(const std::string& of, const std::string& in) {
939939
} // namespace
940940

941941
TEST_P(JSITest, JSErrorsArePropagatedNicely) {
942-
unsigned callsBeoreError = 5;
942+
unsigned callsBeforeError = 5;
943943

944944
Function sometimesThrows = function(
945945
"function sometimesThrows(shouldThrow, callback) {"
@@ -953,9 +953,9 @@ TEST_P(JSITest, JSErrorsArePropagatedNicely) {
953953
rt,
954954
PropNameID::forAscii(rt, "callback"),
955955
0,
956-
[&sometimesThrows, &callsBeoreError](
956+
[&sometimesThrows, &callsBeforeError](
957957
Runtime& rt, const Value& thisVal, const Value* args, size_t count) {
958-
return sometimesThrows.call(rt, --callsBeoreError == 0, args[0]);
958+
return sometimesThrows.call(rt, --callsBeforeError == 0, args[0]);
959959
});
960960

961961
try {
@@ -975,6 +975,55 @@ TEST_P(JSITest, JSErrorsCanBeConstructedWithStack) {
975975
EXPECT_EQ(err.getStack(), "stack");
976976
}
977977

978+
TEST_P(JSITest, JSErrorDoesNotInfinitelyRecurse) {
979+
Value globalString = rt.global().getProperty(rt, "String");
980+
rt.global().setProperty(rt, "String", Value::undefined());
981+
try {
982+
eval("throw Error('whoops')");
983+
FAIL() << "expected exception";
984+
} catch (const JSError& ex) {
985+
EXPECT_EQ(
986+
ex.getMessage(),
987+
"[Exception while creating message string: callGlobalFunction: "
988+
"JS global property 'String' is undefined, expected a Function]");
989+
}
990+
rt.global().setProperty(rt, "String", globalString);
991+
992+
Value globalError = rt.global().getProperty(rt, "Error");
993+
rt.global().setProperty(rt, "Error", Value::undefined());
994+
try {
995+
rt.global().getPropertyAsFunction(rt, "NotAFunction");
996+
FAIL() << "expected exception";
997+
} catch (const JSError& ex) {
998+
EXPECT_EQ(
999+
ex.getMessage(),
1000+
"callGlobalFunction: JS global property 'Error' is undefined, "
1001+
"expected a Function (while raising getPropertyAsObject: "
1002+
"property 'NotAFunction' is undefined, expected an Object)");
1003+
}
1004+
1005+
// If Error is missing, this is fundamentally a problem with JS code
1006+
// messing up the global object, so it should present in JS code as
1007+
// a catchable string. Not an Error (because that's broken), or as
1008+
// a C++ failure.
1009+
1010+
auto fails = [](Runtime& rt, const Value&, const Value*, size_t) -> Value {
1011+
return rt.global().getPropertyAsObject(rt, "NotAProperty");
1012+
};
1013+
EXPECT_EQ(
1014+
function("function (f) { try { f(); return 'undefined'; }"
1015+
"catch (e) { return typeof e; } }")
1016+
.call(
1017+
rt,
1018+
Function::createFromHostFunction(
1019+
rt, PropNameID::forAscii(rt, "fails"), 0, fails))
1020+
.getString(rt)
1021+
.utf8(rt),
1022+
"string");
1023+
1024+
rt.global().setProperty(rt, "Error", globalError);
1025+
}
1026+
9781027
TEST_P(JSITest, ScopeDoesNotCrashTest) {
9791028
Scope scope(rt);
9801029
Object o(rt);

0 commit comments

Comments
 (0)