Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement process.on("uncaughtException", ...) #10902

Merged
merged 14 commits into from
May 9, 2024
8 changes: 4 additions & 4 deletions src/bun.js/api/BunObject.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3685,7 +3685,7 @@ pub const Timer = struct {
}

var this = args.ptr[1].asPtr(CallbackJob);
globalThis.bunVM().onError(globalThis, args.ptr[0]);
_ = globalThis.bunVM().uncaughtException(globalThis, args.ptr[0], true);
this.deinit();
return JSValue.jsUndefined();
}
Expand Down Expand Up @@ -3787,7 +3787,7 @@ pub const Timer = struct {
}

if (result.isAnyError()) {
vm.onError(globalThis, result);
_ = vm.uncaughtException(globalThis, result, false);
this.deinit();
return;
}
Expand All @@ -3796,7 +3796,7 @@ pub const Timer = struct {
switch (promise.status(globalThis.vm())) {
.Rejected => {
this.deinit();
vm.onError(globalThis, promise.result(globalThis.vm()));
_ = vm.unhandledRejection(globalThis, promise.result(globalThis.vm()), promise.asValue(globalThis));
},
.Fulfilled => {
this.deinit();
Expand Down Expand Up @@ -5265,7 +5265,7 @@ pub const EnvironmentVariables = struct {
};

export fn Bun__reportError(globalObject: *JSGlobalObject, err: JSC.JSValue) void {
JSC.VirtualMachine.get().onError(globalObject, err);
_ = JSC.VirtualMachine.get().uncaughtException(globalObject, err, false);
}

comptime {
Expand Down
4 changes: 2 additions & 2 deletions src/bun.js/api/bun/socket.zig
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,14 @@ const Handlers = struct {
const onError = this.onError;
if (onError == .zero) {
if (err.len > 0)
this.vm.onError(this.globalObject, err[0]);
_ = this.vm.uncaughtException(this.globalObject, err[0], false);

return false;
}

const result = onError.callWithThis(this.globalObject, thisValue, err);
if (result.isAnyError()) {
this.vm.onError(this.globalObject, result);
_ = this.vm.uncaughtException(this.globalObject, result, false);
}

return true;
Expand Down
4 changes: 2 additions & 2 deletions src/bun.js/api/bun/udp_socket.zig
Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,14 @@ pub const UDPSocket = struct {

if (callback == .zero) {
if (err.len > 0)
vm.onError(globalThis, err[0]);
_ = vm.uncaughtException(globalThis, err[0], false);

return false;
}

const result = callback.callWithThis(globalThis, thisValue, err);
if (result.isAnyError()) {
vm.onError(globalThis, result);
_ = vm.uncaughtException(globalThis, result, false);
}

return true;
Expand Down
2 changes: 1 addition & 1 deletion src/bun.js/api/html_rewriter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ fn HandlerCallback(
this.global.bunVM().waitForPromise(promise);
const fail = promise.status(this.global.vm()) == .Rejected;
if (fail) {
this.global.bunVM().onError(this.global, promise.result(this.global.vm()));
_ = this.global.bunVM().unhandledRejection(this.global, promise.result(this.global.vm()), promise.asValue(this.global));
}
return fail;
}
Expand Down
6 changes: 3 additions & 3 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3930,7 +3930,7 @@ pub const ServerWebSocket = struct {
}

if (error_handler.isEmptyOrUndefinedOrNull()) {
vm.onError(globalObject, err_value);
_ = vm.uncaughtException(globalObject, err_value, false);
} else {
const corky = [_]JSValue{err_value};
corker.args = &corky;
Expand Down Expand Up @@ -3997,7 +3997,7 @@ pub const ServerWebSocket = struct {

if (result.toError()) |err_value| {
if (this.handler.onError.isEmptyOrUndefinedOrNull()) {
vm.onError(globalObject, err_value);
_ = vm.uncaughtException(globalObject, err_value, false);
} else {
const args = [_]JSValue{err_value};
corker.args = &args;
Expand Down Expand Up @@ -4049,7 +4049,7 @@ pub const ServerWebSocket = struct {

if (result.toError()) |err_value| {
if (this.handler.onError.isEmptyOrUndefinedOrNull()) {
vm.onError(globalObject, err_value);
_ = vm.uncaughtException(globalObject, err_value, false);
} else {
const args = [_]JSValue{err_value};
corker.args = &args;
Expand Down
90 changes: 90 additions & 0 deletions src/bun.js/bindings/BunProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
#include <JavaScriptCore/JSMicrotask.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/NumberPrototype.h>
#include "JavaScriptCore/CatchScope.h"
#include "JavaScriptCore/JSCJSValue.h"
#include "JavaScriptCore/JSCast.h"
#include "JavaScriptCore/JSString.h"
#include "JavaScriptCore/Protect.h"
#include "ScriptExecutionContext.h"
#include "headers-handwritten.h"
#include "node_api.h"
Expand All @@ -20,6 +25,7 @@
#include "wtf-bindings.h"

#include "ProcessBindingTTYWrap.h"
#include "wtf/text/ASCIILiteral.h"

#ifndef WIN32
#include <errno.h>
Expand Down Expand Up @@ -476,6 +482,23 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionExit,
return JSC::JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(Process_setUncaughtExceptionCaptureCallback,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm());
JSValue arg0 = callFrame->argument(0);
if (!arg0.isCallable() && !arg0.isNull()) {
throwTypeError(globalObject, throwScope, "The \"callback\" argument must be callable or null"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}
auto* zigGlobal = jsDynamicCast<Zig::GlobalObject*>(globalObject);
if (UNLIKELY(!zigGlobal)) {
zigGlobal = Bun__getDefaultGlobal();
}
jsCast<Process*>(zigGlobal->processObject())->setUncaughtExceptionCaptureCallback(arg0);
return JSC::JSValue::encode(jsUndefined());
}

extern "C" uint64_t Bun__readOriginTimer(void*);

JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime,
Expand Down Expand Up @@ -746,6 +769,71 @@ 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)
{
if (!lexicalGlobalObject->inherits(Zig::GlobalObject::info()))
return false;
auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
auto* process = jsCast<Process*>(globalObject->processObject());
auto& wrapped = process->wrapped();
auto& vm = globalObject->vm();

MarkedArgumentBuffer args;
args.append(exception);
if (isRejection) {
args.append(jsString(vm, String("unhandledRejection"_s)));
} else {
args.append(jsString(vm, String("uncaughtException"_s)));
}

auto uncaughtExceptionMonitor = Identifier::fromString(globalObject->vm(), "uncaughtExceptionMonitor"_s);
if (wrapped.listenerCount(uncaughtExceptionMonitor) > 0) {
wrapped.emit(uncaughtExceptionMonitor, args);
}

auto uncaughtExceptionIdent = Identifier::fromString(globalObject->vm(), "uncaughtException"_s);

// if there is an uncaughtExceptionCaptureCallback, call it and consider the exception handled
auto capture = process->getUncaughtExceptionCaptureCallback();
if (!capture.isEmpty() && !capture.isUndefinedOrNull()) {
auto scope = DECLARE_CATCH_SCOPE(vm);
(void)call(lexicalGlobalObject, capture, args, "uncaughtExceptionCaptureCallback"_s);
if (auto ex = scope.exception()) {
scope.clearException();
// if an exception is thrown in the uncaughtException handler, we abort
Bun__logUnhandledException(JSValue::encode(JSValue(ex)));
Bun__Process__exit(lexicalGlobalObject, 1);
}
} else if (wrapped.listenerCount(uncaughtExceptionIdent) > 0) {
wrapped.emit(uncaughtExceptionIdent, args);
} else {
return false;
}

return true;
}

extern "C" int Bun__handleUnhandledRejection(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue reason, JSC::JSValue promise)
{
if (!lexicalGlobalObject->inherits(Zig::GlobalObject::info()))
return false;
auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
auto* process = jsCast<Process*>(globalObject->processObject());
MarkedArgumentBuffer args;
args.append(reason);
args.append(promise);
auto eventType = Identifier::fromString(globalObject->vm(), "unhandledRejection"_s);
auto& wrapped = process->wrapped();
if (wrapped.listenerCount(eventType) > 0) {
wrapped.emit(eventType, args);
return true;
} else {
return false;
}
}

static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& eventName, bool isAdded)
{
if (eventEmitter.scriptExecutionContext()->isMainThread()) {
Expand Down Expand Up @@ -2027,6 +2115,7 @@ void Process::visitChildrenImpl(JSCell* cell, Visitor& visitor)
Process* thisObject = jsCast<Process*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_uncaughtExceptionCaptureCallback);
thisObject->m_cpuUsageStructure.visit(visitor);
thisObject->m_memoryUsageStructure.visit(visitor);
thisObject->m_bindingUV.visit(visitor);
Expand Down Expand Up @@ -2714,6 +2803,7 @@ extern "C" void Process__emitDisconnectEvent(Zig::GlobalObject* global)
report constructProcessReportObject PropertyCallback
revision constructRevision PropertyCallback
setSourceMapsEnabled Process_stubEmptyFunction Function 1
setUncaughtExceptionCaptureCallback Process_setUncaughtExceptionCaptureCallback Function 1
send constructProcessSend PropertyCallback
stderr constructStderr PropertyCallback
stdin constructStdin PropertyCallback
Expand Down
9 changes: 9 additions & 0 deletions src/bun.js/bindings/BunProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Process : public WebCore::JSEventEmitter {
LazyProperty<Process, Structure> m_memoryUsageStructure;
LazyProperty<Process, JSObject> m_bindingUV;
LazyProperty<Process, JSObject> m_bindingNatives;
WriteBarrier<Unknown> m_uncaughtExceptionCaptureCallback;

public:
Process(JSC::Structure* structure, WebCore::JSDOMGlobalObject& globalObject, Ref<WebCore::EventEmitter>&& impl)
Expand Down Expand Up @@ -70,6 +71,14 @@ class Process : public WebCore::JSEventEmitter {

void finishCreation(JSC::VM& vm);

inline void setUncaughtExceptionCaptureCallback(JSC::JSValue callback) {
m_uncaughtExceptionCaptureCallback.set(vm(), this, callback);
}

inline JSC::JSValue getUncaughtExceptionCaptureCallback() {
return m_uncaughtExceptionCaptureCallback.get();
}

inline Structure* cpuUsageStructure() { return m_cpuUsageStructure.getInitializedOnMainThread(this); }
inline Structure* memoryUsageStructure() { return m_memoryUsageStructure.getInitializedOnMainThread(this); }
inline JSObject* bindingUV() { return m_bindingUV.getInitializedOnMainThread(this); }
Expand Down
10 changes: 10 additions & 0 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2487,6 +2487,10 @@ pub const JSInternalPromise = extern struct {
return cppFn("create", .{globalThis});
}

pub fn asValue(this: *JSInternalPromise) JSValue {
return JSValue.fromCell(this);
}

pub const Extern = [_][]const u8{
"create",
// "then_",
Expand Down Expand Up @@ -2558,6 +2562,12 @@ pub const AnyPromise = union(enum) {
inline else => |promise| promise.rejectAsHandledException(globalThis, value),
}
}
pub fn asValue(this: AnyPromise, globalThis: *JSGlobalObject) JSValue {
return switch (this) {
.Normal => |promise| promise.asValue(globalThis),
.Internal => |promise| promise.asValue(),
};
}
};

// SourceProvider.h
Expand Down
2 changes: 1 addition & 1 deletion src/bun.js/event_loop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,7 @@ pub const EventLoop = struct {
const result = callback.callWithThis(globalObject, thisValue, arguments);

if (result.toError()) |err| {
this.virtual_machine.onError(globalObject, err);
_ = this.virtual_machine.uncaughtException(globalObject, err, false);
}
}

Expand Down
Loading
Loading