diff options
20 files changed, 444 insertions, 137 deletions
diff --git a/packages/bun-types/sqlite.d.ts b/packages/bun-types/sqlite.d.ts index e1b8adcd0..c5b1af1c8 100644 --- a/packages/bun-types/sqlite.d.ts +++ b/packages/bun-types/sqlite.d.ts @@ -235,6 +235,7 @@ declare module "bun:sqlite" { ParamsType extends SQLQueryBindings | SQLQueryBindings[], >( sqlQuery: string, + params?: ParamsType ): Statement< ReturnType, ParamsType extends Array<any> ? ParamsType : [ParamsType] diff --git a/src/bun.js/bindings/JSBundlerPlugin.cpp b/src/bun.js/bindings/JSBundlerPlugin.cpp index d55c5fc2e..ec3933574 100644 --- a/src/bun.js/bindings/JSBundlerPlugin.cpp +++ b/src/bun.js/bindings/JSBundlerPlugin.cpp @@ -157,7 +157,8 @@ public: JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction> setupFunction; private: - JSBundlerPlugin(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure, void* config, BunPluginTarget target, JSBundlerPluginAddErrorCallback addError, JSBundlerPluginOnLoadAsyncCallback onLoadAsync, JSBundlerPluginOnResolveAsyncCallback onResolveAsync) + JSBundlerPlugin(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure, void* config, BunPluginTarget target, + JSBundlerPluginAddErrorCallback addError, JSBundlerPluginOnLoadAsyncCallback onLoadAsync, JSBundlerPluginOnResolveAsyncCallback onResolveAsync) : JSC::JSNonFinalObject(vm, structure) , plugin(BundlerPlugin(config, target, addError, onLoadAsync, onResolveAsync)) { diff --git a/src/bun.js/bindings/Process.cpp b/src/bun.js/bindings/Process.cpp index 7d7bdd982..f9fb85b95 100644 --- a/src/bun.js/bindings/Process.cpp +++ b/src/bun.js/bindings/Process.cpp @@ -437,91 +437,215 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionChdir, return JSC::JSValue::encode(result); } -// static const NeverDestroyed<String> signalNames[] = { -// MAKE_STATIC_STRING_IMPL("SIGHUP"), -// MAKE_STATIC_STRING_IMPL("SIGINT"), -// MAKE_STATIC_STRING_IMPL("SIGQUIT"), -// MAKE_STATIC_STRING_IMPL("SIGILL"), -// MAKE_STATIC_STRING_IMPL("SIGTRAP"), -// MAKE_STATIC_STRING_IMPL("SIGABRT"), -// MAKE_STATIC_STRING_IMPL("SIGIOT"), -// MAKE_STATIC_STRING_IMPL("SIGBUS"), -// MAKE_STATIC_STRING_IMPL("SIGFPE"), -// MAKE_STATIC_STRING_IMPL("SIGKILL"), -// MAKE_STATIC_STRING_IMPL("SIGUSR1"), -// MAKE_STATIC_STRING_IMPL("SIGSEGV"), -// MAKE_STATIC_STRING_IMPL("SIGUSR2"), -// MAKE_STATIC_STRING_IMPL("SIGPIPE"), -// MAKE_STATIC_STRING_IMPL("SIGALRM"), -// MAKE_STATIC_STRING_IMPL("SIGTERM"), -// MAKE_STATIC_STRING_IMPL("SIGCHLD"), -// MAKE_STATIC_STRING_IMPL("SIGCONT"), -// MAKE_STATIC_STRING_IMPL("SIGSTOP"), -// MAKE_STATIC_STRING_IMPL("SIGTSTP"), -// MAKE_STATIC_STRING_IMPL("SIGTTIN"), -// MAKE_STATIC_STRING_IMPL("SIGTTOU"), -// MAKE_STATIC_STRING_IMPL("SIGURG"), -// MAKE_STATIC_STRING_IMPL("SIGXCPU"), -// MAKE_STATIC_STRING_IMPL("SIGXFSZ"), -// MAKE_STATIC_STRING_IMPL("SIGVTALRM"), -// MAKE_STATIC_STRING_IMPL("SIGPROF"), -// MAKE_STATIC_STRING_IMPL("SIGWINCH"), -// MAKE_STATIC_STRING_IMPL("SIGIO"), -// MAKE_STATIC_STRING_IMPL("SIGINFO"), -// MAKE_STATIC_STRING_IMPL("SIGSYS"), -// }; -// static const int signalNumbers[] = { -// SIGHUP, -// SIGINT, -// SIGQUIT, -// SIGILL, -// SIGTRAP, -// SIGABRT, -// SIGIOT, -// SIGBUS, -// SIGFPE, -// SIGKILL, -// SIGUSR1, -// SIGSEGV, -// SIGUSR2, -// SIGPIPE, -// SIGALRM, -// SIGTERM, -// SIGCHLD, -// SIGCONT, -// SIGSTOP, -// SIGTSTP, -// SIGTTIN, -// SIGTTOU, -// SIGURG, -// SIGXCPU, -// SIGXFSZ, -// SIGVTALRM, -// SIGPROF, -// SIGWINCH, -// SIGIO, -// SIGINFO, -// SIGSYS, -// }; - -// JSC_DEFINE_HOST_FUNCTION(jsFunctionProcessOn, (JSGlobalObject * globalObject, CallFrame* callFrame)) -// { -// VM& vm = globalObject->vm(); -// auto scope = DECLARE_THROW_SCOPE(vm); - -// if (callFrame->argumentCount() < 2) { -// throwVMError(globalObject, scope, "Not enough arguments"_s); -// return JSValue::encode(jsUndefined()); -// } - -// String eventName = callFrame->uncheckedArgument(0).toWTFString(globalObject); -// RETURN_IF_EXCEPTION(scope, encodedJSValue()); -// } +static HashMap<String, int>* signalNameToNumberMap = nullptr; +static HashMap<int, String>* signalNumberToNameMap = nullptr; + +// signal number to array of script execution context ids that care about the signal +static HashMap<int, HashSet<uint32_t>>* signalToContextIdsMap = nullptr; +static Lock signalToContextIdsMapLock; + +static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& eventName, bool isAdded) +{ + + static const NeverDestroyed<String> signalNames[] = { + MAKE_STATIC_STRING_IMPL("SIGHUP"), + MAKE_STATIC_STRING_IMPL("SIGINT"), + MAKE_STATIC_STRING_IMPL("SIGQUIT"), + MAKE_STATIC_STRING_IMPL("SIGILL"), + MAKE_STATIC_STRING_IMPL("SIGTRAP"), + MAKE_STATIC_STRING_IMPL("SIGABRT"), + MAKE_STATIC_STRING_IMPL("SIGIOT"), + MAKE_STATIC_STRING_IMPL("SIGBUS"), + MAKE_STATIC_STRING_IMPL("SIGFPE"), + MAKE_STATIC_STRING_IMPL("SIGKILL"), + MAKE_STATIC_STRING_IMPL("SIGUSR1"), + MAKE_STATIC_STRING_IMPL("SIGSEGV"), + MAKE_STATIC_STRING_IMPL("SIGUSR2"), + MAKE_STATIC_STRING_IMPL("SIGPIPE"), + MAKE_STATIC_STRING_IMPL("SIGALRM"), + MAKE_STATIC_STRING_IMPL("SIGTERM"), + MAKE_STATIC_STRING_IMPL("SIGCHLD"), + MAKE_STATIC_STRING_IMPL("SIGCONT"), + MAKE_STATIC_STRING_IMPL("SIGSTOP"), + MAKE_STATIC_STRING_IMPL("SIGTSTP"), + MAKE_STATIC_STRING_IMPL("SIGTTIN"), + MAKE_STATIC_STRING_IMPL("SIGTTOU"), + MAKE_STATIC_STRING_IMPL("SIGURG"), + MAKE_STATIC_STRING_IMPL("SIGXCPU"), + MAKE_STATIC_STRING_IMPL("SIGXFSZ"), + MAKE_STATIC_STRING_IMPL("SIGVTALRM"), + MAKE_STATIC_STRING_IMPL("SIGPROF"), + MAKE_STATIC_STRING_IMPL("SIGWINCH"), + MAKE_STATIC_STRING_IMPL("SIGIO"), + MAKE_STATIC_STRING_IMPL("SIGINFO"), + MAKE_STATIC_STRING_IMPL("SIGSYS"), + }; + + static std::once_flag signalNameToNumberMapOnceFlag; + std::call_once(signalNameToNumberMapOnceFlag, [] { + signalNameToNumberMap = new HashMap<String, int>(); + signalNameToNumberMap->reserveInitialCapacity(31); + signalNameToNumberMap->add(signalNames[0], SIGHUP); + signalNameToNumberMap->add(signalNames[1], SIGINT); + signalNameToNumberMap->add(signalNames[2], SIGQUIT); + signalNameToNumberMap->add(signalNames[3], SIGILL); + signalNameToNumberMap->add(signalNames[4], SIGTRAP); + signalNameToNumberMap->add(signalNames[5], SIGABRT); + signalNameToNumberMap->add(signalNames[6], SIGIOT); + signalNameToNumberMap->add(signalNames[7], SIGBUS); + signalNameToNumberMap->add(signalNames[8], SIGFPE); + // signalNameToNumberMap->add(signalNames[9], SIGKILL); + signalNameToNumberMap->add(signalNames[10], SIGUSR1); + signalNameToNumberMap->add(signalNames[11], SIGSEGV); + signalNameToNumberMap->add(signalNames[12], SIGUSR2); + signalNameToNumberMap->add(signalNames[13], SIGPIPE); + signalNameToNumberMap->add(signalNames[14], SIGALRM); + signalNameToNumberMap->add(signalNames[15], SIGTERM); + signalNameToNumberMap->add(signalNames[16], SIGCHLD); + signalNameToNumberMap->add(signalNames[17], SIGCONT); + // signalNameToNumberMap->add(signalNames[18], SIGSTOP); + signalNameToNumberMap->add(signalNames[19], SIGTSTP); + signalNameToNumberMap->add(signalNames[20], SIGTTIN); + signalNameToNumberMap->add(signalNames[21], SIGTTOU); + signalNameToNumberMap->add(signalNames[22], SIGURG); + signalNameToNumberMap->add(signalNames[23], SIGXCPU); + signalNameToNumberMap->add(signalNames[24], SIGXFSZ); + signalNameToNumberMap->add(signalNames[25], SIGVTALRM); + signalNameToNumberMap->add(signalNames[26], SIGPROF); + signalNameToNumberMap->add(signalNames[27], SIGWINCH); + signalNameToNumberMap->add(signalNames[28], SIGIO); +#ifdef SIGINFO + signalNameToNumberMap->add(signalNames[29], SIGINFO); +#endif + +#ifndef SIGINFO + signalNameToNumberMap->add(signalNames[29], 255); +#endif + signalNameToNumberMap->add(signalNames[30], SIGSYS); + }); + + static std::once_flag signalNumberToNameMapOnceFlag; + std::call_once(signalNumberToNameMapOnceFlag, [] { + signalNumberToNameMap = new HashMap<int, String>(); + signalNumberToNameMap->reserveInitialCapacity(31); + signalNumberToNameMap->add(SIGHUP, signalNames[0]); + signalNumberToNameMap->add(SIGINT, signalNames[1]); + signalNumberToNameMap->add(SIGQUIT, signalNames[2]); + signalNumberToNameMap->add(SIGILL, signalNames[3]); + signalNumberToNameMap->add(SIGTRAP, signalNames[4]); + signalNumberToNameMap->add(SIGABRT, signalNames[5]); + signalNumberToNameMap->add(SIGIOT, signalNames[6]); + signalNumberToNameMap->add(SIGBUS, signalNames[7]); + signalNumberToNameMap->add(SIGFPE, signalNames[8]); + // signalNumberToNameMap->add(SIGKILL, signalNames[9]); + signalNumberToNameMap->add(SIGUSR1, signalNames[10]); + signalNumberToNameMap->add(SIGSEGV, signalNames[11]); + signalNumberToNameMap->add(SIGUSR2, signalNames[12]); + signalNumberToNameMap->add(SIGPIPE, signalNames[13]); + signalNumberToNameMap->add(SIGALRM, signalNames[14]); + signalNumberToNameMap->add(SIGTERM, signalNames[15]); + signalNumberToNameMap->add(SIGCHLD, signalNames[16]); + signalNumberToNameMap->add(SIGCONT, signalNames[17]); + // signalNumberToNameMap->add(SIGSTOP, signalNames[18]); + signalNumberToNameMap->add(SIGTSTP, signalNames[19]); + signalNumberToNameMap->add(SIGTTIN, signalNames[20]); + signalNumberToNameMap->add(SIGTTOU, signalNames[21]); + signalNumberToNameMap->add(SIGURG, signalNames[22]); + signalNumberToNameMap->add(SIGXCPU, signalNames[23]); + signalNumberToNameMap->add(SIGXFSZ, signalNames[24]); + signalNumberToNameMap->add(SIGVTALRM, signalNames[25]); + signalNumberToNameMap->add(SIGPROF, signalNames[26]); + signalNumberToNameMap->add(SIGWINCH, signalNames[27]); + signalNumberToNameMap->add(SIGIO, signalNames[28]); +#ifdef SIGINFO + signalNameToNumberMap->add(signalNames[29], SIGINFO); +#endif + signalNumberToNameMap->add(SIGSYS, signalNames[30]); + }); + + if (!signalToContextIdsMap) { + signalToContextIdsMap = new HashMap<int, HashSet<uint32_t>>(); + } + + if (isAdded) { + if (auto signalNumber = signalNameToNumberMap->get(eventName.string())) { + uint32_t contextId = eventEmitter.scriptExecutionContext()->identifier(); + Locker lock { signalToContextIdsMapLock }; + if (!signalToContextIdsMap->contains(signalNumber)) { + HashSet<uint32_t> contextIds; + contextIds.add(contextId); + signalToContextIdsMap->set(signalNumber, contextIds); + + lock.unlockEarly(); + + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + + // Set the handler in the action struct + action.sa_handler = [](int signalNumber) { + if (UNLIKELY(signalNumberToNameMap->find(signalNumber) == signalNumberToNameMap->end())) + return; + + Locker lock { signalToContextIdsMapLock }; + if (UNLIKELY(signalToContextIdsMap->find(signalNumber) == signalToContextIdsMap->end())) + return; + auto contextIds = signalToContextIdsMap->get(signalNumber); + + for (int contextId : contextIds) { + auto* context = ScriptExecutionContext::getScriptExecutionContext(contextId); + if (UNLIKELY(!context)) + continue; + + JSGlobalObject* lexicalGlobalObject = context->jsGlobalObject(); + Zig::GlobalObject* globalObject = static_cast<Zig::GlobalObject*>(lexicalGlobalObject); + + Process* process = jsCast<Process*>(globalObject->processObject()); + + context->postCrossThreadTask(*process, &Process::emitSignalEvent, signalNumber); + } + }; + + // Clear the sa_mask + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, signalNumber); + action.sa_flags = SA_RESTART; + + sigaction(signalNumber, &action, nullptr); + } else { + auto contextIds = signalToContextIdsMap->get(signalNumber); + contextIds.add(contextId); + signalToContextIdsMap->set(signalNumber, contextIds); + } + } + } else { + if (auto signalNumber = signalNameToNumberMap->get(eventName.string())) { + uint32_t contextId = eventEmitter.scriptExecutionContext()->identifier(); + Locker lock { signalToContextIdsMapLock }; + if (signalToContextIdsMap->find(signalNumber) != signalToContextIdsMap->end()) { + HashSet<uint32_t> contextIds = signalToContextIdsMap->get(signalNumber); + contextIds.remove(contextId); + if (contextIds.isEmpty()) { + signal(signalNumber, SIG_DFL); + signalToContextIdsMap->remove(signalNumber); + } else { + signalToContextIdsMap->set(signalNumber, contextIds); + } + } + } + } +} + +void Process::emitSignalEvent(int signalNumber) +{ + String signalName = signalNumberToNameMap->get(signalNumber); + Identifier signalNameIdentifier = Identifier::fromString(vm(), signalName); + MarkedArgumentBuffer args; + args.append(jsNumber(signalNumber)); + wrapped().emitForBindings(signalNameIdentifier, args); +} Process::~Process() { - for (auto& listener : this->wrapped().eventListenerMap().entries()) { - } } JSC_DEFINE_HOST_FUNCTION(Process_functionAbort, (JSGlobalObject * globalObject, CallFrame*)) @@ -1550,6 +1674,8 @@ void Process::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); + this->wrapped().onDidChangeListener = &onDidChangeListeners; + this->cpuUsageStructure.initLater([](const JSC::LazyProperty<JSC::JSObject, JSC::Structure>::Initializer& init) { init.set(constructCPUUsageStructure(init.vm, init.owner->globalObject())); }); diff --git a/src/bun.js/bindings/Process.h b/src/bun.js/bindings/Process.h index fbad9b1ff..0ee6f4243 100644 --- a/src/bun.js/bindings/Process.h +++ b/src/bun.js/bindings/Process.h @@ -19,6 +19,8 @@ public: { } + void emitSignalEvent(int signalNumber); + DECLARE_EXPORT_INFO; static void destroy(JSC::JSCell* cell) diff --git a/src/bun.js/bindings/ScriptExecutionContext.cpp b/src/bun.js/bindings/ScriptExecutionContext.cpp index e8cae5e33..3262bdb5d 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.cpp +++ b/src/bun.js/bindings/ScriptExecutionContext.cpp @@ -20,6 +20,12 @@ static HashMap<ScriptExecutionContextIdentifier, ScriptExecutionContext*>& allSc return contexts; } +ScriptExecutionContext* ScriptExecutionContext::getScriptExecutionContext(ScriptExecutionContextIdentifier identifier) +{ + Locker locker { allScriptExecutionContextsMapLock }; + return allScriptExecutionContextsMap().get(identifier); +} + template<bool SSL, bool isServer> static void registerHTTPContextForWebSocket(ScriptExecutionContext* script, us_socket_context_t* ctx, us_loop_t* loop) { diff --git a/src/bun.js/bindings/ScriptExecutionContext.h b/src/bun.js/bindings/ScriptExecutionContext.h index 5f6c56a90..aed7977a5 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.h +++ b/src/bun.js/bindings/ScriptExecutionContext.h @@ -96,7 +96,12 @@ public: } } - const WTF::URL& url() const { return m_url; } + static ScriptExecutionContext* getScriptExecutionContext(ScriptExecutionContextIdentifier identifier); + + const WTF::URL& url() const + { + return m_url; + } bool activeDOMObjectsAreSuspended() { return false; } bool activeDOMObjectsAreStopped() { return false; } bool isContextThread() { return true; } @@ -141,6 +146,7 @@ public: auto* task = new EventLoopTask(WTFMove(lambda)); postTaskOnTimeout(task, timeout); } + template<typename... Arguments> void postCrossThreadTask(Arguments&&... arguments) { diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 9f9b20c1e..d311072e4 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2775,8 +2775,18 @@ void JSC__JSValue__put(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, const Z bool JSC__JSValue__isClass(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1) { - JSC::JSValue value = JSC::JSValue::decode(JSValue0); - return value.isConstructor(); + JSValue value = JSValue::decode(JSValue0); + auto callData = getCallData(value); + + switch (callData.type) { + case CallData::Type::JS: + return callData.js.functionExecutable->isClassConstructorFunction(); + case CallData::Type::Native: + if (callData.native.isBoundFunction) + return false; + return value.isConstructor(); + } + return false; } bool JSC__JSValue__isCell(JSC__JSValue JSValue0) { return JSC::JSValue::decode(JSValue0).isCell(); } bool JSC__JSValue__isCustomGetterSetter(JSC__JSValue JSValue0) diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 73a26e4be..e9e9d3a8d 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -460,7 +460,7 @@ pub const ZigStackTrace = extern struct { var source_line_len = source_lines_iter.getLength(); if (source_line_len > 0) { - var source_lines = try allocator.alloc(Api.SourceLine, @intCast(usize, @max(source_lines_iter.i, 0))); + var source_lines = try allocator.alloc(Api.SourceLine, @intCast(usize, @max(source_lines_iter.i + 1, 0))); var source_line_buf = try allocator.alloc(u8, source_line_len); source_lines_iter = this.sourceLineIterator(); var remain_buf = source_line_buf[0..]; @@ -468,7 +468,7 @@ pub const ZigStackTrace = extern struct { while (source_lines_iter.next()) |source| { const text = source.text.slice(); defer source.text.deinit(); - defer bun.copy( + bun.copy( u8, remain_buf, text, @@ -515,7 +515,7 @@ pub const ZigStackTrace = extern struct { pub fn getLength(this: *SourceLineIterator) usize { var count: usize = 0; - for (this.trace.source_lines_ptr[0..@intCast(usize, this.i)]) |*line| { + for (this.trace.source_lines_ptr[0..@intCast(usize, this.i + 1)]) |*line| { count += line.length(); } @@ -1417,23 +1417,20 @@ pub const ZigConsoleClient = struct { // If we check an Object has a method table and it does not // it will crash - const callable = js_type != .Object and value.isCallable(globalThis.vm()); - - if (value.isClass(globalThis) and !callable) { - return .{ - .tag = .Object, - .cell = js_type, - }; - } + if (js_type != .Object and value.isCallable(globalThis.vm())) { + if (value.isClass(globalThis)) { + return .{ + .tag = .Class, + .cell = js_type, + }; + } - if (callable and js_type == .JSFunction) { - return .{ - .tag = .Function, - .cell = js_type, - }; - } else if (callable and js_type == .InternalFunction) { return .{ - .tag = .Object, + // TODO: we print InternalFunction as Object because we have a lot of + // callable namespaces and printing the contents of it is better than [Function: namespace] + // ideally, we would print [Function: namespace] { ... } on all functions, internal and js. + // what we'll do later is rid of .Function and .Class and handle the prefix in the .Object formatter + .tag = if (js_type == .InternalFunction) .Object else .Function, .cell = js_type, }; } @@ -1756,7 +1753,7 @@ pub const ZigConsoleClient = struct { parent: JSValue, const enable_ansi_colors = enable_ansi_colors_; pub fn handleFirstProperty(this: *@This(), globalThis: *JSC.JSGlobalObject, value: JSValue) void { - if (!value.jsType().isFunction() and !value.isClass(globalThis)) { + if (!value.jsType().isFunction()) { var writer = WrappedWriter(Writer){ .ctx = this.writer, .failed = false, @@ -2094,9 +2091,9 @@ pub const ZigConsoleClient = struct { this.addForNewLine(printable.len); if (printable.len == 0) { - writer.print(comptime Output.prettyFmt("[class]", enable_ansi_colors), .{}); + writer.print(comptime Output.prettyFmt("<cyan>[class]<r>", enable_ansi_colors), .{}); } else { - writer.print(comptime Output.prettyFmt("[class <cyan>{}<r>]", enable_ansi_colors), .{printable}); + writer.print(comptime Output.prettyFmt("<cyan>[class {}]<r>", enable_ansi_colors), .{printable}); } }, .Function => { @@ -2106,7 +2103,7 @@ pub const ZigConsoleClient = struct { if (printable.len == 0) { writer.print(comptime Output.prettyFmt("<cyan>[Function]<r>", enable_ansi_colors), .{}); } else { - writer.print(comptime Output.prettyFmt("<cyan>[Function<d>:<r> <cyan>{}]<r>", enable_ansi_colors), .{printable}); + writer.print(comptime Output.prettyFmt("<cyan>[Function: {}]<r>", enable_ansi_colors), .{printable}); } }, .Getter => { @@ -2802,7 +2799,7 @@ pub const ZigConsoleClient = struct { } if (iter.i == 0) { - if (value.isClass(this.globalThis) and !value.isCallable(this.globalThis.vm())) + if (value.isClass(this.globalThis)) this.printAs(.Class, Writer, writer_, value, jsType, enable_ansi_colors) else if (value.isCallable(this.globalThis.vm())) this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors) diff --git a/src/bun.js/bindings/webcore/EventEmitter.cpp b/src/bun.js/bindings/webcore/EventEmitter.cpp index 0650d624c..0e273042b 100644 --- a/src/bun.js/bindings/webcore/EventEmitter.cpp +++ b/src/bun.js/bindings/webcore/EventEmitter.cpp @@ -35,6 +35,8 @@ bool EventEmitter::addListener(const Identifier& eventType, Ref<EventListener>&& } eventListenersDidChange(); + if (this->onDidChangeListener) + this->onDidChangeListener(*this, eventType, true); return true; } @@ -62,6 +64,9 @@ bool EventEmitter::removeListener(const Identifier& eventType, EventListener& li if (data->eventListenerMap.remove(eventType, listener)) { eventListenersDidChange(); + + if (this->onDidChangeListener) + this->onDidChangeListener(*this, eventType, false); return true; } return false; @@ -93,6 +98,8 @@ bool EventEmitter::removeAllListeners(const Identifier& eventType) if (data->eventListenerMap.removeAll(eventType)) { eventListenersDidChange(); + if (this->onDidChangeListener) + this->onDidChangeListener(*this, eventType, false); return true; } return false; diff --git a/src/bun.js/bindings/webcore/EventEmitter.h b/src/bun.js/bindings/webcore/EventEmitter.h index b46bcff5d..8db59c188 100644 --- a/src/bun.js/bindings/webcore/EventEmitter.h +++ b/src/bun.js/bindings/webcore/EventEmitter.h @@ -67,6 +67,8 @@ public: bool hasActiveEventListeners(const Identifier& eventType) const; bool hasEventListeners(JSC::VM& vm, ASCIILiteral eventType) const; + WTF::Function<void(EventEmitter&, const Identifier& eventName, bool isAdded)> onDidChangeListener = WTF::Function<void(EventEmitter&, const Identifier& eventName, bool isAdded)>(nullptr); + unsigned getMaxListeners() const { return m_maxListeners; }; void setMaxListeners(unsigned count); @@ -101,7 +103,9 @@ private: EventEmitterData* eventTargetData() { return &m_eventTargetData; } EventEmitterData* eventTargetDataConcurrently() { return &m_eventTargetData; } EventEmitterData& ensureEventEmitterData() { return m_eventTargetData; } - void eventListenersDidChange() {} + void eventListenersDidChange() + { + } void innerInvokeEventListeners(const Identifier&, SimpleEventListenerVector, const MarkedArgumentBuffer& arguments); void invalidateEventListenerRegions(); diff --git a/src/bun.js/bindings/webcore/JSEventEmitter.cpp b/src/bun.js/bindings/webcore/JSEventEmitter.cpp index 231ae0db4..959cbd8d7 100644 --- a/src/bun.js/bindings/webcore/JSEventEmitter.cpp +++ b/src/bun.js/bindings/webcore/JSEventEmitter.cpp @@ -219,7 +219,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsEventEmitterConstructor, (JSGlobalObject * lexicalGlo return JSValue::encode(JSEventEmitter::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject())); } -static inline JSC::EncodedJSValue addListener(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSEventEmitter>::ClassParameter castedThis, bool once, bool prepend) +inline JSC::EncodedJSValue JSEventEmitter::addListener(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, JSEventEmitter* castedThis, bool once, bool prepend) { auto& vm = JSC::getVM(lexicalGlobalObject); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -251,7 +251,7 @@ static inline JSC::EncodedJSValue addListener(JSC::JSGlobalObject* lexicalGlobal static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_addListenerBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSEventEmitter>::ClassParameter castedThis) { - return addListener(lexicalGlobalObject, callFrame, castedThis, false, false); + return JSEventEmitter::addListener(lexicalGlobalObject, callFrame, castedThis, false, false); } static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_setMaxListenersBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSEventEmitter>::ClassParameter castedThis) @@ -280,17 +280,17 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_getMaxListener static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_addOnceListenerBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSEventEmitter>::ClassParameter castedThis) { - return addListener(lexicalGlobalObject, callFrame, castedThis, true, false); + return JSEventEmitter::addListener(lexicalGlobalObject, callFrame, castedThis, true, false); } static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_prependListenerBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSEventEmitter>::ClassParameter castedThis) { - return addListener(lexicalGlobalObject, callFrame, castedThis, false, true); + return JSEventEmitter::addListener(lexicalGlobalObject, callFrame, castedThis, false, true); } static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_prependOnceListenerBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSEventEmitter>::ClassParameter castedThis) { - return addListener(lexicalGlobalObject, callFrame, castedThis, true, true); + return JSEventEmitter::addListener(lexicalGlobalObject, callFrame, castedThis, true, true); } JSC_DEFINE_HOST_FUNCTION(jsEventEmitterPrototypeFunction_addListener, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) @@ -325,6 +325,11 @@ JSC_DEFINE_HOST_FUNCTION(jsEventEmitterPrototypeFunction_prependOnceListener, (J static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_removeListenerBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSEventEmitter>::ClassParameter castedThis) { + return JSEventEmitter::removeListener(lexicalGlobalObject, callFrame, castedThis); +} + +inline JSC::EncodedJSValue JSEventEmitter::removeListener(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, JSEventEmitter* castedThis) +{ auto& vm = JSC::getVM(lexicalGlobalObject); auto throwScope = DECLARE_THROW_SCOPE(vm); JSC::JSValue actualThis = callFrame->thisValue(); diff --git a/src/bun.js/bindings/webcore/JSEventEmitter.h b/src/bun.js/bindings/webcore/JSEventEmitter.h index 855241011..30d62d792 100644 --- a/src/bun.js/bindings/webcore/JSEventEmitter.h +++ b/src/bun.js/bindings/webcore/JSEventEmitter.h @@ -27,6 +27,9 @@ public: static EventEmitter* toWrapped(JSC::VM&, JSC::JSValue); static void destroy(JSC::JSCell*); + static inline JSC::EncodedJSValue addListener(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, JSEventEmitter* castedThis, bool once, bool prepend); + static inline JSC::EncodedJSValue removeListener(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, JSEventEmitter* castedThis); + DECLARE_INFO; static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index 4a245c3bb..a6c6aa631 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -423,23 +423,20 @@ pub const JestPrettyFormat = struct { // If we check an Object has a method table and it does not // it will crash - const callable = js_type != .Object and value.isCallable(globalThis.vm()); + if (js_type != .Object and value.isCallable(globalThis.vm())) { + if (value.isClass(globalThis)) { + return .{ + .tag = .Class, + .cell = js_type, + }; + } - if (value.isClass(globalThis) and !callable) { return .{ - .tag = .Object, - .cell = js_type, - }; - } - - if (callable and js_type == .JSFunction) { - return .{ - .tag = .Function, - .cell = js_type, - }; - } else if (callable and js_type == .InternalFunction) { - return .{ - .tag = .Object, + // TODO: we print InternalFunction as Object because we have a lot of + // callable namespaces and printing the contents of it is better than [Function: namespace] + // ideally, we would print [Function: namespace] { ... } on all functions, internal and js. + // what we'll do later is rid of .Function and .Class and handle the prefix in the .Object formatter + .tag = if (js_type == .InternalFunction) .Object else .Function, .cell = js_type, }; } @@ -750,7 +747,7 @@ pub const JestPrettyFormat = struct { parent: JSValue, const enable_ansi_colors = enable_ansi_colors_; pub fn handleFirstProperty(this: *@This(), globalThis: *JSC.JSGlobalObject, value: JSValue) void { - if (!value.jsType().isFunction() and !value.isClass(globalThis)) { + if (!value.jsType().isFunction()) { var writer = WrappedWriter(Writer){ .ctx = this.writer, .failed = false, @@ -1126,13 +1123,20 @@ pub const JestPrettyFormat = struct { this.addForNewLine(printable.len); if (printable.len == 0) { - writer.print(comptime Output.prettyFmt("[class]", enable_ansi_colors), .{}); + writer.print(comptime Output.prettyFmt("<cyan>[class]<r>", enable_ansi_colors), .{}); } else { - writer.print(comptime Output.prettyFmt("[class <cyan>{}<r>]", enable_ansi_colors), .{printable}); + writer.print(comptime Output.prettyFmt("<cyan>[class {}]<r>", enable_ansi_colors), .{printable}); } }, .Function => { - writer.writeAll("[Function]"); + var printable = ZigString.init(&name_buf); + value.getNameProperty(this.globalThis, &printable); + + if (printable.len == 0) { + writer.print(comptime Output.prettyFmt("<cyan>[Function]<r>", enable_ansi_colors), .{}); + } else { + writer.print(comptime Output.prettyFmt("<cyan>[Function: {}]<r>", enable_ansi_colors), .{printable}); + } }, .Array => { const len = @truncate(u32, value.getLength(this.globalThis)); diff --git a/test/js/bun/http/error-response.js b/test/js/bun/http/error-response.js new file mode 100644 index 000000000..3284c146b --- /dev/null +++ b/test/js/bun/http/error-response.js @@ -0,0 +1,8 @@ +const s = Bun.serve({ + fetch(req, res) { + s.stop(true); + throw new Error("1"); + }, + port: 0, +}); +fetch(`http://${s.hostname}:${s.port}`).then(res => console.log(res.status)); diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts index 7182ba68d..bba35c085 100644 --- a/test/js/bun/http/serve.test.ts +++ b/test/js/bun/http/serve.test.ts @@ -2,8 +2,10 @@ import { file, gc, Serve, serve, Server } from "bun"; import { afterEach, describe, it, expect, afterAll } from "bun:test"; import { readFileSync, writeFileSync } from "fs"; import { resolve } from "path"; +import { bunExe, bunEnv } from "harness"; import { renderToReadableStream } from "react-dom/server"; import app_jsx from "./app.jsx"; +import { spawn } from "child_process"; type Handler = (req: Request) => Response; afterEach(() => gc(true)); @@ -980,6 +982,19 @@ describe("should support Content-Range with Bun.file()", () => { } }); +it("formats error responses correctly", async () => { + const c = spawn(bunExe(), ["./error-response.js"], { cwd: import.meta.dir, env: bunEnv }); + + var output = ""; + c.stderr.on("data", chunk => { + output += chunk.toString(); + }); + c.stderr.on("end", () => { + expect(output).toContain('throw new Error("1");'); + c.kill(); + }); +}); + it("request body and signal life cycle", async () => { { const headers = { diff --git a/test/js/node/process/call-raise.js b/test/js/node/process/call-raise.js new file mode 100644 index 000000000..898906759 --- /dev/null +++ b/test/js/node/process/call-raise.js @@ -0,0 +1,15 @@ +import { dlopen } from "bun:ffi"; + +var lazyRaise; +export function raise(signal) { + if (!lazyRaise) { + const suffix = process.platform === "darwin" ? "dylib" : "so.6"; + lazyRaise = dlopen(`libc.${suffix}`, { + raise: { + args: ["int"], + returns: "int", + }, + }).symbols.raise; + } + lazyRaise(signal); +} diff --git a/test/js/node/process/process-signal-handler.fixture.js b/test/js/node/process/process-signal-handler.fixture.js new file mode 100644 index 000000000..de5a78bda --- /dev/null +++ b/test/js/node/process/process-signal-handler.fixture.js @@ -0,0 +1,63 @@ +import os from "os"; +import { raise } from "./call-raise"; + +var counter = 0; +function done() { + counter++; + if (counter === 2) { + setTimeout(() => { + if (counter !== 2) { + console.log(counter); + console.log("FAIL"); + process.exit(1); + } + + console.log("PASS"); + process.exit(0); + }, 1); + } +} + +var counter2 = 0; +function done2() { + counter2++; + if (counter2 === 2) { + setTimeout(() => { + if (counter2 !== 2) { + console.log(counter2); + console.log("FAIL"); + process.exit(1); + } + + console.log("PASS"); + process.exit(0); + }, 1); + } +} + +const SIGUSR1 = os.constants.signals.SIGUSR1; +const SIGUSR2 = os.constants.signals.SIGUSR2; + +switch (process.argv.at(-1)) { + case "SIGUSR1": { + process.on("SIGUSR1", () => { + done(); + }); + process.on("SIGUSR1", () => { + done(); + }); + raise(SIGUSR1); + break; + } + case "SIGUSR2": { + process.on("SIGUSR2", () => { + done2(); + }); + process.emit("SIGUSR2"); + raise(SIGUSR2); + break; + } + default: { + throw new Error("Unknown argument: " + process.argv.at(-1)); + } +} diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index e038383de..51825b2b4 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -1,6 +1,6 @@ -import { resolveSync, spawnSync, which } from "bun"; +import { spawnSync, which } from "bun"; import { describe, expect, it } from "bun:test"; -import { existsSync, readFileSync, realpathSync } from "fs"; +import { existsSync, readFileSync } from "fs"; import { bunEnv, bunExe } from "harness"; import { basename, join, resolve } from "path"; @@ -381,6 +381,28 @@ it("process.getuid", () => { expect(typeof process.getuid()).toBe("number"); }); +describe("signal", () => { + const fixture = join(import.meta.dir, "./process-signal-handler.fixture.js"); + it("simple case works", async () => { + const child = Bun.spawn({ + cmd: [bunExe(), fixture, "SIGUSR1"], + env: bunEnv, + }); + + expect(await child.exited).toBe(0); + expect(await new Response(child.stdout).text()).toBe("PASS\n"); + }); + it("process.emit will call signal events", async () => { + const child = Bun.spawn({ + cmd: [bunExe(), fixture, "SIGUSR2"], + env: bunEnv, + }); + + expect(await child.exited).toBe(0); + expect(await new Response(child.stdout).text()).toBe("PASS\n"); + }); +}); + const undefinedStubs = [ "_debugEnd", "_debugProcess", diff --git a/test/js/web/console/console-log.expected.txt b/test/js/web/console/console-log.expected.txt index 97191c8be..332322665 100644 --- a/test/js/web/console/console-log.expected.txt +++ b/test/js/web/console/console-log.expected.txt @@ -1,4 +1,6 @@ Hello World! +0 +-0 123 -123 123.567 @@ -7,6 +9,8 @@ true false null undefined +Infinity +-Infinity Symbol(Symbol Description) 2000-06-27T02:24:34.304Z [ 123, 456, 789 ] @@ -29,7 +33,9 @@ Symbol(Symbol Description) } Promise { <pending> } [Function] -[Function: Foo] +[Function] +[class Foo] +[class] {} [Function: foooo] /FooRegex/ diff --git a/test/js/web/console/console-log.js b/test/js/web/console/console-log.js index e23a3e9cb..4db40aaac 100644 --- a/test/js/web/console/console-log.js +++ b/test/js/web/console/console-log.js @@ -1,4 +1,6 @@ console.log("Hello World!"); +console.log(0); +console.log(-0); console.log(123); console.log(-123); console.log(123.567); @@ -7,6 +9,8 @@ console.log(true); console.log(false); console.log(null); console.log(undefined); +console.log(Infinity); +console.log(-Infinity); console.log(Symbol("Symbol Description")); console.log(new Date(Math.pow(2, 34) * 56)); console.log([123, 456, 789]); @@ -27,7 +31,9 @@ console.log(new Promise(() => {})); class Foo {} console.log(() => {}); +console.log(function () {}); console.log(Foo); +console.log(class {}); console.log(new Foo()); console.log(function foooo() {}); |