Compare commits

...

9 Commits

Author SHA1 Message Date
Meghan Denny
c728e5ed24 add uncaughtException tests 2024-08-03 01:01:44 -07:00
Meghan Denny
e96a3c3e9e more tests 2024-08-03 00:48:39 -07:00
Meghan Denny
e5d23f515c cleaner compare view 2024-08-03 00:48:18 -07:00
Meghan Denny
6b3cf30a07 dead code 2024-08-03 00:48:01 -07:00
Meghan Denny
9e54c2892a add some tests 2024-08-03 00:22:13 -07:00
Meghan Denny
e230f06779 make it a little more robust 2024-08-03 00:21:44 -07:00
Meghan Denny
8ab959740d pass unhandled rejections to uncaughtException when no unhandledRejection handler exists 2024-08-02 22:40:20 -07:00
Meghan Denny
88919676c6 add ERR_UNHANDLED_REJECTION 2024-08-02 22:38:52 -07:00
Meghan Denny
1677d04588 fix Bun__handleUnhandledRejection + Bun__handleUncaughtException type signature 2024-08-02 22:38:09 -07:00
5 changed files with 280 additions and 7 deletions

View File

@@ -816,7 +816,7 @@ void signalHandler(uv_signal_t* signal, int signalNumber)
extern "C" void Bun__logUnhandledException(JSC::EncodedJSValue exception);
extern "C" int Bun__handleUncaughtException(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue exception, int isRejection)
extern "C" bool Bun__handleUncaughtException(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue exception, bool isRejection)
{
if (!lexicalGlobalObject->inherits(Zig::GlobalObject::info()))
return false;
@@ -860,7 +860,7 @@ extern "C" int Bun__handleUncaughtException(JSC::JSGlobalObject* lexicalGlobalOb
return true;
}
extern "C" int Bun__handleUnhandledRejection(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue reason, JSC::JSValue promise)
extern "C" bool Bun__handleUnhandledRejection(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue reason, JSC::JSValue promise)
{
if (!lexicalGlobalObject->inherits(Zig::GlobalObject::info()))
return false;

View File

@@ -19,6 +19,7 @@ JSC::EncodedJSValue JSC__JSValue__createRangeError(const ZigString* message, con
extern "C" JSC::EncodedJSValue Bun__ERR_INVALID_ARG_TYPE(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue val_arg_name, JSC::EncodedJSValue val_expected_type, JSC::EncodedJSValue val_actual_value);
extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2, JSC::EncodedJSValue arg3);
extern "C" JSC::EncodedJSValue Bun__ERR_IPC_CHANNEL_CLOSED(JSC::JSGlobalObject* globalObject);
extern "C" JSC::EncodedJSValue Bun__ERR_UNHANDLED_REJECTION(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue reason);
namespace Bun {
@@ -204,4 +205,61 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_SOCKET_BAD_TYPE, (JSC::JSGlobalObject *
return JSC::JSValue::encode(createTypeErrorWithCode(globalObject, "Bad socket type specified. Valid types are: udp4, udp6"_s, "ERR_SOCKET_BAD_TYPE"_s));
}
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_UNHANDLED_REJECTION, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto argCount = callFrame->argumentCount();
if (argCount < 1) {
JSC::throwTypeError(globalObject, scope, "requires 3 arguments"_s);
return {};
}
auto reason = callFrame->argument(0);
return Bun__ERR_UNHANDLED_REJECTION(globalObject, JSValue::encode(reason));
}
extern "C" JSC::EncodedJSValue Bun__ERR_UNHANDLED_REJECTION(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue encoded_reason)
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
// need to make sure this does not throw
auto value = JSC::JSValue::decode(encoded_reason);
ASSERT(!value.isEmpty());
WTF::String string;
do {
if (!value.isCell()) {
string = value.toString(globalObject)->getString(globalObject);
break;
}
auto cell = value.asCell();
auto jstype = cell->type();
if (jstype == JSC::JSType::StringType) {
string = cell->toStringInline(globalObject)->getString(globalObject);
break;
}
if (jstype == JSC::JSType::SymbolType) {
auto symbol = jsCast<Symbol*>(cell);
auto result = symbol->tryGetDescriptiveString();
if (result.has_value()) {
string = result.value();
break;
}
}
auto jsstring = value.toStringOrNull(globalObject);
if (!jsstring) {
scope.clearException();
jsstring = jsString(vm, String("[object Object]"_s));
}
string = jsstring->getString(globalObject);
} while (0);
auto message = makeString("This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason \""_s, string, "\"."_s);
return JSC::JSValue::encode(createErrorWithCode(globalObject, message, "ERR_UNHANDLED_REJECTION"_s));
}
}

View File

@@ -8,5 +8,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_DISCONNECTED, (JSC::JSGlobalObject *
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_SERVER_NOT_RUNNING, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*));
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_CHANNEL_CLOSED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*));
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_SOCKET_BAD_TYPE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*));
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_UNHANDLED_REJECTION, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame));
}

View File

@@ -1043,9 +1043,10 @@ pub const VirtualMachine = struct {
}
}
extern fn Bun__handleUncaughtException(*JSC.JSGlobalObject, err: JSC.JSValue, is_rejection: c_int) c_int;
extern fn Bun__handleUnhandledRejection(*JSC.JSGlobalObject, reason: JSC.JSValue, promise: JSC.JSValue) c_int;
extern fn Bun__handleUncaughtException(*JSC.JSGlobalObject, err: JSC.JSValue, is_rejection: bool) bool;
extern fn Bun__handleUnhandledRejection(*JSC.JSGlobalObject, reason: JSC.JSValue, promise: JSC.JSValue) bool;
extern fn Bun__Process__exit(*JSC.JSGlobalObject, code: c_int) noreturn;
extern fn Bun__ERR_UNHANDLED_REJECTION(*JSC.JSGlobalObject, reason: JSC.JSValue) JSC.JSValue;
pub fn unhandledRejection(this: *JSC.VirtualMachine, globalObject: *JSC.JSGlobalObject, reason: JSC.JSValue, promise: JSC.JSValue) bool {
if (this.isShuttingDown()) {
@@ -1059,8 +1060,10 @@ pub const VirtualMachine = struct {
return true;
}
const handled = Bun__handleUnhandledRejection(globalObject, reason, promise) > 0;
const handled = Bun__handleUnhandledRejection(globalObject, reason, promise);
if (!handled) {
const handled2 = Bun__handleUncaughtException(globalObject, Bun__ERR_UNHANDLED_REJECTION(globalObject, reason), true);
if (handled2) return true;
this.unhandled_error_counter += 1;
this.onUnhandledRejection(this, globalObject, reason);
}
@@ -1086,7 +1089,7 @@ pub const VirtualMachine = struct {
}
this.is_handling_uncaught_exception = true;
defer this.is_handling_uncaught_exception = false;
const handled = Bun__handleUncaughtException(globalObject, err.toError() orelse err, if (is_rejection) 1 else 0) > 0;
const handled = Bun__handleUncaughtException(globalObject, err.toError() orelse err, is_rejection);
if (!handled) {
// TODO maybe we want a separate code path for uncaught exceptions
this.unhandled_error_counter += 1;

View File

@@ -37,7 +37,7 @@ expect.extend({
return {
pass: result.stdout.toString("utf-8") === optionalStdout,
message: () =>
`Expected ${cmds.join(" ")} to output ${optionalStdout} but got ${result.stdout.toString("utf-8")}`,
`Expected ${cmds.join(" ")}\n to output ${optionalStdout}\n but got ${result.stdout.toString("utf-8")}`,
};
}
@@ -985,3 +985,214 @@ describe("process.exitCode", () => {
]).toRunInlineFixture();
});
});
describe("on(uncaughtException)", () => {
it("throw number", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log(a);
// TODO b is wrong
});
throw 42;
`,
"42\n",
0,
]).toRunInlineFixture();
});
it("throw error", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log(a instanceof Error);
console.log(a.message);
// TODO b is wrong
});
throw new Error("hello");
`,
"true\nhello\n",
0,
]).toRunInlineFixture();
});
it("call undefined function", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log(a instanceof TypeError);
console.log(a.message);
// TODO b is wrong
});
global.foo();
`,
"true\nglobal.foo is not a function. (In 'global.foo()', 'global.foo' is undefined)\n",
0,
]).toRunInlineFixture();
});
});
describe("on(unhandledRejection)", () => {
it("normal", () => {
expect([
`
process.on("unhandledRejection", (a, b) => {
console.log("unhandledRejection", a, b);
});
Promise.reject(42);
`,
"unhandledRejection 42 Promise { <rejected> }\n",
0,
]).toRunInlineFixture();
});
});
describe("unhandled rejection -> uncaughtException", () => {
it("number", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log("uncaughtException");
console.log(a.code);
console.log(a.message);
console.log(b);
});
Promise.reject(42);
`,
`uncaughtException\nERR_UNHANDLED_REJECTION\nThis error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "42".\nunhandledRejection\n`,
0,
]).toRunInlineFixture();
});
it("plain object with toString that throws", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log("uncaughtException");
console.log(a.code);
console.log(a.message);
console.log(b);
});
const x = {toString:()=>{throw new Error()}};
Promise.reject(x);
`,
`uncaughtException\nERR_UNHANDLED_REJECTION\nThis error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "[object Object]".\nunhandledRejection\n`,
0,
]).toRunInlineFixture();
});
it("symbol", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log("uncaughtException");
console.log(a.code);
console.log(a.message);
console.log(b);
});
Promise.reject(Symbol('bun'));
`,
`uncaughtException\nERR_UNHANDLED_REJECTION\nThis error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Symbol(bun)".\nunhandledRejection\n`,
0,
]).toRunInlineFixture();
});
it("string", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log("uncaughtException");
console.log(a.code);
console.log(a.message);
console.log(b);
});
Promise.reject('bun');
`,
`uncaughtException\nERR_UNHANDLED_REJECTION\nThis error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "bun".\nunhandledRejection\n`,
0,
]).toRunInlineFixture();
});
it.todo("string object", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log(a.message);
});
Promise.reject(new String('bun'));
`,
`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "[object String]".\n`,
0,
]).toRunInlineFixture();
});
it("bigint", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log(a.message);
});
Promise.reject(3n);
`,
`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "3".\n`,
0,
]).toRunInlineFixture();
});
it.todo("array", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log(a.message);
});
Promise.reject([]);
`,
`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "[object Array]".\n`,
0,
]).toRunInlineFixture();
});
it.todo("require", () => {
expect([
`
process.on("uncaughtException", (a, b) => {
console.log(a.message);
});
Promise.reject(require);
`,
`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "function require() {\n [native code]\n}".\n`,
0,
]).toRunInlineFixture();
});
it("function", () => {
expect([
`
// @bun
process.on("uncaughtException", (a, b) => {
console.log(a.message);
});
const x = () => {};
Promise.reject(x);
`,
`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "() => {}".\n`,
0,
]).toRunInlineFixture();
});
it.todo("function with toString that throws", () => {
expect([
`
// @bun
process.on("uncaughtException", (a, b) => {
console.log(a.message);
});
const x = () => {};
x.__proto__.toString = () => {throw new Error()};
Promise.reject(x);
`,
`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "() => {}".\n`,
0,
]).toRunInlineFixture();
});
});