diff --git a/docs/runtime/nodejs-apis.md b/docs/runtime/nodejs-apis.md index da92af28e49a09..b0e2630b6a0654 100644 --- a/docs/runtime/nodejs-apis.md +++ b/docs/runtime/nodejs-apis.md @@ -341,7 +341,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa ### [`process`](https://nodejs.org/api/process.html) -🟡 Missing `domain` `initgroups` `setegid` `seteuid` `setgid` `setgroups` `setuid` `allowedNodeEnvironmentFlags` `getActiveResourcesInfo` `setActiveResourcesInfo` `moduleLoadList` `setSourceMapsEnabled`. `process.binding` is partially implemented. +🟡 Missing `initgroups` `allowedNodeEnvironmentFlags` `getActiveResourcesInfo` `setActiveResourcesInfo` `moduleLoadList` `setSourceMapsEnabled`. `process.binding` is partially implemented. ### [`queueMicrotask()`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) diff --git a/scripts/check-node-all.sh b/scripts/check-node-all.sh index 9358e55ae97a23..4c907de593e1d6 100755 --- a/scripts/check-node-all.sh +++ b/scripts/check-node-all.sh @@ -25,7 +25,7 @@ esac export BUN_DEBUG_QUIET_LOGS=1 -for x in $(find test/js/node/test/parallel -type f -name "test-$1*.js") +for x in $(find test/js/node/test/parallel -type f -name "test-$1*.js" | sort) do i=$((i+1)) echo ./$x diff --git a/src/api/schema.zig b/src/api/schema.zig index 763d3954f1fcf0..77ed146a3e4417 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -1631,7 +1631,7 @@ pub const Api = struct { origin: ?[]const u8 = null, /// absolute_working_dir - absolute_working_dir: ?[]const u8 = null, + absolute_working_dir: ?[:0]const u8 = null, /// define define: ?StringMap = null, diff --git a/src/bake/DevServer.zig b/src/bake/DevServer.zig index 78aec7e11e6670..5f59b524011936 100644 --- a/src/bake/DevServer.zig +++ b/src/bake/DevServer.zig @@ -13,7 +13,7 @@ pub const igLog = bun.Output.scoped(.IncrementalGraph, false); pub const Options = struct { /// Arena must live until DevServer.deinit() arena: Allocator, - root: []const u8, + root: [:0]const u8, vm: *VirtualMachine, framework: bake.Framework, bundler_options: bake.SplitBundlerOptions, diff --git a/src/bake/bake.zig b/src/bake/bake.zig index eead195a75e82d..3722c8efdc7699 100644 --- a/src/bake/bake.zig +++ b/src/bake/bake.zig @@ -15,7 +15,7 @@ pub const UserOptions = struct { arena: std.heap.ArenaAllocator, allocations: StringRefList, - root: []const u8, + root: [:0]const u8, framework: Framework, bundler_options: SplitBundlerOptions, @@ -78,9 +78,9 @@ pub const UserOptions = struct { const StringRefList = struct { strings: std.ArrayListUnmanaged(ZigString.Slice), - pub fn track(al: *StringRefList, str: ZigString.Slice) []const u8 { + pub fn track(al: *StringRefList, str: ZigString.Slice) [:0]const u8 { al.strings.append(bun.default_allocator, str) catch bun.outOfMemory(); - return str.slice(); + return str.sliceZ(); } pub fn free(al: *StringRefList) void { diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index 9693629256455a..bd1760cfb0a2b8 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -247,7 +247,6 @@ JSC_DEFINE_HOST_FUNCTION(functionConcatTypedArrays, (JSGlobalObject * globalObje auto arg2 = callFrame->argument(2); if (!arg2.isUndefined()) { asUint8Array = arg2.toBoolean(globalObject); - RETURN_IF_EXCEPTION(throwScope, {}); } return flattenArrayOfBuffersIntoArrayBufferOrUint8Array(globalObject, arrayValue, maxLength, asUint8Array); diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 95c5ca6f4f480c..36313623577582 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -4,11 +4,17 @@ #include #include #include "CommonJSModuleRecord.h" +#include "ErrorCode+List.h" +#include "JavaScriptCore/ArgList.h" #include "JavaScriptCore/CallData.h" #include "JavaScriptCore/CatchScope.h" #include "JavaScriptCore/JSCJSValue.h" #include "JavaScriptCore/JSCast.h" +#include "JavaScriptCore/JSMap.h" +#include "JavaScriptCore/JSMapInlines.h" +#include "JavaScriptCore/JSObjectInlines.h" #include "JavaScriptCore/JSString.h" +#include "JavaScriptCore/JSType.h" #include "JavaScriptCore/MathCommon.h" #include "JavaScriptCore/Protect.h" #include "JavaScriptCore/PutPropertySlot.h" @@ -35,7 +41,10 @@ #include #include "ProcessBindingTTYWrap.h" #include "wtf/text/ASCIILiteral.h" +#include "wtf/text/StringToIntegerConversion.h" #include "wtf/text/OrdinalNumber.h" +#include "NodeValidator.h" +#include "NodeModuleModule.h" #include "AsyncContextFrame.h" #include "ErrorCode.h" @@ -51,6 +60,9 @@ #include #include #include +#include +#include +#include #else #include #include @@ -89,6 +101,9 @@ typedef int mode_t; #include // setuid, getuid #endif +extern "C" bool Bun__Node__ProcessNoDeprecation; +extern "C" bool Bun__Node__ProcessThrowDeprecation; + namespace Bun { using namespace JSC; @@ -134,6 +149,8 @@ BUN_DECLARE_HOST_FUNCTION(Bun__Process__send); extern "C" void Process__emitDisconnectEvent(Zig::GlobalObject* global); extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValue value); +bool setProcessExitCodeInner(JSC::JSGlobalObject* lexicalGlobalObject, Process* process, JSValue code); + static JSValue constructArch(VM& vm, JSObject* processObject) { #if CPU(X86_64) @@ -228,13 +245,9 @@ static JSValue constructProcessReleaseObject(VM& vm, JSObject* processObject) auto* globalObject = processObject->globalObject(); auto* release = JSC::constructEmptyObject(globalObject); - // SvelteKit compatibility hack - release->putDirect(vm, vm.propertyNames->name, jsOwnedString(vm, WTF::String("node"_s)), 0); - - release->putDirect(vm, Identifier::fromString(vm, "lts"_s), jsBoolean(false), 0); + release->putDirect(vm, vm.propertyNames->name, jsOwnedString(vm, String("node"_s)), 0); // maybe this should be 'bun' eventually release->putDirect(vm, Identifier::fromString(vm, "sourceUrl"_s), jsOwnedString(vm, WTF::String(std::span { Bun__githubURL, strlen(Bun__githubURL) })), 0); - release->putDirect(vm, Identifier::fromString(vm, "headersUrl"_s), jsEmptyString(vm), 0); - release->putDirect(vm, Identifier::fromString(vm, "libUrl"_s), jsEmptyString(vm), 0); + release->putDirect(vm, Identifier::fromString(vm, "headersUrl"_s), jsOwnedString(vm, String("https://nodejs.org/download/release/v" REPORTED_NODEJS_VERSION "/node-v" REPORTED_NODEJS_VERSION "-headers.tar.gz"_s)), 0); return release; } @@ -262,9 +275,7 @@ static void dispatchExitInternal(JSC::JSGlobalObject* globalObject, Process* pro emitter.emit(event, arguments); } -JSC_DEFINE_CUSTOM_SETTER(Process_defaultSetter, - (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, - JSC::EncodedJSValue value, JSC::PropertyName propertyName)) +JSC_DEFINE_CUSTOM_SETTER(Process_defaultSetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName propertyName)) { JSC::VM& vm = globalObject->vm(); @@ -282,8 +293,7 @@ extern "C" HMODULE Bun__LoadLibraryBunString(BunString*); extern "C" size_t Bun__process_dlopen_count; -JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, - (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) { Zig::GlobalObject* globalObject = reinterpret_cast(globalObject_); auto callCountAtStart = globalObject->napiModuleRegisterCallCount; @@ -376,8 +386,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, #else WTF::String msg = WTF::String::fromUTF8(dlerror()); #endif - JSC::throwTypeError(globalObject, scope, msg); - return {}; + return throwError(globalObject, scope, ErrorCode::ERR_DLOPEN_FAILED, msg); } if (callCountAtStart != globalObject->napiModuleRegisterCallCount) { @@ -463,32 +472,29 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, return JSValue::encode(resultValue); } -JSC_DEFINE_HOST_FUNCTION(Process_functionUmask, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionUmask, (JSGlobalObject * globalObject, CallFrame* callFrame)) { if (callFrame->argumentCount() == 0 || callFrame->argument(0).isUndefined()) { mode_t currentMask = umask(0); umask(currentMask); - return JSC::JSValue::encode(JSC::jsNumber(currentMask)); + return JSValue::encode(jsNumber(currentMask)); } auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); - JSValue numberValue = callFrame->argument(0); - - if (!numberValue.isNumber()) { - return Bun::ERR::INVALID_ARG_TYPE(throwScope, globalObject, "mask"_s, "number"_s, numberValue); - } - - if (!numberValue.isAnyInt()) { - return Bun::ERR::OUT_OF_RANGE(throwScope, globalObject, "mask"_s, "an integer"_s, numberValue); - } - - double number = numberValue.toNumber(globalObject); - int64_t newUmask = isInt52(number) ? tryConvertToInt52(number) : numberValue.toInt32(globalObject); - RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::JSValue {})); - if (newUmask < 0 || newUmask > 4294967295) { - return Bun::ERR::OUT_OF_RANGE(throwScope, globalObject, "mask"_s, 0, 4294967295, numberValue); + auto value = callFrame->argument(0); + + mode_t newUmask; + if (value.isString()) { + auto str = value.getString(globalObject); + auto policy = WTF::TrailingJunkPolicy::Disallow; + auto opt = str.is8Bit() ? WTF::parseInteger(str.span8(), 8, policy) : WTF::parseInteger(str.span16(), 8, policy); + if (!opt.has_value()) return Bun::ERR::INVALID_ARG_VALUE(throwScope, globalObject, "mask"_s, value, "must be a 32-bit unsigned integer or an octal string"_s); + newUmask = opt.value(); + } else { + Bun::V::validateUint32(throwScope, globalObject, value, "mask"_s, jsUndefined()); + RETURN_IF_EXCEPTION(throwScope, {}); + newUmask = value.toUInt32(globalObject); } return JSC::JSValue::encode(JSC::jsNumber(umask(newUmask))); @@ -496,6 +502,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionUmask, extern "C" uint64_t Bun__readOriginTimer(void*); extern "C" double Bun__readOriginTimerStart(void*); +extern "C" void Bun__VirtualMachine__exitDuringUncaughtException(void*); // https://github.com/nodejs/node/blob/1936160c31afc9780e4365de033789f39b7cbc0c/src/api/hooks.cc#L49 extern "C" void Process__dispatchOnBeforeExit(Zig::GlobalObject* globalObject, uint8_t exitCode) @@ -503,11 +510,18 @@ extern "C" void Process__dispatchOnBeforeExit(Zig::GlobalObject* globalObject, u if (!globalObject->hasProcessObject()) { return; } - + auto& vm = globalObject->vm(); auto* process = jsCast(globalObject->processObject()); MarkedArgumentBuffer arguments; arguments.append(jsNumber(exitCode)); - process->wrapped().emit(Identifier::fromString(globalObject->vm(), "beforeExit"_s), arguments); + Bun__VirtualMachine__exitDuringUncaughtException(bunVM(vm)); + auto fired = process->wrapped().emit(Identifier::fromString(vm, "beforeExit"_s), arguments); + if (fired) { + if (globalObject->m_nextTickQueue) { + auto nextTickQueue = jsDynamicCast(globalObject->m_nextTickQueue.get()); + if (nextTickQueue) nextTickQueue->drain(vm, globalObject); + } + } } extern "C" void Process__dispatchOnExit(Zig::GlobalObject* globalObject, uint8_t exitCode) @@ -522,58 +536,65 @@ extern "C" void Process__dispatchOnExit(Zig::GlobalObject* globalObject, uint8_t dispatchExitInternal(globalObject, process, exitCode); } -JSC_DEFINE_HOST_FUNCTION(Process_functionUptime, - (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionUptime, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { double now = static_cast(Bun__readOriginTimer(bunVM(lexicalGlobalObject))); double result = (now / 1000000.0) / 1000.0; return JSC::JSValue::encode(JSC::jsNumber(result)); } -JSC_DEFINE_HOST_FUNCTION(Process_functionExit, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionExit, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); - uint8_t exitCode = 0; - JSValue arg0 = callFrame->argument(0); - if (arg0.isAnyInt()) { - int extiCode32 = arg0.toInt32(globalObject) % 256; - RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::JSValue {})); - - exitCode = static_cast(extiCode32); - Bun__setExitCode(bunVM(globalObject), exitCode); - } else if (!arg0.isUndefinedOrNull()) { - throwTypeError(globalObject, throwScope, "The \"code\" argument must be an integer"_s); - return {}; - } else { - exitCode = Bun__getExitCode(bunVM(globalObject)); - } - + auto& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); auto* zigGlobal = defaultGlobalObject(globalObject); auto process = jsCast(zigGlobal->processObject()); - process->m_isExitCodeObservable = true; + auto code = callFrame->argument(0); + + setProcessExitCodeInner(globalObject, process, code); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto exitCode = Bun__getExitCode(bunVM(zigGlobal)); Process__dispatchOnExit(zigGlobal, exitCode); - Bun__Process__exit(zigGlobal, exitCode); + + // process.reallyExit(exitCode); + auto reallyExitVal = process->get(globalObject, Identifier::fromString(vm, "reallyExit"_s)); + RETURN_IF_EXCEPTION(throwScope, {}); + MarkedArgumentBuffer args; + args.append(jsNumber(exitCode)); + JSC::call(globalObject, reallyExitVal, args, ""_s); + RETURN_IF_EXCEPTION(throwScope, {}); + return JSC::JSValue::encode(jsUndefined()); } -JSC_DEFINE_HOST_FUNCTION(Process_setUncaughtExceptionCaptureCallback, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_setUncaughtExceptionCaptureCallback, (JSC::JSGlobalObject * lexicalGlobalObject, 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 {}; + auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto arg0 = callFrame->argument(0); + auto process = jsCast(globalObject->processObject()); + + if (arg0.isNull()) { + process->setUncaughtExceptionCaptureCallback(arg0); + process->m_reportOnUncaughtException = false; + return JSC::JSValue::encode(jsUndefined()); } - auto* zigGlobal = defaultGlobalObject(globalObject); - jsCast(zigGlobal->processObject())->setUncaughtExceptionCaptureCallback(arg0); + if (!arg0.isCallable()) { + return Bun::ERR::INVALID_ARG_TYPE(throwScope, globalObject, "fn"_s, "function or null"_s, arg0); + } + if (process->m_reportOnUncaughtException) { + return Bun::ERR::UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET(throwScope, globalObject); + } + + process->setUncaughtExceptionCaptureCallback(arg0); + process->m_reportOnUncaughtException = true; return JSC::JSValue::encode(jsUndefined()); } -JSC_DEFINE_HOST_FUNCTION(Process_hasUncaughtExceptionCaptureCallback, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_hasUncaughtExceptionCaptureCallback, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* zigGlobal = defaultGlobalObject(globalObject); JSValue cb = jsCast(zigGlobal->processObject())->getUncaughtExceptionCaptureCallback(); @@ -586,12 +607,9 @@ JSC_DEFINE_HOST_FUNCTION(Process_hasUncaughtExceptionCaptureCallback, extern "C" uint64_t Bun__readOriginTimer(void*); -JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime, - (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime, (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) { - - Zig::GlobalObject* globalObject - = reinterpret_cast(globalObject_); + Zig::GlobalObject* globalObject = reinterpret_cast(globalObject_); auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -599,29 +617,24 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime, int64_t seconds = static_cast(time / 1000000000); int64_t nanoseconds = time % 1000000000; - if (callFrame->argumentCount() > 0) { - JSC::JSValue arg0 = callFrame->uncheckedArgument(0); - if (!arg0.isUndefinedOrNull()) { - JSArray* relativeArray = JSC::jsDynamicCast(arg0); - if ((!relativeArray && !arg0.isUndefinedOrNull()) || relativeArray->length() < 2) { - JSC::throwTypeError(globalObject, throwScope, "hrtime() argument must be an array or undefined"_s); - return {}; - } - JSValue relativeSecondsValue = relativeArray->getIndexQuickly(0); - JSValue relativeNanosecondsValue = relativeArray->getIndexQuickly(1); - if (!relativeSecondsValue.isNumber() || !relativeNanosecondsValue.isNumber()) { - JSC::throwTypeError(globalObject, throwScope, "hrtime() argument must be an array of 2 integers"_s); - return {}; - } - - int64_t relativeSeconds = JSC__JSValue__toInt64(JSC::JSValue::encode(relativeSecondsValue)); - int64_t relativeNanoseconds = JSC__JSValue__toInt64(JSC::JSValue::encode(relativeNanosecondsValue)); - seconds -= relativeSeconds; - nanoseconds -= relativeNanoseconds; - if (nanoseconds < 0) { - seconds--; - nanoseconds += 1000000000; - } + auto arg0 = callFrame->argument(0); + if (callFrame->argumentCount() > 0 && !arg0.isUndefined()) { + JSArray* relativeArray = JSC::jsDynamicCast(arg0); + if (!relativeArray) { + return Bun::ERR::INVALID_ARG_TYPE(throwScope, globalObject, "time"_s, "Array"_s, arg0); + } + if (relativeArray->length() != 2) return Bun::ERR::OUT_OF_RANGE(throwScope, globalObject_, "time"_s, "2"_s, jsNumber(relativeArray->length())); + + JSValue relativeSecondsValue = relativeArray->getIndexQuickly(0); + JSValue relativeNanosecondsValue = relativeArray->getIndexQuickly(1); + + int64_t relativeSeconds = JSC__JSValue__toInt64(JSC::JSValue::encode(relativeSecondsValue)); + int64_t relativeNanoseconds = JSC__JSValue__toInt64(JSC::JSValue::encode(relativeNanosecondsValue)); + seconds -= relativeSeconds; + nanoseconds -= relativeNanoseconds; + if (nanoseconds < 0) { + seconds--; + nanoseconds += 1000000000; } } @@ -646,24 +659,22 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime, RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(array)); } -JSC_DEFINE_HOST_FUNCTION(Process_functionHRTimeBigInt, - (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionHRTimeBigInt, (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) { Zig::GlobalObject* globalObject = reinterpret_cast(globalObject_); return JSC::JSValue::encode(JSValue(JSC::JSBigInt::createFrom(globalObject, Bun__readOriginTimer(globalObject->bunVM())))); } -JSC_DEFINE_HOST_FUNCTION(Process_functionChdir, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionChdir, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - ZigString str = ZigString { nullptr, 0 }; - if (callFrame->argumentCount() > 0) { - str = Zig::toZigString(callFrame->uncheckedArgument(0).toWTFString(globalObject)); - } + auto value = callFrame->argument(0); + Bun::V::validateString(scope, globalObject, value, "directory"_s); + RETURN_IF_EXCEPTION(scope, {}); + ZigString str = Zig::toZigString(value.toWTFString(globalObject)); JSC::JSValue result = JSC::JSValue::decode(Bun__Process__setCwd(globalObject, &str)); RETURN_IF_EXCEPTION(scope, {}); @@ -1112,6 +1123,37 @@ Process::~Process() { } +JSC_DEFINE_HOST_FUNCTION(jsFunction_emitWarning, (Zig::JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto* globalObject = jsCast(lexicalGlobalObject); + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* process = jsCast(globalObject->processObject()); + auto value = callFrame->argument(0); + + auto ident = builtinNames(vm).warningPublicName(); + if (process->wrapped().hasEventListeners(ident)) { + JSC::MarkedArgumentBuffer args; + args.append(value); + process->wrapped().emit(ident, args); + return JSValue::encode(jsUndefined()); + } + + auto jsArgs = JSValue::encode(value); + Bun__ConsoleObject__messageWithTypeAndLevel(reinterpret_cast(globalObject->consoleClient().get())->m_client, static_cast(MessageType::Log), static_cast(MessageLevel::Warning), globalObject, &jsArgs, 1); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_throwValue, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + scope.throwException(globalObject, value); + return {}; +} + JSC_DEFINE_HOST_FUNCTION(Process_functionAbort, (JSGlobalObject * globalObject, CallFrame*)) { #if OS(WINDOWS) @@ -1127,40 +1169,89 @@ JSC_DEFINE_HOST_FUNCTION(Process_emitWarning, (JSGlobalObject * lexicalGlobalObj Zig::GlobalObject* globalObject = jsCast(lexicalGlobalObject); VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + auto* process = jsCast(globalObject->processObject()); - if (callFrame->argumentCount() < 1) { - throwVMError(globalObject, scope, "Not enough arguments"_s); - return {}; + auto warning = callFrame->argument(0); + auto type = callFrame->argument(1); + auto code = callFrame->argument(2); + auto ctor = callFrame->argument(3); + auto detail = jsUndefined(); + + auto dep_warning = jsString(vm, String("DeprecationWarning"_s)); + + if (Bun__Node__ProcessNoDeprecation && JSC::JSValue::strictEqual(globalObject, type, dep_warning)) { + return JSValue::encode(jsUndefined()); } - RETURN_IF_EXCEPTION(scope, {}); + if (!type.isNull() && type.isObject() && !isJSArray(type)) { + ctor = type.get(globalObject, Identifier::fromString(vm, "ctor"_s)); + RETURN_IF_EXCEPTION(scope, {}); - auto* process = jsCast(globalObject->processObject()); + code = type.get(globalObject, builtinNames(vm).codePublicName()); + RETURN_IF_EXCEPTION(scope, {}); - JSObject* errorInstance = ([&]() -> JSObject* { - JSValue arg0 = callFrame->uncheckedArgument(0); - if (!arg0.isEmpty() && arg0.isCell() && arg0.asCell()->type() == ErrorInstanceType) { - return arg0.getObject(); - } + detail = type.get(globalObject, vm.propertyNames->detail); + RETURN_IF_EXCEPTION(scope, {}); + if (!detail.isString()) detail = jsUndefined(); - WTF::String str = arg0.toWTFString(globalObject); - auto err = createError(globalObject, str); - err->putDirect(vm, vm.propertyNames->name, jsString(vm, String("warn"_s)), JSC::PropertyAttribute::DontEnum | 0); - return err; - })(); + type = type.get(globalObject, vm.propertyNames->type); + RETURN_IF_EXCEPTION(scope, {}); + if (!type.toBoolean(globalObject)) type = jsString(vm, String("Warning"_s)); + } else if (type.isCallable()) { + ctor = type; + code = jsUndefined(); + type = jsString(vm, String("Warning"_s)); + } - auto ident = Identifier::fromString(vm, "warning"_s); - if (process->wrapped().hasEventListeners(ident)) { - JSC::MarkedArgumentBuffer args; - args.append(errorInstance); + if (!type.isUndefined()) { + Bun::V::validateString(scope, globalObject, type, "type"_s); + RETURN_IF_EXCEPTION(scope, {}); + } else { + type = jsString(vm, String("Warning"_s)); + } - process->wrapped().emit(ident, args); - return JSValue::encode(jsUndefined()); + if (code.isCallable()) { + ctor = code; + code = jsUndefined(); + } else if (!code.isUndefined()) { + Bun::V::validateString(scope, globalObject, code, "code"_s); + RETURN_IF_EXCEPTION(scope, {}); } - auto jsArgs = JSValue::encode(errorInstance); - Bun__ConsoleObject__messageWithTypeAndLevel(reinterpret_cast(globalObject->consoleClient().get())->m_client, static_cast(MessageType::Log), static_cast(MessageLevel::Warning), globalObject, &jsArgs, 1); - RETURN_IF_EXCEPTION(scope, {}); + JSObject* errorInstance; + + if (warning.isString()) { + auto s = warning.getString(globalObject); + errorInstance = createError(globalObject, !s.isEmpty() ? s : "Warning"_s); + errorInstance->putDirect(vm, vm.propertyNames->name, type, JSC::PropertyAttribute::DontEnum | 0); + } else if (warning.isCell() && warning.asCell()->type() == ErrorInstanceType) { + errorInstance = warning.getObject(); + } else { + return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "warning"_s, "string or Error"_s, warning); + } + + if (!code.isUndefined()) errorInstance->putDirect(vm, builtinNames(vm).codePublicName(), code, JSC::PropertyAttribute::DontEnum | 0); + if (!detail.isUndefined()) errorInstance->putDirect(vm, vm.propertyNames->detail, detail, JSC::PropertyAttribute::DontEnum | 0); + // ErrorCaptureStackTrace(warning, ctor || process.emitWarning); + + if (JSC::JSValue::strictEqual(globalObject, type, dep_warning)) { + if (Bun__Node__ProcessNoDeprecation) { + return JSValue::encode(jsUndefined()); + } + if (Bun__Node__ProcessThrowDeprecation) { + // // Delay throwing the error to guarantee that all former warnings were properly logged. + // return process.nextTick(() => { + // throw warning; + // }); + auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_throwValue, JSC::ImplementationVisibility::Private); + process->queueNextTick(vm, globalObject, func, errorInstance); + return JSValue::encode(jsUndefined()); + } + } + + // process.nextTick(doEmitWarning, warning); + auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_emitWarning, JSC::ImplementationVisibility::Private); + process->queueNextTick(vm, globalObject, func, errorInstance); return JSValue::encode(jsUndefined()); } @@ -1177,28 +1268,39 @@ JSC_DEFINE_CUSTOM_GETTER(processExitCode, (JSC::JSGlobalObject * lexicalGlobalOb return JSValue::encode(jsNumber(Bun__getExitCode(jsCast(process->globalObject())->bunVM()))); } +bool setProcessExitCodeInner(JSC::JSGlobalObject* lexicalGlobalObject, Process* process, JSValue code) +{ + auto throwScope = DECLARE_THROW_SCOPE(process->vm()); + + if (!code.isUndefinedOrNull()) { + if (code.isString() && !code.getString(lexicalGlobalObject).isEmpty()) { + auto num = code.toNumber(lexicalGlobalObject); + if (!std::isnan(num)) { + code = jsDoubleNumber(num); + } + } + Bun::V::validateInteger(throwScope, lexicalGlobalObject, code, "code"_s, jsUndefined(), jsUndefined()); + RETURN_IF_EXCEPTION(throwScope, false); + + int exitCodeInt = code.toInt32(lexicalGlobalObject) % 256; + RETURN_IF_EXCEPTION(throwScope, false); + + process->m_isExitCodeObservable = true; + void* ptr = jsCast(process->globalObject())->bunVM(); + Bun__setExitCode(ptr, static_cast(exitCodeInt)); + } + return true; +} JSC_DEFINE_CUSTOM_SETTER(setProcessExitCode, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName)) { Process* process = jsDynamicCast(JSValue::decode(thisValue)); if (!process) { return false; } - auto throwScope = DECLARE_THROW_SCOPE(process->vm()); - JSValue exitCode = JSValue::decode(value); - if (!exitCode.isAnyInt()) { - throwTypeError(lexicalGlobalObject, throwScope, "exitCode must be an integer"_s); - return false; - } - - int exitCodeInt = exitCode.toInt32(lexicalGlobalObject) % 256; - RETURN_IF_EXCEPTION(throwScope, false); - - process->m_isExitCodeObservable = true; - void* ptr = jsCast(process->globalObject())->bunVM(); - Bun__setExitCode(ptr, static_cast(exitCodeInt)); + auto code = JSValue::decode(value); - return true; + return setProcessExitCodeInner(lexicalGlobalObject, process, code); } JSC_DEFINE_CUSTOM_GETTER(processConnected, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName name)) @@ -1832,10 +1934,19 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionWriteReport, (JSGlobalObject * globalOb static JSValue constructProcessReportObject(VM& vm, JSObject* processObject) { auto* globalObject = processObject->globalObject(); - auto* report = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 4); - report->putDirect(vm, JSC::Identifier::fromString(vm, "getReport"_s), JSC::JSFunction::create(vm, globalObject, 0, String("getReport"_s), Process_functionGetReport, ImplementationVisibility::Public), 0); + // auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto process = jsCast(processObject); + + auto* report = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 10); + report->putDirect(vm, JSC::Identifier::fromString(vm, "compact"_s), JSC::jsBoolean(false), 0); report->putDirect(vm, JSC::Identifier::fromString(vm, "directory"_s), JSC::jsEmptyString(vm), 0); report->putDirect(vm, JSC::Identifier::fromString(vm, "filename"_s), JSC::jsEmptyString(vm), 0); + report->putDirect(vm, JSC::Identifier::fromString(vm, "getReport"_s), JSC::JSFunction::create(vm, globalObject, 0, String("getReport"_s), Process_functionGetReport, ImplementationVisibility::Public), 0); + report->putDirect(vm, JSC::Identifier::fromString(vm, "reportOnFatalError"_s), JSC::jsBoolean(false), 0); + report->putDirect(vm, JSC::Identifier::fromString(vm, "reportOnSignal"_s), JSC::jsBoolean(false), 0); + report->putDirect(vm, JSC::Identifier::fromString(vm, "reportOnUncaughtException"_s), JSC::jsBoolean(process->m_reportOnUncaughtException), 0); + report->putDirect(vm, JSC::Identifier::fromString(vm, "excludeEnv"_s), JSC::jsBoolean(false), 0); + report->putDirect(vm, JSC::Identifier::fromString(vm, "excludeEnv"_s), JSC::jsString(vm, String("SIGUSR2"_s)), 0); report->putDirect(vm, JSC::Identifier::fromString(vm, "writeReport"_s), JSC::JSFunction::create(vm, globalObject, 1, String("writeReport"_s), Process_functionWriteReport, ImplementationVisibility::Public), 0); return report; } @@ -1867,24 +1978,22 @@ static JSValue constructProcessConfigObject(VM& vm, JSObject* processObject) // } // } JSC::JSObject* config = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 2); - JSC::JSObject* variables = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 1); - variables->putDirect(vm, JSC::Identifier::fromString(vm, "v8_enable_i8n_support"_s), - JSC::jsNumber(1), 0); + JSC::JSObject* variables = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 2); + variables->putDirect(vm, JSC::Identifier::fromString(vm, "v8_enable_i8n_support"_s), JSC::jsNumber(1), 0); variables->putDirect(vm, JSC::Identifier::fromString(vm, "enable_lto"_s), JSC::jsBoolean(false), 0); config->putDirect(vm, JSC::Identifier::fromString(vm, "target_defaults"_s), JSC::constructEmptyObject(globalObject), 0); config->putDirect(vm, JSC::Identifier::fromString(vm, "variables"_s), variables, 0); + config->freeze(vm); return config; } static JSValue constructProcessHrtimeObject(VM& vm, JSObject* processObject) { auto* globalObject = processObject->globalObject(); - JSC::JSFunction* hrtime = JSC::JSFunction::create(vm, globalObject, 0, - String("hrtime"_s), Process_functionHRTime, ImplementationVisibility::Public); + JSC::JSFunction* hrtime = JSC::JSFunction::create(vm, globalObject, 0, String("hrtime"_s), Process_functionHRTime, ImplementationVisibility::Public); - JSC::JSFunction* hrtimeBigInt = JSC::JSFunction::create(vm, globalObject, 0, - String("bigint"_s), Process_functionHRTimeBigInt, ImplementationVisibility::Public); + JSC::JSFunction* hrtimeBigInt = JSC::JSFunction::create(vm, globalObject, 0, String("bigint"_s), Process_functionHRTimeBigInt, ImplementationVisibility::Public); hrtime->putDirect(vm, JSC::Identifier::fromString(vm, "bigint"_s), hrtimeBigInt); @@ -1974,6 +2083,16 @@ static JSValue constructStdin(VM& vm, JSObject* processObject) RELEASE_AND_RETURN(scope, result); } +JSC_DEFINE_CUSTOM_GETTER(processThrowDeprecation, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName name)) +{ + return JSValue::encode(jsBoolean(Bun__Node__ProcessThrowDeprecation)); +} + +JSC_DEFINE_CUSTOM_SETTER(setProcessThrowDeprecation, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName)) +{ + return true; +} + static JSValue constructProcessSend(VM& vm, JSObject* processObject) { auto* globalObject = processObject->globalObject(); @@ -1996,7 +2115,7 @@ JSC_DEFINE_HOST_FUNCTION(Bun__Process__disconnect, (JSGlobalObject * globalObjec auto global = jsCast(globalObject); if (!Bun__GlobalObject__hasIPC(globalObject)) { - Process__emitErrorEvent(global, jsFunction_ERR_IPC_DISCONNECTED(globalObject, nullptr)); + Process__emitErrorEvent(global, JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s))); return JSC::JSValue::encode(jsUndefined()); } @@ -2154,6 +2273,167 @@ JSC_DEFINE_HOST_FUNCTION(Process_functiongetgroups, (JSGlobalObject * globalObje } return JSValue::encode(groups); } + +static JSValue maybe_uid_by_name(JSC::ThrowScope& throwScope, JSGlobalObject* globalObject, JSValue value) +{ + if (!value.isNumber() && !value.isString()) return JSValue::decode(Bun::ERR::INVALID_ARG_TYPE(throwScope, globalObject, "id"_s, "number or string"_s, value)); + if (!value.isString()) return value; + + auto str = value.getString(globalObject); + if (!str.is8Bit()) { + auto message = makeString("User identifier does not exist: "_s, str); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_CREDENTIAL, message)); + return {}; + } + + auto name = (const char*)(str.span8().data()); + struct passwd pwd; + struct passwd* pp = nullptr; + char buf[8192]; + + if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { + return jsNumber(pp->pw_uid); + } + + auto message = makeString("User identifier does not exist: "_s, str); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_CREDENTIAL, message)); + return {}; +} + +static JSValue maybe_gid_by_name(JSC::ThrowScope& throwScope, JSGlobalObject* globalObject, JSValue value) +{ + if (!value.isNumber() && !value.isString()) return JSValue::decode(Bun::ERR::INVALID_ARG_TYPE(throwScope, globalObject, "id"_s, "number or string"_s, value)); + if (!value.isString()) return value; + + auto str = value.getString(globalObject); + if (!str.is8Bit()) { + auto message = makeString("Group identifier does not exist: "_s, str); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_CREDENTIAL, message)); + return {}; + } + + auto name = (const char*)(str.span8().data()); + struct group pwd; + struct group* pp = nullptr; + char buf[8192]; + + if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { + return jsNumber(pp->gr_gid); + } + + auto message = makeString("Group identifier does not exist: "_s, str); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_CREDENTIAL, message)); + return {}; +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionsetuid, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + auto is_number = value.isNumber(); + value = maybe_uid_by_name(scope, globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + if (is_number) Bun::V::validateInteger(scope, globalObject, value, "id"_s, jsNumber(0), jsNumber(std::pow(2, 31) - 1)); + RETURN_IF_EXCEPTION(scope, {}); + auto id = value.toUInt32(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto result = setuid(id); + if (result != 0) throwSystemError(scope, globalObject, "setuid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionseteuid, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + auto is_number = value.isNumber(); + value = maybe_uid_by_name(scope, globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + if (is_number) Bun::V::validateInteger(scope, globalObject, value, "id"_s, jsNumber(0), jsNumber(std::pow(2, 31) - 1)); + RETURN_IF_EXCEPTION(scope, {}); + auto id = value.toUInt32(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto result = seteuid(id); + if (result != 0) throwSystemError(scope, globalObject, "seteuid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionsetegid, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + auto is_number = value.isNumber(); + value = maybe_gid_by_name(scope, globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + if (is_number) Bun::V::validateInteger(scope, globalObject, value, "id"_s, jsNumber(0), jsNumber(std::pow(2, 31) - 1)); + RETURN_IF_EXCEPTION(scope, {}); + auto id = value.toUInt32(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto result = setegid(id); + if (result != 0) throwSystemError(scope, globalObject, "setegid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionsetgid, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + auto is_number = value.isNumber(); + value = maybe_gid_by_name(scope, globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + if (is_number) Bun::V::validateInteger(scope, globalObject, value, "id"_s, jsNumber(0), jsNumber(std::pow(2, 31) - 1)); + RETURN_IF_EXCEPTION(scope, {}); + auto id = value.toUInt32(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto result = setgid(id); + if (result != 0) throwSystemError(scope, globalObject, "setgid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionsetgroups, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto groups = callFrame->argument(0); + Bun::V::validateArray(scope, globalObject, groups, "groups"_s, jsUndefined()); + RETURN_IF_EXCEPTION(scope, {}); + auto groupsArray = JSC::jsDynamicCast(groups); + auto count = groupsArray->length(); + gid_t groupsStack[64]; + if (count > 64) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, "groups.length"_s, 0, 64, groups); + + for (unsigned i = 0; i < count; i++) { + auto item = groupsArray->getIndexQuickly(i); + auto name = makeString("groups["_s, i, "]"_s); + + if (item.isNumber()) { + Bun::V::validateUint32(scope, globalObject, item, jsString(vm, name), jsUndefined()); + RETURN_IF_EXCEPTION(scope, {}); + groupsStack[i] = item.toUInt32(globalObject); + continue; + } else if (item.isString()) { + item = maybe_gid_by_name(scope, globalObject, item); + RETURN_IF_EXCEPTION(scope, {}); + groupsStack[i] = item.toUInt32(globalObject); + continue; + } + return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number or string"_s, item); + } + + auto result = setgroups(count, groupsStack); + if (result != 0) throwSystemError(scope, globalObject, "setgid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + #endif JSC_DEFINE_HOST_FUNCTION(Process_functionAssert, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -2163,18 +2443,22 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionAssert, (JSGlobalObject * globalObject, JSValue arg0 = callFrame->argument(0); bool condition = arg0.toBoolean(globalObject); - RETURN_IF_EXCEPTION(throwScope, {}); if (condition) { return JSValue::encode(jsUndefined()); } - JSValue arg1 = callFrame->argument(1); - String message = arg1.isUndefined() ? String() : arg1.toWTFString(globalObject); - RETURN_IF_EXCEPTION(throwScope, {}); - auto error = createError(globalObject, makeString("Assertion failed: "_s, message)); - error->putDirect(vm, Identifier::fromString(vm, "code"_s), jsString(vm, makeString("ERR_ASSERTION"_s))); - throwException(globalObject, throwScope, error); - return {}; + auto msg = callFrame->argument(1); + auto msgb = msg.toBoolean(globalObject); + if (msgb) { + return Bun::ERR::ASSERTION(throwScope, globalObject, msg); + } + return Bun::ERR::ASSERTION(throwScope, globalObject, "assertion error"_s); +} + +extern "C" uint64_t Bun__Os__getFreeMemory(void); +JSC_DEFINE_HOST_FUNCTION(Process_availableMemory, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return JSValue::encode(jsDoubleNumber(Bun__Os__getFreeMemory())); } #define PROCESS_BINDING_NOT_IMPLEMENTED_ISSUE(str, issue) \ @@ -2270,12 +2554,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionReallyExit, (JSGlobalObject * globalObj JSValue arg0 = callFrame->argument(0); if (arg0.isAnyInt()) { exitCode = static_cast(arg0.toInt32(globalObject) % 256); - RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::JSValue {})); - } else if (!arg0.isUndefinedOrNull()) { - throwTypeError(globalObject, throwScope, "The \"code\" argument must be an integer"_s); - return {}; - } else { - exitCode = Bun__getExitCode(bunVM(globalObject)); + RETURN_IF_EXCEPTION(throwScope, {}); } auto* zigGlobal = defaultGlobalObject(globalObject); @@ -2346,18 +2625,12 @@ static Process* getProcessObject(JSC::JSGlobalObject* lexicalGlobalObject, JSVal return process; } -JSC_DEFINE_HOST_FUNCTION(Process_functionConstrainedMemory, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionConstrainedMemory, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { -#if OS(LINUX) || OS(FREEBSD) return JSValue::encode(jsDoubleNumber(static_cast(WTF::ramSize()))); -#else - return JSValue::encode(jsUndefined()); -#endif } -JSC_DEFINE_HOST_FUNCTION(Process_functionCpuUsage, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionCpuUsage, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -2389,8 +2662,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionCpuUsage, if (!comparatorValue.isUndefined()) { JSC::JSObject* comparator = comparatorValue.getObject(); if (UNLIKELY(!comparator)) { - throwTypeError(globalObject, throwScope, "Expected an object as the first argument"_s); - return JSC::JSValue::encode(JSC::jsUndefined()); + return Bun::ERR::INVALID_ARG_TYPE(throwScope, globalObject, "prevValue"_s, "object"_s, comparatorValue); } JSValue userValue; @@ -2401,33 +2673,29 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionCpuUsage, systemValue = comparator->getDirect(1); } else { userValue = comparator->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "user"_s)); - RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined())); + RETURN_IF_EXCEPTION(throwScope, {}); + if (userValue.isEmpty()) userValue = jsUndefined(); systemValue = comparator->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "system"_s)); - RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined())); + RETURN_IF_EXCEPTION(throwScope, {}); + if (systemValue.isEmpty()) systemValue = jsUndefined(); } - if (UNLIKELY(!userValue || !userValue.isNumber())) { - throwTypeError(globalObject, throwScope, "Expected a number for the 'user' property"_s); - return JSC::JSValue::encode(JSC::jsUndefined()); - } + Bun::V::validateNumber(throwScope, globalObject, userValue, "prevValue.user"_s, jsUndefined(), jsUndefined()); + RETURN_IF_EXCEPTION(throwScope, {}); - if (UNLIKELY(!systemValue || !systemValue.isNumber())) { - throwTypeError(globalObject, throwScope, "Expected a number for the 'system' property"_s); - return JSC::JSValue::encode(JSC::jsUndefined()); - } + Bun::V::validateNumber(throwScope, globalObject, systemValue, "prevValue.system"_s, jsUndefined(), jsUndefined()); + RETURN_IF_EXCEPTION(throwScope, {}); double userComparator = userValue.toNumber(globalObject); double systemComparator = systemValue.toNumber(globalObject); - if (userComparator > JSC::maxSafeInteger() || userComparator < 0 || std::isnan(userComparator)) { - throwRangeError(globalObject, throwScope, "The 'user' property must be a number between 0 and 2^53"_s); - return JSC::JSValue::encode(JSC::jsUndefined()); + if (!(userComparator >= 0 && userComparator <= JSC::maxSafeInteger())) { + return Bun::ERR::INVALID_ARG_VALUE_RangeError(throwScope, globalObject, "prevValue.user"_s, userValue, "is invalid"_s); } - if (systemComparator > JSC::maxSafeInteger() || systemComparator < 0 || std::isnan(systemComparator)) { - throwRangeError(globalObject, throwScope, "The 'system' property must be a number between 0 and 2^53"_s); - return JSC::JSValue::encode(JSC::jsUndefined()); + if (!(systemComparator >= 0 && systemComparator <= JSC::maxSafeInteger())) { + return Bun::ERR::INVALID_ARG_VALUE_RangeError(throwScope, globalObject, "prevValue.system"_s, systemValue, "is invalid"_s); } user -= userComparator; @@ -2529,8 +2797,7 @@ int getRSS(size_t* rss) #endif } -JSC_DEFINE_HOST_FUNCTION(Process_functionMemoryUsage, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionMemoryUsage, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -2544,7 +2811,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionMemoryUsage, JSC::JSObject* result = JSC::constructEmptyObject(vm, process->memoryUsageStructure()); if (UNLIKELY(throwScope.exception())) { - return JSC::JSValue::encode(JSC::JSValue {}); + return {}; } // Node.js: @@ -2581,8 +2848,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionMemoryUsage, RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(result)); } -JSC_DEFINE_HOST_FUNCTION(Process_functionMemoryUsageRSS, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionMemoryUsageRSS, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -2725,7 +2991,8 @@ JSValue Process::constructNextTickFn(JSC::VM& vm, Zig::GlobalObject* globalObjec { JSValue nextTickQueueObject; if (!globalObject->m_nextTickQueue) { - nextTickQueueObject = Bun::JSNextTickQueue::create(globalObject); + auto nextTickQueue = Bun::JSNextTickQueue::create(globalObject); + nextTickQueueObject = nextTickQueue; globalObject->m_nextTickQueue.set(vm, globalObject, nextTickQueueObject); } else { nextTickQueueObject = jsCast(globalObject->m_nextTickQueue.get()); @@ -2754,6 +3021,17 @@ static JSValue constructProcessNextTickFn(VM& vm, JSObject* processObject) return jsCast(processObject)->constructNextTickFn(globalObject->vm(), globalObject); } +JSC_DEFINE_CUSTOM_GETTER(processNoDeprecation, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName name)) +{ + return JSValue::encode(jsBoolean(Bun__Node__ProcessNoDeprecation)); +} + +JSC_DEFINE_CUSTOM_SETTER(setProcessNoDeprecation, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName)) +{ + Bun__Node__ProcessNoDeprecation = JSC::JSValue::decode(encodedValue).toBoolean(globalObject); + return true; +} + static JSValue constructFeatures(VM& vm, JSObject* processObject) { // { @@ -2800,9 +3078,7 @@ JSC_DEFINE_CUSTOM_GETTER(processDebugPort, (JSC::JSGlobalObject * globalObject, return JSC::JSValue::encode(jsNumber(_debugPort)); } -JSC_DEFINE_CUSTOM_SETTER(setProcessDebugPort, - (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, - JSC::EncodedJSValue encodedValue, JSC::PropertyName)) +JSC_DEFINE_CUSTOM_SETTER(setProcessDebugPort, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName)) { auto& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -2843,9 +3119,7 @@ JSC_DEFINE_CUSTOM_GETTER(processTitle, (JSC::JSGlobalObject * globalObject, JSC: #endif } -JSC_DEFINE_CUSTOM_SETTER(setProcessTitle, - (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, - JSC::EncodedJSValue value, JSC::PropertyName)) +JSC_DEFINE_CUSTOM_SETTER(setProcessTitle, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName)) { JSC::VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -2889,14 +3163,12 @@ extern "C" EncodedJSValue Process__getCachedCwd(JSC::JSGlobalObject* globalObjec return JSValue::encode(getCachedCwd(globalObject)); } -JSC_DEFINE_HOST_FUNCTION(Process_functionCwd, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionCwd, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { return JSValue::encode(getCachedCwd(globalObject)); } -JSC_DEFINE_HOST_FUNCTION(Process_functionReallyKill, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionReallyKill, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); @@ -2922,13 +3194,18 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionReallyKill, RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(result))); } -JSC_DEFINE_HOST_FUNCTION(Process_functionKill, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(Process_functionKill, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); auto pid_value = callFrame->argument(0); + + // this is mimicking `if (pid != (pid | 0)) {` int pid = pid_value.toInt32(globalObject); RETURN_IF_EXCEPTION(scope, {}); + if (!JSC::JSValue::equal(globalObject, pid_value, jsNumber(pid))) { + return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "pid"_s, "number"_s, pid_value); + } + JSC::JSValue signalValue = callFrame->argument(1); int signal = SIGTERM; if (signalValue.isNumber()) { @@ -3022,6 +3299,7 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu argv constructArgv PropertyCallback argv0 constructArgv0 PropertyCallback assert Process_functionAssert Function 1 + availableMemory Process_availableMemory Function 0 binding Process_functionBinding Function 1 browser constructBrowser PropertyCallback chdir Process_functionChdir Function 1 @@ -3039,7 +3317,7 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu execArgv constructExecArgv PropertyCallback execPath constructExecPath PropertyCallback exit Process_functionExit Function 1 - exitCode processExitCode CustomAccessor + exitCode processExitCode CustomAccessor|DontDelete features constructFeatures PropertyCallback getActiveResourcesInfo Process_stubFunctionReturningArray Function 0 hasUncaughtExceptionCaptureCallback Process_hasUncaughtExceptionCaptureCallback Function 0 @@ -3050,6 +3328,7 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu memoryUsage constructMemoryUsage PropertyCallback moduleLoadList Process_stubEmptyArray PropertyCallback nextTick constructProcessNextTickFn PropertyCallback + noDeprecation processNoDeprecation CustomAccessor openStdin Process_functionOpenStdin Function 0 pid constructPid PropertyCallback platform constructPlatform PropertyCallback @@ -3064,6 +3343,7 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu stderr constructStderr PropertyCallback stdin constructStdin PropertyCallback stdout constructStdout PropertyCallback + throwDeprecation processThrowDeprecation CustomAccessor title processTitle CustomAccessor umask Process_functionUmask Function 1 uptime Process_functionUptime Function 1 @@ -3081,12 +3361,19 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu _stopProfilerIdleNotifier Process_stubEmptyFunction Function 0 _tickCallback Process_stubEmptyFunction Function 0 _kill Process_functionReallyKill Function 2 + #if !OS(WINDOWS) getegid Process_functiongetegid Function 0 geteuid Process_functiongeteuid Function 0 getgid Process_functiongetgid Function 0 getgroups Process_functiongetgroups Function 0 getuid Process_functiongetuid Function 0 + + setegid Process_functionsetegid Function 1 + seteuid Process_functionseteuid Function 1 + setgid Process_functionsetgid Function 1 + setgroups Process_functionsetgroups Function 1 + setuid Process_functionsetuid Function 1 #endif @end */ diff --git a/src/bun.js/bindings/BunProcess.h b/src/bun.js/bindings/BunProcess.h index 368d93ae8b32f2..3fbbfd01429691 100644 --- a/src/bun.js/bindings/BunProcess.h +++ b/src/bun.js/bindings/BunProcess.h @@ -36,6 +36,7 @@ class Process : public WebCore::JSEventEmitter { } DECLARE_EXPORT_INFO; + bool m_reportOnUncaughtException = false; static void destroy(JSC::JSCell* cell) { diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index bb7c91195c1d41..1e5d8e720ca746 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -214,7 +214,7 @@ WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg) return makeString("[Function: "_s, name, ']'); } - return "[Function: (anonymous)]"_s; + return "[Function (anonymous)]"_s; break; } @@ -279,7 +279,7 @@ WTF::String determineSpecificType(JSC::JSGlobalObject* globalObject, JSValue val if (!name.isNull() && name.length() > 0) { return makeString("function "_s, name); } - return String("function"_s); + return String("function "_s); } if (cell->isString()) { auto str = value.toString(globalObject)->getString(globalObject); @@ -405,7 +405,7 @@ namespace ERR { JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value) { - auto arg_kind = arg_name.startsWith("options."_s) ? "property"_s : "argument"_s; + auto arg_kind = arg_name.contains('.') ? "property"_s : "argument"_s; auto ty_first_char = expected_type[0]; auto ty_kind = ty_first_char >= 'A' && ty_first_char <= 'Z' ? "an instance of"_s : "of type"_s; @@ -420,7 +420,7 @@ JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalO { auto arg_name = val_arg_name.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); - auto arg_kind = arg_name.startsWith("options."_s) ? "property"_s : "argument"_s; + auto arg_kind = arg_name.contains('.') ? "property"_s : "argument"_s; auto ty_first_char = expected_type[0]; auto ty_kind = ty_first_char >= 'A' && ty_first_char <= 'Z' ? "an instance of"_s : "of type"_s; @@ -500,7 +500,7 @@ JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObjec JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason) { - ASCIILiteral type = String(name).find('.') != notFound ? "property"_s : "argument"_s; + ASCIILiteral type = String(name).contains('.') ? "property"_s : "argument"_s; auto value_string = JSValueToStringSafe(globalObject, value); RETURN_IF_EXCEPTION(throwScope, {}); @@ -509,6 +509,20 @@ JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobal throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, message)); return {}; } +JSC::EncodedJSValue INVALID_ARG_VALUE_RangeError(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason) +{ + ASCIILiteral type = String(name).contains('.') ? "property"_s : "argument"_s; + + auto value_string = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto& vm = globalObject->vm(); + auto message = makeString("The "_s, type, " '"_s, name, "' "_s, reason, ". Received "_s, value_string); + auto* structure = createErrorStructure(vm, globalObject, ErrorType::RangeError, "RangeError"_s, "ERR_INVALID_ARG_VALUE"_s); + auto error = JSC::ErrorInstance::create(vm, structure, message, jsUndefined(), nullptr, JSC::RuntimeType::TypeNothing, ErrorType::RangeError, true); + throwScope.throwException(globalObject, error); + return {}; +} JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue value, const WTF::String& reason) { auto name_string = JSValueToStringSafe(globalObject, name); @@ -551,7 +565,7 @@ JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlo JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal, bool triedUppercase) { - auto signal_string = JSValueToStringSafe(globalObject, signal); + auto signal_string = signal.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); auto message_extra = triedUppercase ? " (signals must use all capital letters)"_s : ""_s; @@ -574,6 +588,28 @@ JSC::EncodedJSValue SOCKET_BAD_PORT(JSC::ThrowScope& throwScope, JSC::JSGlobalOb return {}; } +JSC::EncodedJSValue UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + auto message = "`process.setupUncaughtExceptionCapture()` was called while a capture callback was already active"_s; + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET, message)); + return {}; +} + +JSC::EncodedJSValue ASSERTION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue msg) +{ + auto msg_string = msg.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + auto message = msg_string; + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_ASSERTION, message)); + return {}; +} +JSC::EncodedJSValue ASSERTION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral msg) +{ + auto message = msg; + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_ASSERTION, message)); + return {}; +} + } static JSC::JSValue ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue arg0, JSValue arg1, JSValue arg2) @@ -596,6 +632,25 @@ static JSC::JSValue ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalOb return createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, msg); } +static JSValue ERR_INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue value, JSC::JSValue reason) +{ + ASSERT(name.isString()); + auto name_string = name.toWTFString(globalObject); + ASCIILiteral type = name_string.contains('.') ? "property"_s : "argument"_s; + + auto value_string = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(throwScope, {}); + + ASSERT(reason.isUndefined() || reason.isString()); + if (reason.isUndefined()) { + auto message = makeString("The "_s, type, " '"_s, name_string, "' is invalid. Received "_s, value_string); + return createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, message); + } + auto reason_string = reason.toWTFString(globalObject); + auto message = makeString("The "_s, type, " '"_s, name_string, "' "_s, reason_string, ". Received "_s, value_string); + return createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, message); +} + JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_OUT_OF_RANGE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -608,31 +663,11 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_OUT_OF_RANGE, (JSC::JSGlobalObject * glo return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); } -JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_DISCONNECTED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*)) -{ - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s)); -} - -JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_SERVER_NOT_RUNNING, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*)) -{ - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SERVER_NOT_RUNNING, "Server is not running."_s)); -} - extern "C" JSC::EncodedJSValue Bun__createErrorWithCode(JSC::JSGlobalObject* globalObject, ErrorCode code, BunString* message) { return JSValue::encode(createError(globalObject, code, message->toWTFString(BunString::ZeroCopy))); } -JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_CHANNEL_CLOSED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*)) -{ - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_CHANNEL_CLOSED, "Channel closed."_s)); -} - -JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_SOCKET_BAD_TYPE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*)) -{ - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_BAD_TYPE, "Bad socket type specified. Valid types are: udp4, udp6"_s)); -} - JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_INVALID_PROTOCOL, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -650,19 +685,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_INVALID_PROTOCOL, (JSC::JSGlobalObject * return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_PROTOCOL, message)); } -JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_INVALID_ARG_TYPE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - EXPECT_ARG_COUNT(3); - - auto arg_name = callFrame->argument(0); - auto expected_type = callFrame->argument(1); - auto actual_value = callFrame->argument(2); - return JSValue::encode(ERR_INVALID_ARG_TYPE(scope, globalObject, arg_name, expected_type, actual_value)); -} - JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_BROTLI_INVALID_PARAM, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -691,16 +713,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_BUFFER_TOO_LARGE, (JSC::JSGlobalObject * return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_BUFFER_TOO_LARGE, message)); } -JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_ZLIB_INITIALIZATION_FAILED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_ZLIB_INITIALIZATION_FAILED, "Initialization failed"_s)); -} - -JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_BUFFER_OUT_OF_BOUNDS, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*)) -{ - return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, "Attempt to access memory outside buffer bounds"_s)); -} - JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_UNHANDLED_ERROR, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -780,7 +792,7 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject JSC::VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - EXPECT_ARG_COUNT(2); + EXPECT_ARG_COUNT(1); JSC::JSValue codeValue = callFrame->argument(0); RETURN_IF_EXCEPTION(scope, {}); @@ -808,9 +820,43 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject JSValue arg0 = callFrame->argument(1); JSValue arg1 = callFrame->argument(2); JSValue arg2 = callFrame->argument(3); - return JSValue::encode(ERR_INVALID_ARG_TYPE(scope, globalObject, arg0, arg1, arg2)); } + + case Bun::ErrorCode::ERR_INVALID_ARG_VALUE: { + JSValue arg0 = callFrame->argument(1); + JSValue arg1 = callFrame->argument(2); + JSValue arg2 = callFrame->argument(3); + return JSValue::encode(ERR_INVALID_ARG_VALUE(scope, globalObject, arg0, arg1, arg2)); + } + + case ErrorCode::ERR_IPC_DISCONNECTED: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s)); + case ErrorCode::ERR_SERVER_NOT_RUNNING: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SERVER_NOT_RUNNING, "Server is not running."_s)); + case ErrorCode::ERR_IPC_CHANNEL_CLOSED: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_CHANNEL_CLOSED, "Channel closed."_s)); + case ErrorCode::ERR_SOCKET_BAD_TYPE: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_BAD_TYPE, "Bad socket type specified. Valid types are: udp4, udp6"_s)); + case ErrorCode::ERR_ZLIB_INITIALIZATION_FAILED: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_ZLIB_INITIALIZATION_FAILED, "Initialization failed"_s)); + case ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, "Attempt to access memory outside buffer bounds"_s)); + case ErrorCode::ERR_IPC_ONE_PIPE: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_ONE_PIPE, "Child process can have only one IPC pipe"_s)); + case ErrorCode::ERR_SOCKET_ALREADY_BOUND: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_ALREADY_BOUND, "Socket is already bound"_s)); + case ErrorCode::ERR_SOCKET_BAD_BUFFER_SIZE: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_BAD_BUFFER_SIZE, "Buffer size must be a positive integer"_s)); + case ErrorCode::ERR_SOCKET_DGRAM_IS_CONNECTED: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_DGRAM_IS_CONNECTED, "Already connected"_s)); + case ErrorCode::ERR_SOCKET_DGRAM_NOT_CONNECTED: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_DGRAM_NOT_CONNECTED, "Not connected"_s)); + case ErrorCode::ERR_SOCKET_DGRAM_NOT_RUNNING: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_DGRAM_NOT_RUNNING, "Not running"_s)); + case ErrorCode::ERR_INVALID_CURSOR_POS: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_CURSOR_POS, "Cannot set cursor row without setting its column"_s)); + default: { break; } diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index 9c288f9b06ec7b..d06bb8e4a2e823 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -55,18 +55,11 @@ JSC::JSValue toJS(JSC::JSGlobalObject*, ErrorCode); JSObject* createInvalidThisError(JSGlobalObject* globalObject, JSValue thisValue, const ASCIILiteral typeName); JSObject* createInvalidThisError(JSGlobalObject* globalObject, const String& message); -JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_INVALID_ARG_TYPE); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_OUT_OF_RANGE); -JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_IPC_DISCONNECTED); -JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_SERVER_NOT_RUNNING); -JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_IPC_CHANNEL_CLOSED); -JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_SOCKET_BAD_TYPE); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_INVALID_PROTOCOL); JSC_DECLARE_HOST_FUNCTION(jsFunctionMakeErrorWithCode); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_BROTLI_INVALID_PARAM); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_BUFFER_TOO_LARGE); -JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_ZLIB_INITIALIZATION_FAILED); -JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_BUFFER_OUT_OF_BOUNDS); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_UNHANDLED_ERROR); enum Bound { @@ -84,6 +77,7 @@ JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObjec JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, const WTF::String& msg, JSC::JSValue actual); JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name_val, const WTF::String& msg, JSC::JSValue actual); JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); +JSC::EncodedJSValue INVALID_ARG_VALUE_RangeError(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView encoding); JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& statemsg); @@ -91,6 +85,9 @@ JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalOb JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal, bool triedUppercase = false); JSC::EncodedJSValue SOCKET_BAD_PORT(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue port, bool allowZero); +JSC::EncodedJSValue UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue ASSERTION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue msg); +JSC::EncodedJSValue ASSERTION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral msg); } diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index bc9b2bfe289311..3c7b465a24ac92 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -52,6 +52,17 @@ export default [ ["ERR_SCRIPT_EXECUTION_TIMEOUT", Error, "Error"], ["ERR_SCRIPT_EXECUTION_INTERRUPTED", Error, "Error"], ["ERR_UNHANDLED_ERROR", Error], + ["ERR_UNKNOWN_CREDENTIAL", Error], + ["ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET", Error], + ["ERR_DLOPEN_FAILED", Error], + ["ERR_ASSERTION", Error], + ["ERR_IPC_ONE_PIPE", Error], + ["ERR_SOCKET_ALREADY_BOUND", Error], + ["ERR_SOCKET_BAD_BUFFER_SIZE", TypeError], + ["ERR_SOCKET_DGRAM_IS_CONNECTED", Error], + ["ERR_SOCKET_DGRAM_NOT_CONNECTED", Error], + ["ERR_SOCKET_DGRAM_NOT_RUNNING", Error], + ["ERR_INVALID_CURSOR_POS", TypeError], // Bun-specific ["ERR_FORMDATA_PARSE_ERROR", TypeError], diff --git a/src/bun.js/bindings/ImportMetaObject.cpp b/src/bun.js/bindings/ImportMetaObject.cpp index 561ae2d213b20b..0d922f5577cdd6 100644 --- a/src/bun.js/bindings/ImportMetaObject.cpp +++ b/src/bun.js/bindings/ImportMetaObject.cpp @@ -205,7 +205,6 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje JSC::JSValue isESMValue = callFrame->argument(2); if (isESMValue.isBoolean()) { isESM = isESMValue.toBoolean(globalObject); - RETURN_IF_EXCEPTION(scope, {}); } } @@ -223,7 +222,6 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje } else if (fromValue.isBoolean()) { isESM = fromValue.toBoolean(globalObject); - RETURN_IF_EXCEPTION(scope, {}); fromValue = JSC::jsUndefined(); } diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 0476e797bba369..f3cdd036b8c2d3 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -425,7 +425,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocUnsafeBody(JS VM& vm = lexicalGlobalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); JSValue lengthValue = callFrame->argument(0); - Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, jsString(vm, String("size"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); size_t length = lengthValue.toLength(lexicalGlobalObject); auto result = allocBufferUnsafe(lexicalGlobalObject, length); @@ -550,7 +550,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSG auto scope = DECLARE_THROW_SCOPE(vm); JSValue lengthValue = callFrame->argument(0); - Bun::V::validateNumber(scope, lexicalGlobalObject, lengthValue, jsString(vm, String("size"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateNumber(scope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(scope, {}); size_t length = lengthValue.toLength(lexicalGlobalObject); @@ -886,7 +886,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_copyBytesFromBody( if (!offsetValue.isUndefined() || !lengthValue.isUndefined()) { if (!offsetValue.isUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, offsetValue, jsString(vm, String("offset"_s)), jsNumber(0), jsUndefined()); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, offsetValue, "offset"_s, jsNumber(0), jsUndefined()); RETURN_IF_EXCEPTION(throwScope, {}); offset = offsetValue.asNumber(); if (offset >= viewLength) return JSValue::encode(createEmptyBuffer(lexicalGlobalObject)); @@ -896,7 +896,7 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_copyBytesFromBody( double end = 0; if (!lengthValue.isUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, lengthValue, jsString(vm, String("length"_s)), jsNumber(0), jsUndefined()); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, lengthValue, "length"_s, jsNumber(0), jsUndefined()); RETURN_IF_EXCEPTION(throwScope, {}); length = lengthValue.asNumber(); end = offset + length; @@ -998,7 +998,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG default: sourceEndValue = callFrame->uncheckedArgument(4); if (sourceEndValue != jsUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, sourceEndValue, jsString(vm, String("sourceEnd"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, sourceEndValue, "sourceEnd"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); sourceEnd = sourceEndValue.asNumber(); } @@ -1007,7 +1007,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG case 4: sourceStartValue = callFrame->uncheckedArgument(3); if (sourceStartValue != jsUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, sourceStartValue, jsString(vm, String("sourceStart"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, sourceStartValue, "sourceStart"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); sourceStart = sourceStartValue.asNumber(); } @@ -1016,7 +1016,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG case 3: targetEndValue = callFrame->uncheckedArgument(2); if (targetEndValue != jsUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, targetEndValue, jsString(vm, String("targetEnd"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, targetEndValue, "targetEnd"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); targetEnd = targetEndValue.asNumber(); } @@ -1025,7 +1025,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSG case 2: targetStartValue = callFrame->uncheckedArgument(1); if (targetStartValue != jsUndefined()) { - Bun::V::validateInteger(throwScope, lexicalGlobalObject, targetStartValue, jsString(vm, String("targetStart"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateInteger(throwScope, lexicalGlobalObject, targetStartValue, "targetStart"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); targetStart = targetStartValue.asNumber(); } @@ -1225,12 +1225,12 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlob // https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L1066-L1079 // https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L122 if (!offsetValue.isUndefined()) { - Bun::V::validateNumber(scope, lexicalGlobalObject, offsetValue, jsString(vm, String("offset"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateNumber(scope, lexicalGlobalObject, offsetValue, "offset"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(scope, {}); offset = offsetValue.toLength(lexicalGlobalObject); } if (!endValue.isUndefined()) { - Bun::V::validateNumber(scope, lexicalGlobalObject, endValue, jsString(vm, String("end"_s)), jsNumber(0), jsNumber(limit)); + Bun::V::validateNumber(scope, lexicalGlobalObject, endValue, "end"_s, jsNumber(0), jsNumber(limit)); RETURN_IF_EXCEPTION(scope, {}); end = endValue.toLength(lexicalGlobalObject); } @@ -2373,7 +2373,7 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi return JSBuffer__bufferFromLength(lexicalGlobalObject, distinguishingArg.asAnyInt()); } else if (distinguishingArg.isNumber()) { JSValue lengthValue = distinguishingArg; - Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, jsString(vm, String("size"_s)), jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); + Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); size_t length = lengthValue.toLength(lexicalGlobalObject); return JSBuffer__bufferFromLength(lexicalGlobalObject, length); diff --git a/src/bun.js/bindings/JSMockFunction.cpp b/src/bun.js/bindings/JSMockFunction.cpp index 5140505d04c9fd..0facb458fd5c92 100644 --- a/src/bun.js/bindings/JSMockFunction.cpp +++ b/src/bun.js/bindings/JSMockFunction.cpp @@ -1122,7 +1122,6 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockName, (JSC::JSGlobalObject * globalOb // https://github.com/jestjs/jest/blob/bd1c6db7c15c23788ca3e09c919138e48dd3b28a/packages/jest-mock/src/index.ts#L849-L856 if (callframe->argument(0).toBoolean(globalObject)) { - RETURN_IF_EXCEPTION(scope, {}); WTF::String name = callframe->argument(0).toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); thisObject->setName(name); diff --git a/src/bun.js/bindings/NodeValidator.cpp b/src/bun.js/bindings/NodeValidator.cpp index c259609ef63aca..dfd177bc3c4672 100644 --- a/src/bun.js/bindings/NodeValidator.cpp +++ b/src/bun.js/bindings/NodeValidator.cpp @@ -51,6 +51,24 @@ JSC::EncodedJSValue V::validateInteger(JSC::ThrowScope& scope, JSC::JSGlobalObje return JSValue::encode(jsUndefined()); } +JSC::EncodedJSValue V::validateInteger(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, ASCIILiteral name, JSC::JSValue min, JSC::JSValue max) +{ + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + if (min.isUndefined()) min = jsDoubleNumber(JSC::minSafeInteger()); + if (max.isUndefined()) max = jsDoubleNumber(JSC::maxSafeInteger()); + + auto value_num = value.asNumber(); + auto min_num = min.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto max_num = max.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + max_num = std::max(min_num, max_num); + + if (std::fmod(value_num, 1.0) != 0) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); + if (value_num < min_num || value_num > max_num) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + + return JSValue::encode(jsUndefined()); +} JSC_DEFINE_HOST_FUNCTION(jsFunction_validateNumber, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { @@ -85,6 +103,28 @@ JSC::EncodedJSValue V::validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObjec return JSValue::encode(jsUndefined()); } +JSC::EncodedJSValue V::validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue min, JSValue max) +{ + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + + auto value_num = value.asNumber(); + auto min_num = min.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto max_num = max.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + auto min_isnonnull = !min.isUndefinedOrNull(); + auto max_isnonnull = !max.isUndefinedOrNull(); + + if ((min_isnonnull && value_num < min_num) || (max_isnonnull && value_num > max_num) || ((min_isnonnull || max_isnonnull) && std::isnan(value_num))) { + if (min_isnonnull && max_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + if (min_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, Bun::LOWER, value); + if (max_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, max_num, Bun::UPPER, value); + return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, ""_s, value); + } + + return JSValue::encode(jsUndefined()); +} JSC_DEFINE_HOST_FUNCTION(jsFunction_validateString, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { @@ -211,8 +251,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validatePort, (JSC::JSGlobalObject * globalO if (allowZero.isUndefined()) allowZero = jsBoolean(true); auto allowZero_b = allowZero.toBoolean(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - if (!port.isNumber() && !port.isString()) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b); if (port.isString()) { @@ -297,6 +335,30 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateArray, (JSC::JSGlobalObject * global auto value = callFrame->argument(0); auto name = callFrame->argument(1); auto minLength = callFrame->argument(2); + return V::validateArray(scope, globalObject, value, name, minLength); +} +JSC::EncodedJSValue V::validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue minLength) +{ + JSC::VM& vm = globalObject->vm(); + + if (minLength.isUndefined()) minLength = jsNumber(0); + + if (!JSC::isArray(globalObject, value)) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "Array"_s, value); + + auto length = value.get(globalObject, Identifier::fromString(vm, "length"_s)); + RETURN_IF_EXCEPTION(scope, {}); + auto length_num = length.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto minLength_num = minLength.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + if (length_num < minLength_num) { + return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, name, value, makeString("must be longer than "_s, minLength_num)); + } + return JSValue::encode(jsUndefined()); +} +JSC::EncodedJSValue V::validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue minLength) +{ + JSC::VM& vm = globalObject->vm(); if (minLength.isUndefined()) minLength = jsNumber(0); @@ -348,7 +410,25 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateUint32, (JSC::JSGlobalObject * globa auto value = callFrame->argument(0); auto name = callFrame->argument(1); auto positive = callFrame->argument(2); + return V::validateUint32(scope, globalObject, value, name, positive); +} +JSC::EncodedJSValue V::validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue positive) +{ + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + if (positive.isUndefined()) positive = jsBoolean(false); + auto value_num = value.asNumber(); + if (std::fmod(value_num, 1.0) != 0) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); + + auto positive_b = positive.toBoolean(globalObject); + auto min = positive_b ? 1 : 0; + auto max = std::numeric_limits().max(); + if (value_num < min || value_num > max) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min, max, value); + + return JSValue::encode(jsUndefined()); +} +JSC::EncodedJSValue V::validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue positive) +{ if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); if (positive.isUndefined()) positive = jsBoolean(false); @@ -356,7 +436,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateUint32, (JSC::JSGlobalObject * globa if (std::fmod(value_num, 1.0) != 0) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); auto positive_b = positive.toBoolean(globalObject); - RETURN_IF_EXCEPTION(scope, {}); auto min = positive_b ? 1 : 0; auto max = std::numeric_limits().max(); if (value_num < min || value_num > max) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min, max, value); @@ -463,4 +542,5 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBuffer, (JSC::JSGlobalObject * globa } return JSValue::encode(jsUndefined()); } + } diff --git a/src/bun.js/bindings/NodeValidator.h b/src/bun.js/bindings/NodeValidator.h index b691c7f5e0dc6a..2557c8a42269d8 100644 --- a/src/bun.js/bindings/NodeValidator.h +++ b/src/bun.js/bindings/NodeValidator.h @@ -27,10 +27,16 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBuffer, (JSC::JSGlobalObject * globa namespace V { JSC::EncodedJSValue validateInteger(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max); +JSC::EncodedJSValue validateInteger(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, ASCIILiteral name, JSC::JSValue min, JSC::JSValue max); JSC::EncodedJSValue validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max); +JSC::EncodedJSValue validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue min, JSValue max); JSC::EncodedJSValue validateFiniteNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue number, JSC::JSValue name); JSC::EncodedJSValue validateString(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name); JSC::EncodedJSValue validateString(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); +JSC::EncodedJSValue validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue minLength); +JSC::EncodedJSValue validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue minLength); +JSC::EncodedJSValue validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue positive); +JSC::EncodedJSValue validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue positive); } diff --git a/src/bun.js/bindings/OsBinding.cpp b/src/bun.js/bindings/OsBinding.cpp index c994fe8d9866d2..dfe6713d6edc60 100644 --- a/src/bun.js/bindings/OsBinding.cpp +++ b/src/bun.js/bindings/OsBinding.cpp @@ -14,12 +14,31 @@ extern "C" uint64_t Bun__Os__getFreeMemory(void) vm_statistics_data_t info; mach_msg_type_number_t count = sizeof(info) / sizeof(integer_t); - if (host_statistics(mach_host_self(), HOST_VM_INFO, - (host_info_t)&info, &count) - != KERN_SUCCESS) { + if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&info, &count) != KERN_SUCCESS) { return 0; } - return (uint64_t)info.free_count * sysconf(_SC_PAGESIZE); } #endif + +#if OS(LINUX) +#include + +extern "C" uint64_t Bun__Os__getFreeMemory(void) +{ + struct sysinfo info; + if (sysinfo(&info) == 0) { + return info.freeram * info.mem_unit; + } + return 0; +} +#endif + +#if OS(WINDOWS) +extern "C" uint64_t uv_get_available_memory(void); + +extern "C" uint64_t Bun__Os__getFreeMemory(void) +{ + return uv_get_available_memory(); +} +#endif diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 358e84d9da80e7..16402e965b4718 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2795,7 +2795,6 @@ JSC_DEFINE_CUSTOM_SETTER(moduleNamespacePrototypeSetESModuleMarker, (JSGlobalObj auto scope = DECLARE_THROW_SCOPE(vm); JSValue value = JSValue::decode(encodedValue); WTF::TriState triState = value.toBoolean(globalObject) ? WTF::TriState::True : WTF::TriState::False; - RETURN_IF_EXCEPTION(scope, false); moduleNamespaceObject->m_hasESModuleMarker = triState; return true; } diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 4bb4689510b4c5..d8b9c3bbc5a6dd 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -1949,24 +1949,21 @@ JSC__JSValue SystemError__toErrorInstance(const SystemError* arg0, JSC::JSObject* result = JSC::ErrorInstance::create(globalObject, globalObject->errorStructureWithErrorType(), message, options); + auto clientData = WebCore::clientData(vm); + if (err.code.tag != BunStringTag::Empty) { JSC::JSValue code = Bun::toJS(globalObject, err.code); - result->putDirect(vm, names.codePublicName(), code, - JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum | 0); - - result->putDirect(vm, vm.propertyNames->name, code, JSC::PropertyAttribute::DontEnum | 0); - } else { - auto* domGlobalObject = defaultGlobalObject(globalObject); - result->putDirect( - vm, vm.propertyNames->name, - JSC::JSValue(domGlobalObject->commonStrings().SystemErrorString(domGlobalObject)), - JSC::PropertyAttribute::DontEnum | 0); + result->putDirect(vm, clientData->builtinNames().codePublicName(), code, JSC::PropertyAttribute::DontDelete | 0); } if (err.path.tag != BunStringTag::Empty) { JSC::JSValue path = Bun::toJS(globalObject, err.path); - result->putDirect(vm, names.pathPublicName(), path, - JSC::PropertyAttribute::DontDelete | 0); + result->putDirect(vm, clientData->builtinNames().pathPublicName(), path, JSC::PropertyAttribute::DontDelete | 0); + } + + if (err.dest.tag != BunStringTag::Empty) { + JSC::JSValue dest = Bun::toJS(globalObject, err.dest); + result->putDirect(vm, clientData->builtinNames().destPublicName(), dest, JSC::PropertyAttribute::DontDelete | 0); } if (err.fd != -1) { diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index eecc31941389f3..75911433c4ed24 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -1732,6 +1732,7 @@ pub const SystemError = extern struct { path: String = String.empty, syscall: String = String.empty, fd: bun.FileDescriptor = bun.toFD(-1), + dest: String = String.empty, pub fn Maybe(comptime Result: type) type { return union(enum) { @@ -1755,6 +1756,7 @@ pub const SystemError = extern struct { this.code.deref(); this.message.deref(); this.syscall.deref(); + this.dest.deref(); } pub fn ref(this: *SystemError) void { @@ -1762,6 +1764,7 @@ pub const SystemError = extern struct { this.code.ref(); this.message.ref(); this.syscall.ref(); + this.dest.ref(); } pub fn toErrorInstance(this: *const SystemError, global: *JSGlobalObject) JSValue { @@ -1770,6 +1773,7 @@ pub const SystemError = extern struct { this.code.deref(); this.message.deref(); this.syscall.deref(); + this.dest.deref(); } return shim.cppFn("toErrorInstance", .{ this, global }); @@ -1800,6 +1804,7 @@ pub const SystemError = extern struct { this.code.deref(); this.message.deref(); this.syscall.deref(); + this.dest.deref(); } return SystemError__toErrorInstanceWithInfoObject(this, global); diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index a581958296af62..f652c77575ecb4 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -362,15 +362,22 @@ pub const Process = extern struct { pub const shim = Shimmer("Bun", "Process", @This()); pub const name = "Process"; pub const namespace = shim.namespace; - const _bun: string = "bun"; + var title_mutex = std.Thread.Mutex{}; pub fn getTitle(_: *JSGlobalObject, title: *ZigString) callconv(.C) void { - title.* = ZigString.init(_bun); + title_mutex.lock(); + defer title_mutex.unlock(); + const str = bun.CLI.Bun__Node__ProcessTitle; + title.* = ZigString.init(str orelse "bun"); } // TODO: https://github.com/nodejs/node/blob/master/deps/uv/src/unix/darwin-proctitle.c - pub fn setTitle(globalObject: *JSGlobalObject, _: *ZigString) callconv(.C) JSValue { - return ZigString.init(_bun).toJS(globalObject); + pub fn setTitle(globalObject: *JSGlobalObject, newvalue: *ZigString) callconv(.C) JSValue { + title_mutex.lock(); + defer title_mutex.unlock(); + if (bun.CLI.Bun__Node__ProcessTitle) |_| bun.default_allocator.free(bun.CLI.Bun__Node__ProcessTitle.?); + bun.CLI.Bun__Node__ProcessTitle = newvalue.dupe(bun.default_allocator) catch bun.outOfMemory(); + return newvalue.toJS(globalObject); } pub const getArgv = JSC.Node.Process.getArgv; diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 8c7b6802a55618..97080b480262b6 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -125,6 +125,7 @@ typedef struct SystemError { BunString path; BunString syscall; int fd; + BunString dest; } SystemError; typedef void* ArrayBufferSink; diff --git a/src/bun.js/bindings/helpers.cpp b/src/bun.js/bindings/helpers.cpp index 6aa6a6096a8a7d..834bd0a36bf1bb 100644 --- a/src/bun.js/bindings/helpers.cpp +++ b/src/bun.js/bindings/helpers.cpp @@ -1,6 +1,7 @@ #include "root.h" #include "helpers.h" #include "BunClientData.h" +#include JSC::JSValue createSystemError(JSC::JSGlobalObject* global, ASCIILiteral message, ASCIILiteral syscall, int err) { @@ -15,11 +16,13 @@ JSC::JSValue createSystemError(JSC::JSGlobalObject* global, ASCIILiteral message JSC::JSValue createSystemError(JSC::JSGlobalObject* global, ASCIILiteral syscall, int err) { - auto* instance = JSC::createError(global, makeString(String(syscall), "() failed"_s)); + auto errstr = String::fromLatin1(Bun__errnoName(err)); + auto* instance = JSC::createError(global, makeString(syscall, "() failed: "_s, errstr, ": "_s, String::fromLatin1(strerror(err)))); auto& vm = global->vm(); auto& builtinNames = WebCore::builtinNames(vm); instance->putDirect(vm, builtinNames.syscallPublicName(), jsString(vm, String(syscall)), 0); instance->putDirect(vm, builtinNames.errnoPublicName(), JSC::jsNumber(err), 0); instance->putDirect(vm, vm.propertyNames->name, jsString(vm, String("SystemError"_s)), JSC::PropertyAttribute::DontEnum | 0); + instance->putDirect(vm, builtinNames.codePublicName(), jsString(vm, errstr)); return instance; } diff --git a/src/bun.js/bindings/helpers.h b/src/bun.js/bindings/helpers.h index cabb787ce1b297..2653138c7903a8 100644 --- a/src/bun.js/bindings/helpers.h +++ b/src/bun.js/bindings/helpers.h @@ -26,6 +26,7 @@ class GlobalObject; #pragma clang diagnostic ignored "-Wunused-function" extern "C" size_t Bun__stringSyntheticAllocationLimit; +extern "C" const char* Bun__errnoName(int); namespace Zig { diff --git a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp index e27d4159feea6e..fd4d1ce63cded2 100644 --- a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp +++ b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp @@ -1670,7 +1670,6 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementCloseStatementFunction, (JSC::JSGlobalObj } bool shouldThrowOnError = (throwOnError.isEmpty() || throwOnError.isUndefined()) ? false : throwOnError.toBoolean(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); sqlite3* db = databases()[dbIndex]->db; // no-op if already closed @@ -2368,7 +2367,6 @@ JSC_DEFINE_CUSTOM_SETTER(jsSqlStatementSetSafeIntegers, (JSGlobalObject * lexica CHECK_PREPARED bool value = JSValue::decode(encodedValue).toBoolean(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, false); castedThis->useBigInt64 = value; return true; diff --git a/src/bun.js/bindings/webcore/EventEmitter.cpp b/src/bun.js/bindings/webcore/EventEmitter.cpp index 021edf1fcaabec..8db15a8e5ba21a 100644 --- a/src/bun.js/bindings/webcore/EventEmitter.cpp +++ b/src/bun.js/bindings/webcore/EventEmitter.cpp @@ -119,9 +119,9 @@ bool EventEmitter::emitForBindings(const Identifier& eventType, const MarkedArgu return true; } -void EventEmitter::emit(const Identifier& eventType, const MarkedArgumentBuffer& arguments) +bool EventEmitter::emit(const Identifier& eventType, const MarkedArgumentBuffer& arguments) { - fireEventListeners(eventType, arguments); + return fireEventListeners(eventType, arguments); } void EventEmitter::uncaughtExceptionInEventHandler() @@ -175,12 +175,12 @@ Vector EventEmitter::getListeners(const Identifier& eventType) } // https://dom.spec.whatwg.org/#concept-event-listener-invoke -void EventEmitter::fireEventListeners(const Identifier& eventType, const MarkedArgumentBuffer& arguments) +bool EventEmitter::fireEventListeners(const Identifier& eventType, const MarkedArgumentBuffer& arguments) { auto* data = eventTargetData(); if (!data) - return; + return false; auto* listenersVector = data->eventListenerMap.find(eventType); if (UNLIKELY(!listenersVector)) { @@ -188,24 +188,25 @@ void EventEmitter::fireEventListeners(const Identifier& eventType, const MarkedA Ref protectedThis(*this); auto* thisObject = protectedThis->m_thisObject.get(); if (!thisObject) - return; + return false; Bun__reportUnhandledError(thisObject->globalObject(), JSValue::encode(arguments.at(0))); - return; + return false; } - return; + return false; } bool prevFiringEventListeners = data->isFiringEventListeners; data->isFiringEventListeners = true; - innerInvokeEventListeners(eventType, *listenersVector, arguments); + auto fired = innerInvokeEventListeners(eventType, *listenersVector, arguments); data->isFiringEventListeners = prevFiringEventListeners; + return fired; } // Intentionally creates a copy of the listeners vector to avoid event listeners added after this point from being run. // Note that removal still has an effect due to the removed field in RegisteredEventListener. // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke -void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, SimpleEventListenerVector listeners, const MarkedArgumentBuffer& arguments) +bool EventEmitter::innerInvokeEventListeners(const Identifier& eventType, SimpleEventListenerVector listeners, const MarkedArgumentBuffer& arguments) { Ref protectedThis(*this); ASSERT(!listeners.isEmpty()); @@ -216,6 +217,7 @@ void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, Simple auto* thisObject = protectedThis->m_thisObject.get(); JSC::JSValue thisValue = thisObject ? JSC::JSValue(thisObject) : JSC::jsUndefined(); + auto fired = false; for (auto& registeredListener : listeners) { // The below code used to be in here, but it's WRONG. Even if a listener is removed, @@ -244,6 +246,7 @@ void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, Simple if (UNLIKELY(callData.type == JSC::CallData::Type::None)) continue; + fired = true; WTF::NakedPtr exceptionPtr; call(lexicalGlobalObject, jsFunction, callData, thisValue, arguments, exceptionPtr); auto* exception = exceptionPtr.get(); @@ -265,6 +268,8 @@ void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, Simple } } } + + return fired; } Vector EventEmitter::eventTypes() diff --git a/src/bun.js/bindings/webcore/EventEmitter.h b/src/bun.js/bindings/webcore/EventEmitter.h index 23687d43a19b5d..e9f6aa167d2e0c 100644 --- a/src/bun.js/bindings/webcore/EventEmitter.h +++ b/src/bun.js/bindings/webcore/EventEmitter.h @@ -55,7 +55,7 @@ class EventEmitter final : public ScriptWrappable, public CanMakeWeakPtr getEventNames(); @@ -76,7 +76,7 @@ class EventEmitter final : public ScriptWrappable, public CanMakeWeakPtr eventTypes(); const SimpleEventListenerVector& eventListeners(const Identifier& eventType); - void fireEventListeners(const Identifier& eventName, const MarkedArgumentBuffer& arguments); + bool fireEventListeners(const Identifier& eventName, const MarkedArgumentBuffer& arguments); bool isFiringEventListeners() const; void invalidateJSEventListeners(JSC::JSObject*); @@ -109,7 +109,7 @@ class EventEmitter final : public ScriptWrappable, public CanMakeWeakPtr class JSDOMConstructorCallable final : public JSDOMConstructorBase { +public: + using Base = JSDOMConstructorBase; + + static JSDOMConstructorCallable* create(JSC::VM&, JSC::Structure*, JSDOMGlobalObject&); + static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject&, JSC::JSValue prototype); + + DECLARE_INFO; + + // Must be defined for each specialization class. + static JSC::JSValue prototypeForStructure(JSC::VM&, const JSDOMGlobalObject&); + + // Must be defined for each specialization class. + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + +private: + JSDOMConstructorCallable(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, construct, call) + { + } + + void finishCreation(JSC::VM&, JSDOMGlobalObject&); + + // Usually defined for each specialization class. + void initializeProperties(JSC::VM&, JSDOMGlobalObject&) {} +}; + +template inline JSDOMConstructorCallable* JSDOMConstructorCallable::create(JSC::VM& vm, JSC::Structure* structure, JSDOMGlobalObject& globalObject) +{ + JSDOMConstructorCallable* constructor = new (NotNull, JSC::allocateCell(vm)) JSDOMConstructorCallable(vm, structure); + constructor->finishCreation(vm, globalObject); + return constructor; +} + +template inline JSC::Structure* JSDOMConstructorCallable::createStructure(JSC::VM& vm, JSC::JSGlobalObject& globalObject, JSC::JSValue prototype) +{ + return JSC::Structure::create(vm, &globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); +} + +template inline void JSDOMConstructorCallable::finishCreation(JSC::VM& vm, JSDOMGlobalObject& globalObject) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + initializeProperties(vm, globalObject); +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/JSEventEmitter.cpp b/src/bun.js/bindings/webcore/JSEventEmitter.cpp index a112593d685599..d091d8959c8c4f 100644 --- a/src/bun.js/bindings/webcore/JSEventEmitter.cpp +++ b/src/bun.js/bindings/webcore/JSEventEmitter.cpp @@ -7,7 +7,7 @@ #include "IDLTypes.h" #include "JSAddEventListenerOptions.h" #include "JSDOMBinding.h" -#include "JSDOMConstructor.h" +#include "JSDOMConstructorCallable.h" #include "JSDOMConvertBase.h" #include "JSDOMConvertBoolean.h" #include "JSDOMConvertDictionary.h" @@ -23,6 +23,7 @@ #include "JSEvent.h" #include "JSEventListener.h" #include "JSEventListenerOptions.h" +#include "JavaScriptCore/JSCJSValue.h" #include "ScriptExecutionContext.h" #include "WebCoreJSClientData.h" #include @@ -94,7 +95,7 @@ class JSEventEmitterPrototype final : public JSC::JSNonFinalObject { }; STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSEventEmitterPrototype, JSEventEmitterPrototype::Base); -using JSEventEmitterDOMConstructor = JSDOMConstructor; +using JSEventEmitterDOMConstructor = JSDOMConstructorCallable; template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSEventEmitterDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) { @@ -124,6 +125,38 @@ template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSEventEmitterDOMConstru } JSC_ANNOTATE_HOST_FUNCTION(JSEventEmitterDOMConstructorConstruct, JSEventEmitterDOMConstructor::construct); +template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSEventEmitterDOMConstructor::call(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* castedThis = jsCast(callFrame->jsCallee()); + ASSERT(castedThis); + auto* context = castedThis->scriptExecutionContext(); + if (UNLIKELY(!context)) { + return throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, "EventEmitter"_s); + } + const auto object = EventEmitter::create(*context); + if constexpr (IsExceptionOr) { + RETURN_IF_EXCEPTION(throwScope, {}); + } + JSValue maxListeners = castedThis->getIfPropertyExists(lexicalGlobalObject, JSC::Identifier::fromString(vm, "defaultMaxListeners"_s)); + RETURN_IF_EXCEPTION(throwScope, {}); + if (maxListeners && maxListeners.isUInt32()) { + object->setMaxListeners(maxListeners.toUInt32(lexicalGlobalObject)); + } + static_assert(TypeOrExceptionOrUnderlyingType::isRef); + auto jsValue = toJSNewlyCreated>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, object.copyRef()); + if constexpr (IsExceptionOr) { + RETURN_IF_EXCEPTION(throwScope, {}); + } + Structure* structure = JSEventEmitter::createStructure(vm, lexicalGlobalObject, jsValue); + JSEventEmitter* instance + = JSEventEmitter::create(structure, reinterpret_cast(lexicalGlobalObject), object.copyRef()); + RETURN_IF_EXCEPTION(throwScope, {}); + RELEASE_AND_RETURN(throwScope, JSValue::encode(instance)); +} +JSC_ANNOTATE_HOST_FUNCTION(JSEventEmitterDOMConstructorCall, JSEventEmitterDOMConstructor::call); + template<> const ClassInfo JSEventEmitterDOMConstructor::s_info = { "EventEmitter"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSEventEmitterDOMConstructor) }; template<> JSValue JSEventEmitterDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject) diff --git a/src/bun.js/bindings/webcore/JSWorker.cpp b/src/bun.js/bindings/webcore/JSWorker.cpp index 37f167420292f7..57e9dd062b2a44 100644 --- a/src/bun.js/bindings/webcore/JSWorker.cpp +++ b/src/bun.js/bindings/webcore/JSWorker.cpp @@ -140,12 +140,10 @@ template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWorkerDOMConstructor:: if (auto miniModeValue = optionsObject->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "smol"_s))) { options.bun.mini = miniModeValue.toBoolean(lexicalGlobalObject); - RETURN_IF_EXCEPTION(throwScope, {}); } if (auto ref = optionsObject->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "ref"_s))) { options.bun.unref = !ref.toBoolean(lexicalGlobalObject); - RETURN_IF_EXCEPTION(throwScope, {}); } if (auto preloadModulesValue = optionsObject->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "preload"_s))) { diff --git a/src/bun.js/bindings/webcore/JSWorkerOptions.cpp b/src/bun.js/bindings/webcore/JSWorkerOptions.cpp index 8103d23053cf90..cb46c6f204093a 100644 --- a/src/bun.js/bindings/webcore/JSWorkerOptions.cpp +++ b/src/bun.js/bindings/webcore/JSWorkerOptions.cpp @@ -69,7 +69,7 @@ template<> WorkerOptions convertDictionary(JSGlobalObject& lexica // if (isNullOrUndefined) // typeValue = jsUndefined(); // else { - // typeValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "type"_s)); + // typeValue = object->get(&lexicalGlobalObject, vm.propertyNames->type); // RETURN_IF_EXCEPTION(throwScope, { }); // } // if (!typeValue.isUndefined()) { diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 404558d102c705..4029ab0fbb7409 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -885,6 +885,7 @@ pub const VirtualMachine = struct { onUnhandledRejectionExceptionList: ?*ExceptionList = null, unhandled_error_counter: usize = 0, is_handling_uncaught_exception: bool = false, + exit_on_uncaught_exception: bool = false, modules: ModuleLoader.AsyncModule.Queue = .{}, aggressive_garbage_collection: GCLevel = GCLevel.none, @@ -1190,6 +1191,10 @@ pub const VirtualMachine = struct { extern fn Bun__handleUnhandledRejection(*JSC.JSGlobalObject, reason: JSC.JSValue, promise: JSC.JSValue) c_int; extern fn Bun__Process__exit(*JSC.JSGlobalObject, code: c_int) noreturn; + export fn Bun__VirtualMachine__exitDuringUncaughtException(this: *JSC.VirtualMachine) void { + this.exit_on_uncaught_exception = true; + } + pub fn unhandledRejection(this: *JSC.VirtualMachine, globalObject: *JSC.JSGlobalObject, reason: JSC.JSValue, promise: JSC.JSValue) bool { if (this.isShuttingDown()) { Output.debugWarn("unhandledRejection during shutdown.", .{}); @@ -1227,6 +1232,11 @@ pub const VirtualMachine = struct { Bun__Process__exit(globalObject, 7); @panic("Uncaught exception while handling uncaught exception"); } + if (this.exit_on_uncaught_exception) { + this.runErrorHandler(err, null); + Bun__Process__exit(globalObject, 1); + @panic("made it past Bun__Process__exit"); + } 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; @@ -4093,11 +4103,7 @@ pub const VirtualMachine = struct { fn printErrorNameAndMessage(_: *VirtualMachine, name: String, message: String, comptime Writer: type, writer: Writer, comptime allow_ansi_color: bool) !void { if (!name.isEmpty() and !message.isEmpty()) { const display_name: String = if (name.eqlComptime("Error")) String.init("error") else name; - - try writer.print(comptime Output.prettyFmt("{}: {s}\n", allow_ansi_color), .{ - display_name, - message, - }); + try writer.print(comptime Output.prettyFmt("{}: {s}\n", allow_ansi_color), .{ display_name, message }); } else if (!name.isEmpty()) { if (!name.hasPrefixComptime("error")) { try writer.print(comptime Output.prettyFmt("error: {}\n", allow_ansi_color), .{name}); diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index b316a1d764839c..009c0142a4691a 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -2531,6 +2531,7 @@ pub const ModuleLoader = struct { .@"node:stream/consumers" => return jsSyntheticModule(.@"node:stream/consumers", specifier), .@"node:stream/promises" => return jsSyntheticModule(.@"node:stream/promises", specifier), .@"node:stream/web" => return jsSyntheticModule(.@"node:stream/web", specifier), + .@"node:test" => return jsSyntheticModule(.@"node:test", specifier), .@"node:timers" => return jsSyntheticModule(.@"node:timers", specifier), .@"node:timers/promises" => return jsSyntheticModule(.@"node:timers/promises", specifier), .@"node:tls" => return jsSyntheticModule(.@"node:tls", specifier), @@ -2731,6 +2732,7 @@ pub const HardcodedModule = enum { @"node:stream/promises", @"node:stream/web", @"node:string_decoder", + @"node:test", @"node:timers", @"node:timers/promises", @"node:tls", @@ -2781,6 +2783,8 @@ pub const HardcodedModule = enum { .{ "node-fetch", HardcodedModule.@"node-fetch" }, .{ "isomorphic-fetch", HardcodedModule.@"isomorphic-fetch" }, + .{ "node:test", HardcodedModule.@"node:test" }, + .{ "assert", HardcodedModule.@"node:assert" }, .{ "assert/strict", HardcodedModule.@"node:assert/strict" }, .{ "async_hooks", HardcodedModule.@"node:async_hooks" }, @@ -2852,7 +2856,7 @@ pub const HardcodedModule = enum { pub const Aliases = struct { // Used by both Bun and Node. - const common_alias_kvs = .{ + const common_alias_kvs = [_]struct { string, Alias }{ .{ "node:assert", .{ .path = "assert" } }, .{ "node:assert/strict", .{ .path = "assert/strict" } }, .{ "node:async_hooks", .{ .path = "async_hooks" } }, @@ -2892,6 +2896,7 @@ pub const HardcodedModule = enum { .{ "node:stream/promises", .{ .path = "stream/promises" } }, .{ "node:stream/web", .{ .path = "stream/web" } }, .{ "node:string_decoder", .{ .path = "string_decoder" } }, + .{ "node:test", .{ .path = "node:test" } }, .{ "node:timers", .{ .path = "timers" } }, .{ "node:timers/promises", .{ .path = "timers/promises" } }, .{ "node:tls", .{ .path = "tls" } }, @@ -2906,6 +2911,22 @@ pub const HardcodedModule = enum { .{ "node:worker_threads", .{ .path = "worker_threads" } }, .{ "node:zlib", .{ .path = "zlib" } }, + // These are returned in builtinModules, but probably not many packages use them so we will just alias them. + .{ "node:_http_agent", .{ .path = "http" } }, + .{ "node:_http_client", .{ .path = "http" } }, + .{ "node:_http_common", .{ .path = "http" } }, + .{ "node:_http_incoming", .{ .path = "http" } }, + .{ "node:_http_outgoing", .{ .path = "http" } }, + .{ "node:_http_server", .{ .path = "http" } }, + .{ "node:_stream_duplex", .{ .path = "stream" } }, + .{ "node:_stream_passthrough", .{ .path = "stream" } }, + .{ "node:_stream_readable", .{ .path = "stream" } }, + .{ "node:_stream_transform", .{ .path = "stream" } }, + .{ "node:_stream_writable", .{ .path = "stream" } }, + .{ "node:_stream_wrap", .{ .path = "stream" } }, + .{ "node:_tls_wrap", .{ .path = "tls" } }, + .{ "node:_tls_common", .{ .path = "tls" } }, + .{ "assert", .{ .path = "assert" } }, .{ "assert/strict", .{ .path = "assert/strict" } }, .{ "async_hooks", .{ .path = "async_hooks" } }, @@ -2945,6 +2966,7 @@ pub const HardcodedModule = enum { .{ "stream/promises", .{ .path = "stream/promises" } }, .{ "stream/web", .{ .path = "stream/web" } }, .{ "string_decoder", .{ .path = "string_decoder" } }, + // .{ "test", .{ .path = "test" } }, .{ "timers", .{ .path = "timers" } }, .{ "timers/promises", .{ .path = "timers/promises" } }, .{ "tls", .{ .path = "tls" } }, @@ -2987,7 +3009,7 @@ pub const HardcodedModule = enum { .{ "internal/test/binding", .{ .path = "internal/test/binding" } }, }; - const bun_extra_alias_kvs = .{ + const bun_extra_alias_kvs = [_]struct { string, Alias }{ .{ "bun", .{ .path = "bun", .tag = .bun } }, .{ "bun:test", .{ .path = "bun:test", .tag = .bun_test } }, .{ "bun:ffi", .{ .path = "bun:ffi" } }, @@ -3017,10 +3039,9 @@ pub const HardcodedModule = enum { .{ "abort-controller/polyfill", .{ .path = "abort-controller" } }, }; - const node_alias_kvs = .{ + const node_alias_kvs = [_]struct { string, Alias }{ .{ "inspector/promises", .{ .path = "inspector/promises" } }, .{ "node:inspector/promises", .{ .path = "inspector/promises" } }, - .{ "node:test", .{ .path = "node:test" } }, }; const NodeAliases = bun.ComptimeStringMap(Alias, common_alias_kvs ++ node_alias_kvs); diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 1a7440ebe89c07..302e11fd5e2d7d 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -231,14 +231,14 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type { }; } - pub inline fn getErrno(this: @This()) posix.E { + pub fn getErrno(this: @This()) posix.E { return switch (this) { .result => posix.E.SUCCESS, .err => |e| @enumFromInt(e.errno), }; } - pub inline fn errnoSys(rc: anytype, syscall: Syscall.Tag) ?@This() { + pub fn errnoSys(rc: anytype, syscall: Syscall.Tag) ?@This() { if (comptime Environment.isWindows) { if (comptime @TypeOf(rc) == std.os.windows.NTSTATUS) {} else { if (rc != 0) return null; @@ -256,7 +256,7 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type { }; } - pub inline fn errno(err: anytype, syscall: Syscall.Tag) @This() { + pub fn errno(err: anytype, syscall: Syscall.Tag) @This() { return @This(){ // always truncate .err = .{ @@ -266,7 +266,7 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type { }; } - pub inline fn errnoSysFd(rc: anytype, syscall: Syscall.Tag, fd: bun.FileDescriptor) ?@This() { + pub fn errnoSysFd(rc: anytype, syscall: Syscall.Tag, fd: bun.FileDescriptor) ?@This() { if (comptime Environment.isWindows) { if (comptime @TypeOf(rc) == std.os.windows.NTSTATUS) {} else { if (rc != 0) return null; @@ -285,7 +285,7 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type { }; } - pub inline fn errnoSysP(rc: anytype, syscall: Syscall.Tag, path: anytype) ?@This() { + pub fn errnoSysP(rc: anytype, syscall: Syscall.Tag, path: anytype) ?@This() { if (bun.meta.Item(@TypeOf(path)) == u16) { @compileError("Do not pass WString path to errnoSysP, it needs the path encoded as utf8"); } @@ -306,6 +306,49 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type { }, }; } + + pub fn errnoSysFP(rc: anytype, syscall: Syscall.Tag, fd: bun.FileDescriptor, path: anytype) ?@This() { + if (comptime Environment.isWindows) { + if (comptime @TypeOf(rc) == std.os.windows.NTSTATUS) {} else { + if (rc != 0) return null; + } + } + return switch (Syscall.getErrno(rc)) { + .SUCCESS => null, + else => |e| @This(){ + // Always truncate + .err = .{ + .errno = translateToErrInt(e), + .syscall = syscall, + .fd = fd, + .path = bun.asByteSlice(path), + }, + }, + }; + } + + pub fn errnoSysPD(rc: anytype, syscall: Syscall.Tag, path: anytype, dest: anytype) ?@This() { + if (bun.meta.Item(@TypeOf(path)) == u16) { + @compileError("Do not pass WString path to errnoSysPD, it needs the path encoded as utf8"); + } + if (comptime Environment.isWindows) { + if (comptime @TypeOf(rc) == std.os.windows.NTSTATUS) {} else { + if (rc != 0) return null; + } + } + return switch (Syscall.getErrno(rc)) { + .SUCCESS => null, + else => |e| @This(){ + // Always truncate + .err = .{ + .errno = translateToErrInt(e), + .syscall = syscall, + .path = bun.asByteSlice(path), + .dest = bun.asByteSlice(dest), + }, + }, + }; + } }; } @@ -2081,34 +2124,34 @@ pub const Process = struct { if (to.len == 0) { return globalObject.throwInvalidArguments("Expected path to be a non-empty string", .{}); } + const vm = globalObject.bunVM(); + const fs = vm.transpiler.fs; var buf: bun.PathBuffer = undefined; - const slice = to.sliceZBuf(&buf) catch { - return globalObject.throw("Invalid path", .{}); - }; + const slice = to.sliceZBuf(&buf) catch return globalObject.throw("Invalid path", .{}); - switch (Syscall.chdir(slice)) { + switch (Syscall.chdir(fs.top_level_dir, slice)) { .result => { // When we update the cwd from JS, we have to update the bundler's version as well // However, this might be called many times in a row, so we use a pre-allocated buffer // that way we don't have to worry about garbage collector - const fs = JSC.VirtualMachine.get().transpiler.fs; const into_cwd_buf = switch (bun.sys.getcwd(&buf)) { .result => |r| r, .err => |err| { - _ = Syscall.chdir(@as([:0]const u8, @ptrCast(fs.top_level_dir))); + _ = Syscall.chdir(fs.top_level_dir, fs.top_level_dir); return globalObject.throwValue(err.toJSC(globalObject)); }, }; @memcpy(fs.top_level_dir_buf[0..into_cwd_buf.len], into_cwd_buf); - fs.top_level_dir = fs.top_level_dir_buf[0..into_cwd_buf.len]; + fs.top_level_dir_buf[into_cwd_buf.len] = 0; + fs.top_level_dir = fs.top_level_dir_buf[0..into_cwd_buf.len :0]; const len = fs.top_level_dir.len; // Ensure the path ends with a slash if (fs.top_level_dir_buf[len - 1] != std.fs.path.sep) { fs.top_level_dir_buf[len] = std.fs.path.sep; fs.top_level_dir_buf[len + 1] = 0; - fs.top_level_dir = fs.top_level_dir_buf[0 .. len + 1]; + fs.top_level_dir = fs.top_level_dir_buf[0 .. len + 1 :0]; } const withoutTrailingSlash = if (Environment.isWindows) strings.withoutTrailingSlashWindowsPath else strings.withoutTrailingSlash; var str = bun.String.createUTF8(withoutTrailingSlash(fs.top_level_dir)); diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index ae4b6b247819c2..f90c8bbd15f50a 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -4664,7 +4664,6 @@ pub const Expect = struct { if (result.isObject()) { if (try result.get(globalThis, "pass")) |pass_value| { pass = pass_value.toBoolean(); - if (globalThis.hasException()) return false; if (result.fastGet(globalThis, .message)) |message_value| { if (!message_value.isString() and !message_value.isCallable(globalThis.vm())) { diff --git a/src/bun.zig b/src/bun.zig index bd611f9276cf5f..6bb9b890a5d5c4 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1399,10 +1399,10 @@ fn getFdPathViaCWD(fd: std.posix.fd_t, buf: *[@This().MAX_PATH_BYTES]u8) ![]u8 { pub const getcwd = std.posix.getcwd; -pub fn getcwdAlloc(allocator: std.mem.Allocator) ![]u8 { +pub fn getcwdAlloc(allocator: std.mem.Allocator) ![:0]u8 { var temp: PathBuffer = undefined; const temp_slice = try getcwd(&temp); - return allocator.dupe(u8, temp_slice); + return allocator.dupeZ(u8, temp_slice); } /// Get the absolute path to a file descriptor. diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index e7cccd04b30207..660cff180ddbd9 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1655,7 +1655,7 @@ pub const BundleV2 = struct { .entry_points = config.entry_points.keys(), .target = config.target.toAPI(), .absolute_working_dir = if (config.dir.list.items.len > 0) - config.dir.slice() + config.dir.sliceWithSentinel() else null, .inject = &.{}, diff --git a/src/c.zig b/src/c.zig index 03ee097e0821ff..1541b9872eaf0e 100644 --- a/src/c.zig +++ b/src/c.zig @@ -499,3 +499,7 @@ pub extern fn strlen(ptr: [*c]const u8) usize; pub const passwd = translated.passwd; pub const geteuid = translated.geteuid; pub const getpwuid_r = translated.getpwuid_r; + +export fn Bun__errnoName(err: c_int) ?[*:0]const u8 { + return @tagName(bun.C.SystemErrno.init(err) orelse return null); +} diff --git a/src/cli.zig b/src/cli.zig index 792ff2012b7716..73a936ff1d3455 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -47,6 +47,11 @@ pub var start_time: i128 = undefined; const Bunfig = @import("./bunfig.zig").Bunfig; const OOM = bun.OOM; +export var Bun__Node__ProcessNoDeprecation = false; +export var Bun__Node__ProcessThrowDeprecation = false; + +pub var Bun__Node__ProcessTitle: ?string = null; + pub const Cli = struct { pub const CompileTarget = @import("./compile_target.zig"); var wait_group: sync.WaitGroup = undefined; @@ -232,6 +237,9 @@ pub const Arguments = struct { clap.parseParam("--fetch-preconnect ... Preconnect to a URL while code is loading") catch unreachable, clap.parseParam("--max-http-header-size Set the maximum size of HTTP headers in bytes. Default is 16KiB") catch unreachable, clap.parseParam("--expose-internals Expose internals used for testing Bun itself. Usage of these APIs are completely unsupported.") catch unreachable, + clap.parseParam("--no-deprecation Suppress all reporting of the custom deprecation.") catch unreachable, + clap.parseParam("--throw-deprecation Determine whether or not deprecation warnings result in errors.") catch unreachable, + clap.parseParam("--title Set the process title") catch unreachable, }; const auto_or_run_params = [_]ParamType{ @@ -412,7 +420,7 @@ pub const Arguments = struct { var secondbuf: bun.PathBuffer = undefined; const cwd = bun.getcwd(&secondbuf) catch return; - ctx.args.absolute_working_dir = try allocator.dupe(u8, cwd); + ctx.args.absolute_working_dir = try allocator.dupeZ(u8, cwd); } var parts = [_]string{ ctx.args.absolute_working_dir.?, config_path_ }; @@ -487,16 +495,16 @@ pub const Arguments = struct { } } - var cwd: []u8 = undefined; + var cwd: [:0]u8 = undefined; if (args.option("--cwd")) |cwd_arg| { cwd = brk: { var outbuf: bun.PathBuffer = undefined; const out = bun.path.joinAbs(try bun.getcwd(&outbuf), .loose, cwd_arg); - bun.sys.chdir(out).unwrap() catch |err| { + bun.sys.chdir("", out).unwrap() catch |err| { Output.err(err, "Could not change directory to \"{s}\"\n", .{cwd_arg}); Global.exit(1); }; - break :brk try allocator.dupe(u8, out); + break :brk try allocator.dupeZ(u8, out); }; } else { cwd = try bun.getcwdAlloc(allocator); @@ -795,6 +803,15 @@ pub const Arguments = struct { if (args.flag("--expose-internals")) { bun.JSC.ModuleLoader.is_allowed_to_use_internal_testing_apis = true; } + if (args.flag("--no-deprecation")) { + Bun__Node__ProcessNoDeprecation = true; + } + if (args.flag("--throw-deprecation")) { + Bun__Node__ProcessThrowDeprecation = true; + } + if (args.option("--title")) |title| { + Bun__Node__ProcessTitle = title; + } } if (opts.port != null and opts.origin == null) { diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index fb822fa4e97cfc..1ce8301f153385 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -581,7 +581,7 @@ pub const UpgradeCommand = struct { tmpdir_path_buf[tmpdir_path.len] = 0; const tmpdir_z = tmpdir_path_buf[0..tmpdir_path.len :0]; - _ = bun.sys.chdir(tmpdir_z); + _ = bun.sys.chdir("", tmpdir_z); const tmpname = "bun.zip"; const exe = diff --git a/src/codegen/generate-node-errors.ts b/src/codegen/generate-node-errors.ts index debbb07fc50da9..e4c807be702bfb 100644 --- a/src/codegen/generate-node-errors.ts +++ b/src/codegen/generate-node-errors.ts @@ -15,6 +15,8 @@ enumHeader = ` // Generated by: src/codegen/generate-node-errors.ts #pragma once +#include + namespace Bun { static constexpr size_t NODE_ERROR_COUNT = ${NodeErrors.length}; enum class ErrorCode : uint8_t { @@ -25,6 +27,8 @@ listHeader = ` // Generated by: src/codegen/generate-node-errors.ts #pragma once +#include + struct ErrorCodeData { JSC::ErrorType type; WTF::ASCIILiteral name; diff --git a/src/fs.zig b/src/fs.zig index 9f8cd22f246095..b1feeadd4b5165 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -37,7 +37,7 @@ pub const Preallocate = struct { }; pub const FileSystem = struct { - top_level_dir: string, + top_level_dir: stringZ, // used on subsequent updates top_level_dir_buf: bun.PathBuffer = undefined, @@ -108,22 +108,14 @@ pub const FileSystem = struct { ENOTDIR, }; - pub fn init(top_level_dir: ?string) !*FileSystem { + pub fn init(top_level_dir: ?stringZ) !*FileSystem { return initWithForce(top_level_dir, false); } - pub fn initWithForce(top_level_dir_: ?string, comptime force: bool) !*FileSystem { + pub fn initWithForce(top_level_dir_: ?stringZ, comptime force: bool) !*FileSystem { const allocator = bun.fs_allocator; var top_level_dir = top_level_dir_ orelse (if (Environment.isBrowser) "/project/" else try bun.getcwdAlloc(allocator)); - - // Ensure there's a trailing separator in the top level directory - // This makes path resolution more reliable - if (!bun.path.isSepAny(top_level_dir[top_level_dir.len - 1])) { - const tld = try allocator.alloc(u8, top_level_dir.len + 1); - bun.copy(u8, tld, top_level_dir); - tld[tld.len - 1] = std.fs.path.sep; - top_level_dir = tld; - } + _ = &top_level_dir; if (!instance_loaded or force) { instance = FileSystem{ diff --git a/src/install/install.zig b/src/install/install.zig index d7bed546cc4323..546dbf2ef478b7 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -8843,7 +8843,7 @@ pub const PackageManager = struct { } else child_path; if (strings.eqlLong(maybe_workspace_path, path, true)) { - fs.top_level_dir = parent; + fs.top_level_dir = try bun.default_allocator.dupeZ(u8, parent); found = true; child_json.close(); if (comptime Environment.isWindows) { @@ -8860,16 +8860,15 @@ pub const PackageManager = struct { } } - fs.top_level_dir = child_cwd; + fs.top_level_dir = try bun.default_allocator.dupeZ(u8, child_cwd); break :root_package_json_file child_json; }; - try bun.sys.chdir(fs.top_level_dir).unwrap(); + try bun.sys.chdir(fs.top_level_dir, fs.top_level_dir).unwrap(); try BunArguments.loadConfig(ctx.allocator, cli.config, ctx, .InstallCommand); bun.copy(u8, &cwd_buf, fs.top_level_dir); - cwd_buf[fs.top_level_dir.len] = std.fs.path.sep; - cwd_buf[fs.top_level_dir.len + 1] = 0; - fs.top_level_dir = cwd_buf[0 .. fs.top_level_dir.len + 1]; + cwd_buf[fs.top_level_dir.len] = 0; + fs.top_level_dir = cwd_buf[0..fs.top_level_dir.len :0]; package_json_cwd = try bun.getFdPath(root_package_json_file.handle, &package_json_cwd_buf); const entries_option = try fs.fs.readDirectory(fs.top_level_dir, null, 0, true); @@ -10213,7 +10212,7 @@ pub const PackageManager = struct { buf[cwd_.len] = 0; final_path = buf[0..cwd_.len :0]; } - bun.sys.chdir(final_path).unwrap() catch |err| { + bun.sys.chdir("", final_path).unwrap() catch |err| { Output.errGeneric("failed to change directory to \"{s}\": {s}\n", .{ final_path, @errorName(err) }); Global.crash(); }; diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 47f4c6d8ac1c81..dad78519540bfe 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -1681,7 +1681,7 @@ pub const Printer = struct { } if (lockfile_path.len > 0 and lockfile_path[0] == std.fs.path.sep) - _ = bun.sys.chdir(std.fs.path.dirname(lockfile_path) orelse std.fs.path.sep_str); + _ = bun.sys.chdir("", std.fs.path.dirname(lockfile_path) orelse std.fs.path.sep_str); _ = try FileSystem.init(null); diff --git a/src/js/builtins.d.ts b/src/js/builtins.d.ts index 2c07c8aa0e350d..d8df1e8938865b 100644 --- a/src/js/builtins.d.ts +++ b/src/js/builtins.d.ts @@ -549,6 +549,22 @@ declare interface Error { */ declare function $ERR_INVALID_ARG_TYPE(argName: string, expectedType: string, actualValue: string): TypeError; declare function $ERR_INVALID_ARG_TYPE(argName: string, expectedTypes: any[], actualValue: string): TypeError; +declare function $ERR_INVALID_ARG_VALUE(name: string, value: any, reason?: string): TypeError; + +declare function $ERR_IPC_DISCONNECTED(): Error; +declare function $ERR_SERVER_NOT_RUNNING(): Error; +declare function $ERR_IPC_CHANNEL_CLOSED(): Error; +declare function $ERR_SOCKET_BAD_TYPE(): Error; +declare function $ERR_ZLIB_INITIALIZATION_FAILED(): Error; +declare function $ERR_BUFFER_OUT_OF_BOUNDS(): Error; +declare function $ERR_IPC_ONE_PIPE(): Error; +declare function $ERR_SOCKET_ALREADY_BOUND(): Error; +declare function $ERR_SOCKET_BAD_BUFFER_SIZE(): Error; +declare function $ERR_SOCKET_DGRAM_IS_CONNECTED(): Error; +declare function $ERR_SOCKET_DGRAM_NOT_CONNECTED(): Error; +declare function $ERR_SOCKET_DGRAM_NOT_RUNNING(): Error; +declare function $ERR_INVALID_CURSOR_POS(): Error; + /** * Convert a function to a class-like object. * diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h index b3cb19b8164aba..b7017f0154e788 100644 --- a/src/js/builtins/BunBuiltinNames.h +++ b/src/js/builtins/BunBuiltinNames.h @@ -71,6 +71,7 @@ using namespace JSC; macro(dataView) \ macro(decode) \ macro(delimiter) \ + macro(dest) \ macro(destroy) \ macro(dir) \ macro(direct) \ @@ -244,6 +245,7 @@ using namespace JSC; macro(version) \ macro(versions) \ macro(view) \ + macro(warning) \ macro(writable) \ macro(WritableStream) \ macro(WritableStreamDefaultController) \ diff --git a/src/js/builtins/ConsoleObject.ts b/src/js/builtins/ConsoleObject.ts index ee9c6c6cf7668d..7377e5710cd440 100644 --- a/src/js/builtins/ConsoleObject.ts +++ b/src/js/builtins/ConsoleObject.ts @@ -142,7 +142,7 @@ export function createConsoleConstructor(console: typeof globalThis.console) { const { inspect, formatWithOptions, stripVTControlCharacters } = require("node:util"); const { isBuffer } = require("node:buffer"); - const { validateObject, validateInteger, validateArray } = require("internal/validators"); + const { validateObject, validateInteger, validateArray, validateOneOf } = require("internal/validators"); const kMaxGroupIndentation = 1000; const StringPrototypeIncludes = String.prototype.includes; @@ -298,11 +298,7 @@ export function createConsoleConstructor(console: typeof globalThis.console) { throw $ERR_CONSOLE_WRITABLE_STREAM("stderr is not a writable stream"); } - if (typeof colorMode !== "boolean" && colorMode !== "auto") { - throw $ERR_INVALID_ARG_VALUE( - "The argument 'colorMode' must be one of: 'auto', true, false. Received " + inspect(colorMode), - ); - } + validateOneOf(colorMode, "colorMode", ["auto", true, false]); if (groupIndentation !== undefined) { validateInteger(groupIndentation, "groupIndentation", 0, kMaxGroupIndentation); diff --git a/src/js/internal/errors.ts b/src/js/internal/errors.ts index 739958bf03d148..f12e6a067c7c07 100644 --- a/src/js/internal/errors.ts +++ b/src/js/internal/errors.ts @@ -1,14 +1,7 @@ export default { - ERR_INVALID_ARG_TYPE: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_INVALID_ARG_TYPE", 3), ERR_OUT_OF_RANGE: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_OUT_OF_RANGE", 3), - ERR_IPC_DISCONNECTED: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_IPC_DISCONNECTED", 0), - ERR_SERVER_NOT_RUNNING: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_SERVER_NOT_RUNNING", 0), - ERR_IPC_CHANNEL_CLOSED: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_IPC_CHANNEL_CLOSED", 0), - ERR_SOCKET_BAD_TYPE: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_SOCKET_BAD_TYPE", 0), ERR_INVALID_PROTOCOL: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_INVALID_PROTOCOL", 0), ERR_BROTLI_INVALID_PARAM: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_BROTLI_INVALID_PARAM", 0), ERR_BUFFER_TOO_LARGE: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_BUFFER_TOO_LARGE", 0), - ERR_ZLIB_INITIALIZATION_FAILED: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_ZLIB_INITIALIZATION_FAILED", 0), - ERR_BUFFER_OUT_OF_BOUNDS: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_BUFFER_OUT_OF_BOUNDS", 0), ERR_UNHANDLED_ERROR: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_UNHANDLED_ERROR", 0), }; diff --git a/src/js/internal/shared.ts b/src/js/internal/shared.ts index df0f652ee9a523..dc1dcd93e07e11 100644 --- a/src/js/internal/shared.ts +++ b/src/js/internal/shared.ts @@ -1,10 +1,11 @@ class NotImplementedError extends Error { code: string; - constructor(feature: string, issue?: number) { + constructor(feature: string, issue?: number, extra?: string) { super( feature + " is not yet implemented in Bun." + - (issue ? " Track the status & thumbs up the issue: https://github.com/oven-sh/bun/issues/" + issue : ""), + (issue ? " Track the status & thumbs up the issue: https://github.com/oven-sh/bun/issues/" + issue : "") + + (!!extra ? ". " + extra : ""), ); this.name = "NotImplementedError"; this.code = "ERR_NOT_IMPLEMENTED"; @@ -14,11 +15,11 @@ class NotImplementedError extends Error { } } -function throwNotImplemented(feature: string, issue?: number): never { +function throwNotImplemented(feature: string, issue?: number, extra?: string): never { // in the definition so that it isn't bundled unless used hideFromStack(throwNotImplemented); - throw new NotImplementedError(feature, issue); + throw new NotImplementedError(feature, issue, extra); } function hideFromStack(...fns) { diff --git a/src/js/internal/validators.ts b/src/js/internal/validators.ts index a6612d6db0e3b7..414b943e7f4e0f 100644 --- a/src/js/internal/validators.ts +++ b/src/js/internal/validators.ts @@ -1,6 +1,10 @@ const { hideFromStack } = require("internal/shared"); const { ArrayIsArray } = require("internal/primordials"); + const RegExpPrototypeExec = RegExp.prototype.exec; +const ArrayPrototypeIncludes = Array.prototype.includes; +const ArrayPrototypeJoin = Array.prototype.join; +const ArrayPrototypeMap = Array.prototype.map; const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; /** @@ -65,6 +69,18 @@ function validateObject(value, name) { } hideFromStack(validateObject); +function validateOneOf(value, name, oneOf) { + if (!ArrayPrototypeIncludes.$call(oneOf, value)) { + const allowed = ArrayPrototypeJoin.$call( + ArrayPrototypeMap.$call(oneOf, v => (typeof v === "string" ? `'${v}'` : String(v))), + ", ", + ); + const reason = "must be one of: " + allowed; + throw $ERR_INVALID_ARG_VALUE(name, value, reason); + } +} +hideFromStack(validateOneOf); + export default { validateObject: validateObject, validateLinkHeaderValue: validateLinkHeaderValue, @@ -103,4 +119,6 @@ export default { validateUndefined: $newCppFunction("NodeValidator.cpp", "jsFunction_validateUndefined", 0), /** `(buffer, name = 'buffer')` */ validateBuffer: $newCppFunction("NodeValidator.cpp", "jsFunction_validateBuffer", 0), + /** `(value, name, oneOf)` */ + validateOneOf, }; diff --git a/src/js/node/assert.ts b/src/js/node/assert.ts index e5343bd18e495b..daf536b8da6fa3 100644 --- a/src/js/node/assert.ts +++ b/src/js/node/assert.ts @@ -139,19 +139,6 @@ var require_errors = __commonJS({ }, TypeError, ); - createErrorType( - "ERR_INVALID_ARG_VALUE", - function (name, value) { - var reason = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : "is invalid"; - var inspected = util.inspect(value); - return ( - inspected.length > 128 && (inspected = "".concat(inspected.slice(0, 128), "...")), - "The argument '".concat(name, "' ").concat(reason, ". Received ").concat(inspected) - ); - }, - TypeError, - RangeError, - ); createErrorType( "ERR_INVALID_RETURN_VALUE", function (input, name, value) { @@ -835,7 +822,6 @@ var require_assert = __commonJS({ _require$codes = _require.codes, ERR_AMBIGUOUS_ARGUMENT = _require$codes.ERR_AMBIGUOUS_ARGUMENT, ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE = _require$codes.ERR_INVALID_ARG_VALUE, ERR_INVALID_RETURN_VALUE = _require$codes.ERR_INVALID_RETURN_VALUE, ERR_MISSING_ARGS = _require$codes.ERR_MISSING_ARGS, AssertionError = require_assertion_error(), @@ -1065,7 +1051,7 @@ var require_assert = __commonJS({ } var keys = Object.keys(expected); if (expected instanceof Error) keys.push("name", "message"); - else if (keys.length === 0) throw new ERR_INVALID_ARG_VALUE("error", expected, "may not be an empty object"); + else if (keys.length === 0) throw $ERR_INVALID_ARG_VALUE("error", expected, "may not be an empty object"); return ( keys.forEach(function (key) { return ( diff --git a/src/js/node/child_process.ts b/src/js/node/child_process.ts index 30e2077b808090..d8a12370b4ebd2 100644 --- a/src/js/node/child_process.ts +++ b/src/js/node/child_process.ts @@ -2,7 +2,6 @@ const EventEmitter = require("node:events"); const StreamModule = require("node:stream"); const OsModule = require("node:os"); -const { ERR_INVALID_ARG_TYPE, ERR_IPC_DISCONNECTED } = require("internal/errors"); const { kHandle } = require("internal/shared"); const { validateBoolean, @@ -76,9 +75,7 @@ var ReadableFromWeb; // TODO: Add these params after support added in Bun.spawn // uid Sets the user identity of the process (see setuid(2)). // gid Sets the group identity of the process (see setgid(2)). -// detached Prepare child to run independently of its parent process. Specific behavior depends on the platform, see options.detached). -// TODO: Add support for ipc option, verify only one IPC channel in array // stdio | Child's stdio configuration (see options.stdio). // Support wrapped ipc types (e.g. net.Socket, dgram.Socket, TTY, etc.) // IPC FD passing support @@ -556,7 +553,7 @@ function spawnSync(file, args, options) { } else if (typeof input === "string") { bunStdio[0] = Buffer.from(input, encoding || "utf8"); } else { - throw ERR_INVALID_ARG_TYPE(`options.stdio[0]`, ["Buffer", "TypedArray", "DataView", "string"], input); + throw $ERR_INVALID_ARG_TYPE(`options.stdio[0]`, ["Buffer", "TypedArray", "DataView", "string"], input); } } @@ -699,7 +696,7 @@ function stdioStringToArray(stdio, channel) { options = [0, 1, 2]; break; default: - throw ERR_INVALID_ARG_VALUE("stdio", stdio); + throw $ERR_INVALID_ARG_VALUE("stdio", stdio); } if (channel) $arrayPush(options, channel); @@ -797,7 +794,7 @@ function sanitizeKillSignal(killSignal) { if (typeof killSignal === "string" || typeof killSignal === "number") { return convertToValidSignal(killSignal); } else if (killSignal != null) { - throw ERR_INVALID_ARG_TYPE("options.killSignal", ["string", "number"], killSignal); + throw $ERR_INVALID_ARG_TYPE("options.killSignal", ["string", "number"], killSignal); } } @@ -877,14 +874,14 @@ function normalizeSpawnArguments(file, args, options) { validateString(file, "file"); validateArgumentNullCheck(file, "file"); - if (file.length === 0) throw ERR_INVALID_ARG_VALUE("file", file, "cannot be empty"); + if (file.length === 0) throw $ERR_INVALID_ARG_VALUE("file", file, "cannot be empty"); if ($isJSArray(args)) { args = ArrayPrototypeSlice.$call(args); } else if (args == null) { args = []; } else if (typeof args !== "object") { - throw ERR_INVALID_ARG_TYPE("args", "object", args); + throw $ERR_INVALID_ARG_TYPE("args", "object", args); } else { options = args; args = []; @@ -909,17 +906,17 @@ function normalizeSpawnArguments(file, args, options) { // Validate the uid, if present. if (options.uid != null && !isInt32(options.uid)) { - throw ERR_INVALID_ARG_TYPE("options.uid", "int32", options.uid); + throw $ERR_INVALID_ARG_TYPE("options.uid", "int32", options.uid); } // Validate the gid, if present. if (options.gid != null && !isInt32(options.gid)) { - throw ERR_INVALID_ARG_TYPE("options.gid", "int32", options.gid); + throw $ERR_INVALID_ARG_TYPE("options.gid", "int32", options.gid); } // Validate the shell, if present. if (options.shell != null && typeof options.shell !== "boolean" && typeof options.shell !== "string") { - throw ERR_INVALID_ARG_TYPE("options.shell", ["boolean", "string"], options.shell); + throw $ERR_INVALID_ARG_TYPE("options.shell", ["boolean", "string"], options.shell); } // Validate argv0, if present. @@ -1340,7 +1337,7 @@ class ChildProcess extends EventEmitter { options = undefined; } else if (options !== undefined) { if (typeof options !== "object" || options === null) { - throw ERR_INVALID_ARG_TYPE("options", "object", options); + throw $ERR_INVALID_ARG_TYPE("options", "object", options); } } @@ -1373,7 +1370,7 @@ class ChildProcess extends EventEmitter { $assert(this.connected); this.#handle.disconnect(); } else if (!ok) { - this.emit("error", ERR_IPC_DISCONNECTED()); + this.emit("error", $ERR_IPC_DISCONNECTED()); return; } this.#handle.disconnect(); @@ -1432,7 +1429,7 @@ const nodeToBunLookup = { ipc: "ipc", }; -function nodeToBun(item, index) { +function nodeToBun(item: string, index: number): string | number | null { // If not defined, use the default. // For stdin/stdout/stderr, it's pipe. For others, it's ignore. if (item == null) { @@ -1501,6 +1498,7 @@ function fdToStdioName(fd) { function getBunStdioFromOptions(stdio) { const normalizedStdio = normalizeStdio(stdio); + if (normalizedStdio.filter(v => v === "ipc").length > 1) throw $ERR_IPC_ONE_PIPE(); // Node options: // pipe: just a pipe // ipc = can only be one in array @@ -1527,7 +1525,7 @@ function getBunStdioFromOptions(stdio) { return bunStdio; } -function normalizeStdio(stdio) { +function normalizeStdio(stdio): string[] { if (typeof stdio === "string") { switch (stdio) { case "ignore": @@ -1616,7 +1614,7 @@ function validateMaxBuffer(maxBuffer) { function validateArgumentNullCheck(arg, propName) { if (typeof arg === "string" && StringPrototypeIncludes.$call(arg, "\u0000")) { - throw ERR_INVALID_ARG_VALUE(propName, arg, "must be a string without null bytes"); + throw $ERR_INVALID_ARG_VALUE(propName, arg, "must be a string without null bytes"); } } @@ -1649,7 +1647,7 @@ const validateOneOf = (value, name, oneOf) => { ", ", ); const reason = "must be one of: " + allowed; - throw ERR_INVALID_ARG_VALUE(name, value, reason); + throw $ERR_INVALID_ARG_VALUE(name, value, reason); } }; @@ -1675,7 +1673,7 @@ const validateObject = (value, name, options = null) => { (!allowArray && $isJSArray(value)) || (typeof value !== "object" && (!allowFunction || typeof value !== "function")) ) { - throw ERR_INVALID_ARG_TYPE(name, "object", value); + throw $ERR_INVALID_ARG_TYPE(name, "object", value); } }; @@ -1696,7 +1694,7 @@ function nullCheck(path, propName, throwError = true) { return; } - const err = ERR_INVALID_ARG_VALUE(propName, path, "must be a string or Uint8Array without null bytes"); + const err = $ERR_INVALID_ARG_VALUE(propName, path, "must be a string or Uint8Array without null bytes"); if (throwError) { throw err; } @@ -1705,7 +1703,7 @@ function nullCheck(path, propName, throwError = true) { function validatePath(path, propName = "path") { if (typeof path !== "string" && !isUint8Array(path)) { - throw ERR_INVALID_ARG_TYPE(propName, ["string", "Buffer", "URL"], path); + throw $ERR_INVALID_ARG_TYPE(propName, ["string", "Buffer", "URL"], path); } const err = nullCheck(path, propName, false); @@ -1751,7 +1749,7 @@ class AbortError extends Error { name = "AbortError"; constructor(message = "The operation was aborted", options = undefined) { if (options !== undefined && typeof options !== "object") { - throw ERR_INVALID_ARG_TYPE("options", "object", options); + throw $ERR_INVALID_ARG_TYPE("options", "object", options); } super(message, options); } @@ -1937,12 +1935,6 @@ function ERR_INVALID_OPT_VALUE(name, value) { return err; } -function ERR_INVALID_ARG_VALUE(name, value, reason) { - const err = new Error(`The value "${value}" is invalid for argument '${name}'. Reason: ${reason}`); - err.code = "ERR_INVALID_ARG_VALUE"; - return err; -} - function ERR_CHILD_PROCESS_IPC_REQUIRED(name) { const err = new TypeError(`Forked processes must have an IPC channel, missing value 'ipc' in ${name}`); err.code = "ERR_CHILD_PROCESS_IPC_REQUIRED"; diff --git a/src/js/node/dgram.ts b/src/js/node/dgram.ts index b83c64bafdc437..ed652132e50060 100644 --- a/src/js/node/dgram.ts +++ b/src/js/node/dgram.ts @@ -34,7 +34,6 @@ const kStateSymbol = Symbol("state symbol"); const async_id_symbol = Symbol("async_id_symbol"); const { hideFromStack, throwNotImplemented } = require("internal/shared"); -const { ERR_SOCKET_BAD_TYPE } = require("internal/errors"); const { validateString, validateNumber, @@ -54,48 +53,6 @@ const { const EventEmitter = require("node:events"); -class ERR_OUT_OF_RANGE extends Error { - constructor(argumentName, range, received) { - super(`The value of "${argumentName}" is out of range. It must be ${range}. Received ${received}`); - this.code = "ERR_OUT_OF_RANGE"; - } -} - -class ERR_BUFFER_OUT_OF_BOUNDS extends Error { - constructor() { - super("Buffer offset or length is out of bounds"); - this.code = "ERR_BUFFER_OUT_OF_BOUNDS"; - } -} - -class ERR_INVALID_ARG_TYPE extends Error { - constructor(argName, expected, actual) { - super(`The "${argName}" argument must be of type ${expected}. Received type ${typeof actual}`); - this.code = "ERR_INVALID_ARG_TYPE"; - } -} - -class ERR_MISSING_ARGS extends Error { - constructor(argName) { - super(`The "${argName}" argument is required`); - this.code = "ERR_MISSING_ARGS"; - } -} - -class ERR_SOCKET_ALREADY_BOUND extends Error { - constructor() { - super("Socket is already bound"); - this.code = "ERR_SOCKET_ALREADY_BOUND"; - } -} - -class ERR_SOCKET_BAD_BUFFER_SIZE extends Error { - constructor() { - super("Buffer size must be a number"); - this.code = "ERR_SOCKET_BAD_BUFFER_SIZE"; - } -} - class ERR_SOCKET_BUFFER_SIZE extends Error { constructor(ctx) { super(`Invalid buffer size: ${ctx}`); @@ -103,34 +60,6 @@ class ERR_SOCKET_BUFFER_SIZE extends Error { } } -class ERR_SOCKET_DGRAM_IS_CONNECTED extends Error { - constructor() { - super("Socket is connected"); - this.code = "ERR_SOCKET_DGRAM_IS_CONNECTED"; - } -} - -class ERR_SOCKET_DGRAM_NOT_CONNECTED extends Error { - constructor() { - super("Socket is not connected"); - this.code = "ERR_SOCKET_DGRAM_NOT_CONNECTED"; - } -} - -class ERR_SOCKET_BAD_PORT extends Error { - constructor(name, port, allowZero) { - super(`Invalid ${name}: ${port}. Ports must be >= 0 and <= 65535. ${allowZero ? "0" : ""}`); - this.code = "ERR_SOCKET_BAD_PORT"; - } -} - -class ERR_SOCKET_DGRAM_NOT_RUNNING extends Error { - constructor() { - super("Socket is not running"); - this.code = "ERR_SOCKET_DGRAM_NOT_RUNNING"; - } -} - function isInt32(value) { return value === (value | 0); } @@ -167,7 +96,7 @@ function newHandle(type, lookup) { } else if (type === "udp6") { handle.lookup = FunctionPrototypeBind(lookup6, handle, lookup); } else { - throw new ERR_SOCKET_BAD_TYPE(); + throw $ERR_SOCKET_BAD_TYPE(); } return handle; @@ -241,7 +170,7 @@ function createSocket(type, listener) { } function bufferSize(self, size, buffer) { - if (size >>> 0 !== size) throw new ERR_SOCKET_BAD_BUFFER_SIZE(); + if (size >>> 0 !== size) throw $ERR_SOCKET_BAD_BUFFER_SIZE(); const ctx = {}; // const ret = self[kStateSymbol].handle.bufferSize(size, buffer, ctx); @@ -257,7 +186,7 @@ Socket.prototype.bind = function (port_, address_ /* , callback */) { const state = this[kStateSymbol]; - if (state.bindState !== BIND_STATE_UNBOUND) throw new ERR_SOCKET_ALREADY_BOUND(); + if (state.bindState !== BIND_STATE_UNBOUND) throw $ERR_SOCKET_ALREADY_BOUND(); state.bindState = BIND_STATE_BINDING; @@ -393,7 +322,7 @@ Socket.prototype.connect = function (port, address, callback) { const state = this[kStateSymbol]; - if (state.connectState !== CONNECT_STATE_DISCONNECTED) throw new ERR_SOCKET_DGRAM_IS_CONNECTED(); + if (state.connectState !== CONNECT_STATE_DISCONNECTED) throw $ERR_SOCKET_DGRAM_IS_CONNECTED(); state.connectState = CONNECT_STATE_CONNECTING; if (state.bindState === BIND_STATE_UNBOUND) this.bind({ port: 0, exclusive: true }, null); @@ -451,7 +380,7 @@ const disconnectFn = $newZigFunction("udp_socket.zig", "UDPSocket.jsDisconnect", Socket.prototype.disconnect = function () { const state = this[kStateSymbol]; - if (state.connectState !== CONNECT_STATE_CONNECTED) throw new ERR_SOCKET_DGRAM_NOT_CONNECTED(); + if (state.connectState !== CONNECT_STATE_CONNECTED) throw $ERR_SOCKET_DGRAM_NOT_CONNECTED(); disconnectFn.$call(state.handle.socket); state.connectState = CONNECT_STATE_DISCONNECTED; @@ -471,17 +400,17 @@ function sliceBuffer(buffer, offset, length) { if (typeof buffer === "string") { buffer = Buffer.from(buffer); } else if (!ArrayBuffer.isView(buffer)) { - throw new ERR_INVALID_ARG_TYPE("buffer", ["Buffer", "TypedArray", "DataView", "string"], buffer); + throw $ERR_INVALID_ARG_TYPE("buffer", ["Buffer", "TypedArray", "DataView", "string"], buffer); } offset = offset >>> 0; length = length >>> 0; if (offset > buffer.byteLength) { - throw new ERR_BUFFER_OUT_OF_BOUNDS("offset"); + throw $ERR_BUFFER_OUT_OF_BOUNDS("offset"); } if (offset + length > buffer.byteLength) { - throw new ERR_BUFFER_OUT_OF_BOUNDS("length"); + throw $ERR_BUFFER_OUT_OF_BOUNDS("length"); } return Buffer.from(buffer.buffer, buffer.byteOffset + offset, length); @@ -570,19 +499,19 @@ Socket.prototype.send = function (buffer, offset, length, port, address, callbac callback = offset; } - if (port || address) throw new ERR_SOCKET_DGRAM_IS_CONNECTED(); + if (port || address) throw $ERR_SOCKET_DGRAM_IS_CONNECTED(); } if (!Array.isArray(buffer)) { if (typeof buffer === "string") { list = [Buffer.from(buffer)]; } else if (!ArrayBuffer.isView(buffer)) { - throw new ERR_INVALID_ARG_TYPE("buffer", ["Buffer", "TypedArray", "DataView", "string"], buffer); + throw $ERR_INVALID_ARG_TYPE("buffer", ["Buffer", "TypedArray", "DataView", "string"], buffer); } else { list = [buffer]; } } else if (!(list = fixBufferList(buffer))) { - throw new ERR_INVALID_ARG_TYPE("buffer list arguments", ["Buffer", "TypedArray", "DataView", "string"], buffer); + throw $ERR_INVALID_ARG_TYPE("buffer list arguments", ["Buffer", "TypedArray", "DataView", "string"], buffer); } if (!connected) port = validatePort(port, "Port", false); @@ -747,7 +676,7 @@ function socketCloseNT(self) { Socket.prototype.address = function () { const addr = this[kStateSymbol].handle.socket?.address; - if (!addr) throw new ERR_SOCKET_DGRAM_NOT_RUNNING(); + if (!addr) throw $ERR_SOCKET_DGRAM_NOT_RUNNING(); return addr; }; @@ -755,11 +684,11 @@ Socket.prototype.remoteAddress = function () { const state = this[kStateSymbol]; const socket = state.handle.socket; - if (!socket) throw new ERR_SOCKET_DGRAM_NOT_RUNNING(); + if (!socket) throw $ERR_SOCKET_DGRAM_NOT_RUNNING(); - if (state.connectState !== CONNECT_STATE_CONNECTED) throw new ERR_SOCKET_DGRAM_NOT_CONNECTED(); + if (state.connectState !== CONNECT_STATE_CONNECTED) throw $ERR_SOCKET_DGRAM_NOT_CONNECTED(); - if (!socket.remoteAddress) throw new ERR_SOCKET_DGRAM_NOT_CONNECTED(); + if (!socket.remoteAddress) throw $ERR_SOCKET_DGRAM_NOT_CONNECTED(); return socket.remoteAddress; }; diff --git a/src/js/node/diagnostics_channel.ts b/src/js/node/diagnostics_channel.ts index 57722725e7c413..2aa78dbb12a186 100644 --- a/src/js/node/diagnostics_channel.ts +++ b/src/js/node/diagnostics_channel.ts @@ -2,7 +2,6 @@ // Reference: https://github.com/nodejs/node/blob/fb47afc335ef78a8cef7eac52b8ee7f045300696/lib/diagnostics_channel.js const { validateFunction } = require("internal/validators"); -const { ERR_INVALID_ARG_TYPE } = require("internal/errors"); const SafeMap = Map; const SafeFinalizationRegistry = FinalizationRegistry; @@ -212,7 +211,7 @@ function channel(name) { if (channel) return channel; if (typeof name !== "string" && typeof name !== "symbol") { - throw ERR_INVALID_ARG_TYPE("channel", "string or symbol", name); + throw $ERR_INVALID_ARG_TYPE("channel", "string or symbol", name); } return new Channel(name); @@ -237,7 +236,7 @@ const traceEvents = ["start", "end", "asyncStart", "asyncEnd", "error"]; function assertChannel(value, name) { if (!(value instanceof Channel)) { - throw ERR_INVALID_ARG_TYPE(name, ["Channel"], value); + throw $ERR_INVALID_ARG_TYPE(name, ["Channel"], value); } } @@ -264,7 +263,7 @@ class TracingChannel { this.asyncEnd = asyncEnd; this.error = error; } else { - throw ERR_INVALID_ARG_TYPE("nameOrChannels", ["string, object, or Channel"], nameOrChannels); + throw $ERR_INVALID_ARG_TYPE("nameOrChannels", ["string, object, or Channel"], nameOrChannels); } } diff --git a/src/js/node/events.ts b/src/js/node/events.ts index 85a5c7707cfca3..64a14f8edbada4 100644 --- a/src/js/node/events.ts +++ b/src/js/node/events.ts @@ -23,7 +23,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -const { ERR_INVALID_ARG_TYPE, ERR_UNHANDLED_ERROR } = require("internal/errors"); +const { ERR_UNHANDLED_ERROR } = require("internal/errors"); const { validateObject, validateInteger, @@ -55,7 +55,7 @@ const kEmptyObject = Object.freeze({ __proto__: null }); var defaultMaxListeners = 10; // EventEmitter must be a standard function because some old code will do weird tricks like `EventEmitter.$apply(this)`. -const EventEmitter = function EventEmitter(opts) { +function EventEmitter(opts) { if (this._events === undefined || this._events === this.__proto__._events) { this._events = { __proto__: null }; this._eventsCount = 0; @@ -65,13 +65,10 @@ const EventEmitter = function EventEmitter(opts) { if ((this[kCapture] = opts?.captureRejections ? Boolean(opts?.captureRejections) : EventEmitterPrototype[kCapture])) { this.emit = emitWithRejectionCapture; } -}; +} Object.defineProperty(EventEmitter, "name", { value: "EventEmitter", configurable: true }); const EventEmitterPrototype = (EventEmitter.prototype = {}); -EventEmitterPrototype._events = undefined; -EventEmitterPrototype._eventsCount = 0; -EventEmitterPrototype._maxListeners = undefined; EventEmitterPrototype.setMaxListeners = function setMaxListeners(n) { validateNumber(n, "setMaxListeners", 0); this._maxListeners = n; @@ -530,7 +527,7 @@ function on(emitter, event, options = kEmptyObject) { throw(err) { if (!err || !(err instanceof Error)) { - throw ERR_INVALID_ARG_TYPE("EventEmitter.AsyncIterator", "Error", err); + throw $ERR_INVALID_ARG_TYPE("EventEmitter.AsyncIterator", "Error", err); } errorHandler(err); }, @@ -661,7 +658,7 @@ function setMaxListeners(n = defaultMaxListeners, ...eventTargets) { } else if (typeof target.setMaxListeners === "function") { target.setMaxListeners(n); } else { - throw ERR_INVALID_ARG_TYPE("eventTargets", ["EventEmitter", "EventTarget"], target); + throw $ERR_INVALID_ARG_TYPE("eventTargets", ["EventEmitter", "EventTarget"], target); } } } @@ -689,7 +686,7 @@ function eventTargetAgnosticRemoveListener(emitter, name, listener, flags) { } else if (typeof emitter.removeEventListener === "function") { emitter.removeEventListener(name, listener, flags); } else { - throw ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter); + throw $ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter); } } @@ -703,14 +700,14 @@ function eventTargetAgnosticAddListener(emitter, name, listener, flags) { } else if (typeof emitter.addEventListener === "function") { emitter.addEventListener(name, listener, flags); } else { - throw ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter); + throw $ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter); } } class AbortError extends Error { constructor(message = "The operation was aborted", options = undefined) { if (options !== undefined && typeof options !== "object") { - throw ERR_INVALID_ARG_TYPE("options", "object", options); + throw $ERR_INVALID_ARG_TYPE("options", "object", options); } super(message, options); this.code = "ABORT_ERR"; @@ -718,12 +715,6 @@ class AbortError extends Error { } } -function ERR_OUT_OF_RANGE(name, range, value) { - const err = new RangeError(`The "${name}" argument is out of range. It must be ${range}. Received ${value}`); - err.code = "ERR_OUT_OF_RANGE"; - return err; -} - function checkListener(listener) { validateFunction(listener, "listener"); } @@ -741,19 +732,19 @@ function getMaxListeners(emitterOrTarget) { emitterOrTarget[kMaxEventTargetListeners] ??= defaultMaxListeners; return emitterOrTarget[kMaxEventTargetListeners]; } - throw ERR_INVALID_ARG_TYPE("emitter", ["EventEmitter", "EventTarget"], emitterOrTarget); + throw $ERR_INVALID_ARG_TYPE("emitter", ["EventEmitter", "EventTarget"], emitterOrTarget); } Object.defineProperty(getMaxListeners, "name", { value: "getMaxListeners" }); // Copy-pasta from Node.js source code function addAbortListener(signal, listener) { if (signal === undefined) { - throw ERR_INVALID_ARG_TYPE("signal", "AbortSignal", signal); + throw $ERR_INVALID_ARG_TYPE("signal", "AbortSignal", signal); } validateAbortSignal(signal, "signal"); if (typeof listener !== "function") { - throw ERR_INVALID_ARG_TYPE("listener", "function", listener); + throw $ERR_INVALID_ARG_TYPE("listener", "function", listener); } let removeEventListener; diff --git a/src/js/node/http.ts b/src/js/node/http.ts index 08cd434ade6d4a..46c29c17ba046d 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -2,7 +2,7 @@ const EventEmitter = require("node:events"); const { isTypedArray } = require("node:util/types"); const { Duplex, Readable, Writable } = require("node:stream"); -const { ERR_INVALID_ARG_TYPE, ERR_INVALID_PROTOCOL } = require("internal/errors"); +const { ERR_INVALID_PROTOCOL } = require("internal/errors"); const { isPrimary } = require("internal/cluster/isPrimary"); const { kAutoDestroyed } = require("internal/shared"); const { urlToHttpOptions } = require("internal/url"); @@ -126,7 +126,7 @@ function isValidTLSArray(obj) { function validateMsecs(numberlike: any, field: string) { if (typeof numberlike !== "number" || numberlike < 0) { - throw ERR_INVALID_ARG_TYPE(field, "number", numberlike); + throw $ERR_INVALID_ARG_TYPE(field, "number", numberlike); } return numberlike; @@ -1806,7 +1806,7 @@ class ClientRequest extends OutgoingMessage { } else if (agent == null) { agent = defaultAgent; } else if (typeof agent.addRequest !== "function") { - throw ERR_INVALID_ARG_TYPE("options.agent", "Agent-like Object, undefined, or false", agent); + throw $ERR_INVALID_ARG_TYPE("options.agent", "Agent-like Object, undefined, or false", agent); } this.#agent = agent; @@ -1852,8 +1852,7 @@ class ClientRequest extends OutgoingMessage { let method = options.method; const methodIsString = typeof method === "string"; if (method !== null && method !== undefined && !methodIsString) { - // throw ERR_INVALID_ARG_TYPE("options.method", "string", method); - throw new Error("ERR_INVALID_ARG_TYPE: options.method"); + throw $ERR_INVALID_ARG_TYPE("options.method", "string", method); } if (methodIsString && method) { @@ -2088,12 +2087,7 @@ class ClientRequest extends OutgoingMessage { function validateHost(host, name) { if (host !== null && host !== undefined && typeof host !== "string") { - // throw ERR_INVALID_ARG_TYPE( - // `options.${name}`, - // ["string", "undefined", "null"], - // host, - // ); - throw new Error("Invalid arg type in options"); + throw $ERR_INVALID_ARG_TYPE(`options.${name}`, ["string", "undefined", "null"], host); } return host; } diff --git a/src/js/node/net.ts b/src/js/node/net.ts index 277900dd99e334..1fad0650748a0a 100644 --- a/src/js/node/net.ts +++ b/src/js/node/net.ts @@ -24,7 +24,6 @@ const { Duplex } = require("node:stream"); const EventEmitter = require("node:events"); const { addServerName, upgradeDuplexToTLS, isNamedPipeSocket } = require("../internal/net"); const { ExceptionWithHostPort } = require("internal/shared"); -const { ERR_SERVER_NOT_RUNNING } = require("internal/errors"); // IPv4 Segment const v4Seg = "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])"; @@ -1122,7 +1121,7 @@ class Server extends EventEmitter { if (typeof callback === "function") { if (!this._handle) { this.once("close", function close() { - callback(ERR_SERVER_NOT_RUNNING()); + callback($ERR_SERVER_NOT_RUNNING()); }); } else { this.once("close", callback); diff --git a/src/js/node/readline.ts b/src/js/node/readline.ts index 9db2708f43eadf..8cd6e674219a8f 100644 --- a/src/js/node/readline.ts +++ b/src/js/node/readline.ts @@ -270,30 +270,6 @@ var NodeError = getNodeErrorByName("Error"); var NodeTypeError = getNodeErrorByName("TypeError"); var NodeRangeError = getNodeErrorByName("RangeError"); -class ERR_INVALID_ARG_VALUE extends NodeTypeError { - constructor(name, value, reason = "not specified") { - super(`The value "${String(value)}" is invalid for argument '${name}'. Reason: ${reason}`, { - code: "ERR_INVALID_ARG_VALUE", - }); - } -} - -class ERR_INVALID_CURSOR_POS extends NodeTypeError { - constructor() { - super("Cannot set cursor row without setting its column", { - code: "ERR_INVALID_CURSOR_POS", - }); - } -} - -class ERR_OUT_OF_RANGE extends NodeRangeError { - constructor(name, range, received) { - super(`The value of "${name}" is out of range. It must be ${range}. Received ${received}`, { - code: "ERR_OUT_OF_RANGE", - }); - } -} - class ERR_USE_AFTER_CLOSE extends NodeError { constructor() { super("This socket has been ended by the other party", { @@ -881,15 +857,15 @@ function cursorTo(stream, x, y, callback) { y = undefined; } - if (NumberIsNaN(x)) throw new ERR_INVALID_ARG_VALUE("x", x); - if (NumberIsNaN(y)) throw new ERR_INVALID_ARG_VALUE("y", y); + if (NumberIsNaN(x)) throw $ERR_INVALID_ARG_VALUE("x", x); + if (NumberIsNaN(y)) throw $ERR_INVALID_ARG_VALUE("y", y); if (stream == null || (typeof x !== "number" && typeof y !== "number")) { if (typeof callback === "function") process.nextTick(callback, null); return true; } - if (typeof x !== "number") throw new ERR_INVALID_CURSOR_POS(); + if (typeof x !== "number") throw $ERR_INVALID_CURSOR_POS(); var data = typeof y !== "number" ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; return stream.write(data, callback); @@ -1286,7 +1262,7 @@ function InterfaceConstructor(input, output, completer, terminal) { if (NumberIsFinite(inputEscapeCodeTimeout)) { this.escapeCodeTimeout = inputEscapeCodeTimeout; } else { - throw new ERR_INVALID_ARG_VALUE("input.escapeCodeTimeout", this.escapeCodeTimeout); + throw $ERR_INVALID_ARG_VALUE("input.escapeCodeTimeout", this.escapeCodeTimeout); } } @@ -1299,7 +1275,7 @@ function InterfaceConstructor(input, output, completer, terminal) { } if (completer !== undefined && typeof completer !== "function") { - throw new ERR_INVALID_ARG_VALUE("completer", completer); + throw $ERR_INVALID_ARG_VALUE("completer", completer); } if (history === undefined) { @@ -1313,7 +1289,7 @@ function InterfaceConstructor(input, output, completer, terminal) { } if (typeof historySize !== "number" || NumberIsNaN(historySize) || historySize < 0) { - throw new ERR_INVALID_ARG_VALUE("historySize", historySize); + throw $ERR_INVALID_ARG_VALUE("historySize", historySize); } // Backwards compat; check the isTTY prop of the output stream diff --git a/src/js/node/stream.ts b/src/js/node/stream.ts index 26ba3188a60bb4..458ec766a8f6a4 100644 --- a/src/js/node/stream.ts +++ b/src/js/node/stream.ts @@ -31,24 +31,12 @@ const transferToNativeReadable = $newCppFunction("ReadableStream.cpp", "jsFuncti const { kAutoDestroyed } = require("internal/shared"); const { validateBoolean, - validateString, - validateNumber, - validateSignalName, - validateEncoding, - validatePort, validateInteger, validateInt32, - validateUint32, - validateArray, - validateBuffer, validateAbortSignal, validateFunction, - validatePlainFunction, - validateUndefined, } = require("internal/validators"); -const ObjectSetPrototypeOf = Object.setPrototypeOf; - const ProcessNextTick = process.nextTick; const EE = require("node:events").EventEmitter; @@ -70,14 +58,6 @@ $debug("node:stream loaded"); // Node error polyfills //------------------------------------------------------------------------------ -function ERR_INVALID_ARG_TYPE(name, type, value) { - return new Error(`The argument '${name}' is invalid. Received '${value}' for type '${type}'`); -} - -function ERR_INVALID_ARG_VALUE(name, value, reason) { - return new Error(`The value '${value}' is invalid for argument '${name}'. Reason: ${reason}`); -} - // node_modules/readable-stream/lib/ours/primordials.js var require_primordials = __commonJS({ "node_modules/readable-stream/lib/ours/primordials.js"(exports, module) { @@ -516,18 +496,6 @@ var require_errors = __commonJS({ }, TypeError, ); - E( - "ERR_INVALID_ARG_VALUE", - (name, value, reason = "is invalid") => { - let inspected = inspect(value); - if (inspected.length > 128) { - inspected = inspected.slice(0, 128) + "..."; - } - const type = name.includes(".") ? "property" : "argument"; - return `The ${type} '${name}' ${reason}. Received ${inspected}`; - }, - TypeError, - ); E( "ERR_INVALID_RETURN_VALUE", (input, name, value) => { @@ -623,10 +591,8 @@ var require_validators = __commonJS({ } = require_primordials(); var { hideStackFrames, - codes: { ERR_SOCKET_BAD_PORT, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE, ERR_UNKNOWN_SIGNAL }, + codes: { ERR_INVALID_ARG_TYPE }, } = require_errors(); - var { normalizeEncoding } = require_util(); - var { isAsyncFunction, isArrayBufferView } = require_util().types; var signals = {}; function isInt32(value) { return value === (value | 0); @@ -642,7 +608,7 @@ var require_validators = __commonJS({ } if (typeof value === "string") { if (!RegExpPrototypeTest(octalReg, value)) { - throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); + throw $ERR_INVALID_ARG_VALUE(name, value, modeDesc); } value = NumberParseInt(value, 8); } @@ -656,7 +622,7 @@ var require_validators = __commonJS({ ", ", ); const reason = "must be one of: " + allowed; - throw new ERR_INVALID_ARG_VALUE(name, value, reason); + throw $ERR_INVALID_ARG_VALUE(name, value, reason); } }); var validateObject = hideStackFrames((value, name, options) => { @@ -2018,7 +1984,7 @@ function getHighWaterMark(state, options, duplexKey, isDuplex) { if (hwm != null) { if (!NumberIsInteger(hwm) || hwm < 0) { const name = isDuplex ? `options.${duplexKey}` : "options.highWaterMark"; - throw new ERR_INVALID_ARG_VALUE(name, hwm); + throw $ERR_INVALID_ARG_VALUE(name, hwm); } return MathFloor(hwm); } @@ -2467,7 +2433,7 @@ var require_readable = __commonJS({ } = options; if (encoding !== undefined && !Buffer.isEncoding(encoding)) - throw new ERR_INVALID_ARG_VALUE(encoding, "options.encoding"); + throw $ERR_INVALID_ARG_VALUE(encoding, "options.encoding"); validateBoolean(objectMode, "options.objectMode"); // validateBoolean(native, "options.native"); @@ -5103,10 +5069,10 @@ var require_compose = __commonJS({ continue; } if (n < streams.length - 1 && !isReadable(streams[n])) { - throw new ERR_INVALID_ARG_VALUE(`streams[${n}]`, orgStreams[n], "must be readable"); + throw $ERR_INVALID_ARG_VALUE(`streams[${n}]`, orgStreams[n], "must be readable"); } if (n > 0 && !isWritable(streams[n])) { - throw new ERR_INVALID_ARG_VALUE(`streams[${n}]`, orgStreams[n], "must be writable"); + throw $ERR_INVALID_ARG_VALUE(`streams[${n}]`, orgStreams[n], "must be writable"); } } let ondrain; diff --git a/src/js/node/test.ts b/src/js/node/test.ts new file mode 100644 index 00000000000000..01470f3c9ce671 --- /dev/null +++ b/src/js/node/test.ts @@ -0,0 +1,38 @@ +// Hardcoded module "node:test" + +const { throwNotImplemented } = require("internal/shared"); + +function suite() { + throwNotImplemented("node:test", 5090, "bun:test in available in the interim."); +} + +function test() { + throwNotImplemented("node:test", 5090, "bun:test in available in the interim."); +} + +function before() { + throwNotImplemented("node:test", 5090, "bun:test in available in the interim."); +} + +function after() { + throwNotImplemented("node:test", 5090, "bun:test in available in the interim."); +} + +function beforeEach() { + throwNotImplemented("node:test", 5090, "bun:test in available in the interim."); +} + +function afterEach() { + throwNotImplemented("node:test", 5090, "bun:test in available in the interim."); +} + +export default { + suite, + test, + describe: suite, + it: test, + before, + after, + beforeEach, + afterEach, +}; diff --git a/src/js/node/timers.promises.ts b/src/js/node/timers.promises.ts index 68ac1fa3f6dae2..6d011ec78ae165 100644 --- a/src/js/node/timers.promises.ts +++ b/src/js/node/timers.promises.ts @@ -1,17 +1,10 @@ // Hardcoded module "node:timers/promises" // https://github.com/niksy/isomorphic-timers-promises/blob/master/index.js -const { validateBoolean, validateAbortSignal } = require("internal/validators"); +const { validateBoolean, validateAbortSignal, validateObject } = require("internal/validators"); const symbolAsyncIterator = Symbol.asyncIterator; -class ERR_INVALID_ARG_TYPE extends Error { - constructor(name, expected, actual) { - super(`${name} must be ${expected}, ${typeof actual} given`); - this.code = "ERR_INVALID_ARG_TYPE"; - } -} - class AbortError extends Error { constructor() { super("The operation was aborted"); @@ -19,12 +12,6 @@ class AbortError extends Error { } } -function validateObject(object, name) { - if (object === null || typeof object !== "object") { - throw new ERR_INVALID_ARG_TYPE(name, "Object", object); - } -} - function asyncIterator({ next: nextFunction, return: returnFunction }) { const result = {}; if (typeof nextFunction === "function") { diff --git a/src/js/node/util.ts b/src/js/node/util.ts index 562aad9d159b1a..a9ddd44135d437 100644 --- a/src/js/node/util.ts +++ b/src/js/node/util.ts @@ -2,12 +2,14 @@ const types = require("node:util/types"); /** @type {import('node-inspect-extracted')} */ const utl = require("internal/util/inspect"); -const { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } = require("internal/errors"); +const { ERR_OUT_OF_RANGE } = require("internal/errors"); const { promisify } = require("internal/promisify"); +const { validateString, validateOneOf } = require("internal/validators"); const internalErrorName = $newZigFunction("node_util_binding.zig", "internalErrorName", 1); const NumberIsSafeInteger = Number.isSafeInteger; +const ObjectKeys = Object.keys; var cjs_exports; @@ -137,15 +139,15 @@ var log = function log() { }; var inherits = function inherits(ctor, superCtor) { if (ctor === undefined || ctor === null) { - throw ERR_INVALID_ARG_TYPE("ctor", "function", ctor); + throw $ERR_INVALID_ARG_TYPE("ctor", "function", ctor); } if (superCtor === undefined || superCtor === null) { - throw ERR_INVALID_ARG_TYPE("superCtor", "function", superCtor); + throw $ERR_INVALID_ARG_TYPE("superCtor", "function", superCtor); } if (superCtor.prototype === undefined) { - throw ERR_INVALID_ARG_TYPE("superCtor.prototype", "object", superCtor.prototype); + throw $ERR_INVALID_ARG_TYPE("superCtor.prototype", "object", superCtor.prototype); } ctor.super_ = superCtor; Object.setPrototypeOf(ctor.prototype, superCtor.prototype); @@ -201,11 +203,7 @@ var toUSVString = input => { }; function styleText(format, text) { - if (typeof text !== "string") { - const e = new Error(`The text argument must be of type string. Received type ${typeof text}`); - e.code = "ERR_INVALID_ARG_TYPE"; - throw e; - } + validateString(text, "text"); if ($isJSArray(format)) { let left = ""; @@ -213,11 +211,7 @@ function styleText(format, text) { for (const key of format) { const formatCodes = inspect.colors[key]; if (formatCodes == null) { - const e = new Error( - `The value "${typeof key === "symbol" ? key.description : key}" is invalid for argument 'format'. Reason: must be one of: ${Object.keys(inspect.colors).join(", ")}`, - ); - e.code = "ERR_INVALID_ARG_VALUE"; - throw e; + validateOneOf(key, "format", ObjectKeys(inspect.colors)); } left += `\u001b[${formatCodes[0]}m`; right = `\u001b[${formatCodes[1]}m${right}`; @@ -229,17 +223,13 @@ function styleText(format, text) { let formatCodes = inspect.colors[format]; if (formatCodes == null) { - const e = new Error( - `The value "${typeof format === "symbol" ? format.description : format}" is invalid for argument 'format'. Reason: must be one of: ${Object.keys(inspect.colors).join(", ")}`, - ); - e.code = "ERR_INVALID_ARG_VALUE"; - throw e; + validateOneOf(format, "format", ObjectKeys(inspect.colors)); } return `\u001b[${formatCodes[0]}m${text}\u001b[${formatCodes[1]}m`; } function getSystemErrorName(err: any) { - if (typeof err !== "number") throw ERR_INVALID_ARG_TYPE("err", "number", err); + if (typeof err !== "number") throw $ERR_INVALID_ARG_TYPE("err", "number", err); if (err >= 0 || !NumberIsSafeInteger(err)) throw ERR_OUT_OF_RANGE("err", "a negative integer", err); return internalErrorName(err); } @@ -256,11 +246,11 @@ function onAbortedCallback(resolveFn: Function) { function aborted(signal: AbortSignal, resource: object) { if (!$isObject(signal) || !(signal instanceof AbortSignal)) { - throw ERR_INVALID_ARG_TYPE("signal", "AbortSignal", signal); + throw $ERR_INVALID_ARG_TYPE("signal", "AbortSignal", signal); } if (!$isObject(resource)) { - throw ERR_INVALID_ARG_TYPE("resource", "object", resource); + throw $ERR_INVALID_ARG_TYPE("resource", "object", resource); } if (signal.aborted) { diff --git a/src/js/node/zlib.ts b/src/js/node/zlib.ts index 77651013ebb156..b8bd4feadca181 100644 --- a/src/js/node/zlib.ts +++ b/src/js/node/zlib.ts @@ -24,13 +24,7 @@ const isArrayBufferView = ArrayBufferIsView; const isAnyArrayBuffer = b => b instanceof ArrayBuffer || b instanceof SharedArrayBuffer; const kMaxLength = $requireMap.$get("buffer")?.exports.kMaxLength ?? BufferModule.kMaxLength; -const { - ERR_BROTLI_INVALID_PARAM, - ERR_BUFFER_TOO_LARGE, - ERR_INVALID_ARG_TYPE, - ERR_OUT_OF_RANGE, - ERR_ZLIB_INITIALIZATION_FAILED, -} = require("internal/errors"); +const { ERR_BROTLI_INVALID_PARAM, ERR_BUFFER_TOO_LARGE, ERR_OUT_OF_RANGE } = require("internal/errors"); const { Transform, finished } = require("node:stream"); const owner_symbol = Symbol("owner_symbol"); const { @@ -126,7 +120,7 @@ function zlibBufferSync(engine, buffer) { if (isAnyArrayBuffer(buffer)) { buffer = Buffer.from(buffer); } else { - throw ERR_INVALID_ARG_TYPE("buffer", "string, Buffer, TypedArray, DataView, or ArrayBuffer", buffer); + throw $ERR_INVALID_ARG_TYPE("buffer", "string, Buffer, TypedArray, DataView, or ArrayBuffer", buffer); } } buffer = processChunkSync(engine, buffer, engine._finishFlushFlag); @@ -562,7 +556,7 @@ function Zlib(opts, mode) { if (isAnyArrayBuffer(dictionary)) { dictionary = Buffer.from(dictionary); } else { - throw ERR_INVALID_ARG_TYPE("options.dictionary", "Buffer, TypedArray, DataView, or ArrayBuffer", dictionary); + throw $ERR_INVALID_ARG_TYPE("options.dictionary", "Buffer, TypedArray, DataView, or ArrayBuffer", dictionary); } } } @@ -686,7 +680,7 @@ function Brotli(opts, mode) { const value = opts.params[origKey]; if (typeof value !== "number" && typeof value !== "boolean") { - throw ERR_INVALID_ARG_TYPE("options.params[key]", "number", opts.params[origKey]); + throw $ERR_INVALID_ARG_TYPE("options.params[key]", "number", opts.params[origKey]); } brotliInitParamsArray[key] = value; }); @@ -696,7 +690,7 @@ function Brotli(opts, mode) { this._writeState = new Uint32Array(2); if (!handle.init(brotliInitParamsArray, this._writeState, processCallback)) { - throw ERR_ZLIB_INITIALIZATION_FAILED(); + throw $ERR_ZLIB_INITIALIZATION_FAILED(); } ZlibBase.$apply(this, [opts, mode, handle, brotliDefaultOpts]); diff --git a/src/sys.zig b/src/sys.zig index 2327c3bc7edbed..fa7d22c4dff856 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -296,10 +296,12 @@ pub const Error = struct { from_libuv: if (Environment.isWindows) bool else void = if (Environment.isWindows) false else undefined, path: []const u8 = "", syscall: Syscall.Tag = Syscall.Tag.TODO, + dest: []const u8 = "", pub fn clone(this: *const Error, allocator: std.mem.Allocator) !Error { var copy = this.*; copy.path = try allocator.dupe(u8, copy.path); + copy.dest = try allocator.dupe(u8, copy.dest); return copy; } @@ -426,6 +428,10 @@ pub const Error = struct { err.path = bun.String.createUTF8(this.path); } + if (this.dest.len > 0) { + err.dest = bun.String.createUTF8(this.dest); + } + if (this.fd != bun.invalid_fd) { err.fd = this.fd; } @@ -469,7 +475,7 @@ pub fn getcwdZ(buf: *bun.PathBuffer) Maybe([:0]const u8) { var wbuf = bun.WPathBufferPool.get(); defer bun.WPathBufferPool.put(wbuf); const len: windows.DWORD = kernel32.GetCurrentDirectoryW(wbuf.len, wbuf); - if (Result.errnoSys(len, .getcwd)) |err| return err; + if (Result.errnoSysP(len, .getcwd, buf)) |err| return err; return Result{ .result = bun.strings.fromWPath(buf, wbuf[0..len]) }; } @@ -477,7 +483,7 @@ pub fn getcwdZ(buf: *bun.PathBuffer) Maybe([:0]const u8) { return if (rc != null) Result{ .result = rc.?[0..std.mem.len(rc.?) :0] } else - Result.errnoSys(@as(c_int, 0), .getcwd).?; + Result.errnoSysP(@as(c_int, 0), .getcwd, buf).?; } pub fn fchmod(fd: bun.FileDescriptor, mode: bun.Mode) Maybe(void) { @@ -485,14 +491,14 @@ pub fn fchmod(fd: bun.FileDescriptor, mode: bun.Mode) Maybe(void) { return sys_uv.fchmod(fd, mode); } - return Maybe(void).errnoSys(C.fchmod(fd.cast(), mode), .fchmod) orelse + return Maybe(void).errnoSysFd(C.fchmod(fd.cast(), mode), .fchmod, fd) orelse Maybe(void).success; } pub fn fchmodat(fd: bun.FileDescriptor, path: [:0]const u8, mode: bun.Mode, flags: i32) Maybe(void) { if (comptime Environment.isWindows) @compileError("Use fchmod instead"); - return Maybe(void).errnoSys(C.fchmodat(fd.cast(), path.ptr, mode, flags), .fchmodat) orelse + return Maybe(void).errnoSysFd(C.fchmodat(fd.cast(), path.ptr, mode, flags), .fchmodat, fd) orelse Maybe(void).success; } @@ -505,19 +511,21 @@ pub fn chmod(path: [:0]const u8, mode: bun.Mode) Maybe(void) { Maybe(void).success; } -pub fn chdirOSPath(destination: bun.OSPathSliceZ) Maybe(void) { +pub fn chdirOSPath(path: bun.stringZ, destination: if (Environment.isPosix) bun.stringZ else bun.string) Maybe(void) { if (comptime Environment.isPosix) { const rc = syscall.chdir(destination); - return Maybe(void).errnoSys(rc, .chdir) orelse Maybe(void).success; + return Maybe(void).errnoSysPD(rc, .chdir, path, destination) orelse Maybe(void).success; } if (comptime Environment.isWindows) { - if (kernel32.SetCurrentDirectory(destination) == windows.FALSE) { - log("SetCurrentDirectory({}) = {d}", .{ bun.fmt.utf16(destination), kernel32.GetLastError() }); - return Maybe(void).errnoSys(0, .chdir) orelse Maybe(void).success; + const wbuf = bun.WPathBufferPool.get(); + defer bun.WPathBufferPool.put(wbuf); + if (kernel32.SetCurrentDirectory(bun.strings.toWDirPath(wbuf, destination)) == windows.FALSE) { + log("SetCurrentDirectory({s}) = {d}", .{ destination, kernel32.GetLastError() }); + return Maybe(void).errnoSysPD(0, .chdir, path, destination) orelse Maybe(void).success; } - log("SetCurrentDirectory({}) = {d}", .{ bun.fmt.utf16(destination), 0 }); + log("SetCurrentDirectory({s}) = {d}", .{ destination, 0 }); return Maybe(void).success; } @@ -525,12 +533,16 @@ pub fn chdirOSPath(destination: bun.OSPathSliceZ) Maybe(void) { @compileError("Not implemented yet"); } -pub fn chdir(destination: anytype) Maybe(void) { +pub fn chdir(path: anytype, destination: anytype) Maybe(void) { const Type = @TypeOf(destination); if (comptime Environment.isPosix) { if (comptime Type == []u8 or Type == []const u8) { return chdirOSPath( + &(std.posix.toPosixPath(path) catch return .{ .err = .{ + .errno = @intFromEnum(bun.C.SystemErrno.EINVAL), + .syscall = .chdir, + } }), &(std.posix.toPosixPath(destination) catch return .{ .err = .{ .errno = @intFromEnum(bun.C.SystemErrno.EINVAL), .syscall = .chdir, @@ -538,25 +550,23 @@ pub fn chdir(destination: anytype) Maybe(void) { ); } - return chdirOSPath(destination); + return chdirOSPath(path, destination); } if (comptime Environment.isWindows) { if (comptime Type == *[*:0]u16) { if (kernel32.SetCurrentDirectory(destination) != 0) { - return Maybe(void).errnoSys(0, .chdir) orelse Maybe(void).success; + return Maybe(void).errnoSysPD(0, .chdir, path, destination) orelse Maybe(void).success; } return Maybe(void).success; } if (comptime Type == bun.OSPathSliceZ or Type == [:0]u16) { - return chdirOSPath(@as(bun.OSPathSliceZ, destination)); + return chdirOSPath(path, @as(bun.OSPathSliceZ, destination)); } - const wbuf = bun.WPathBufferPool.get(); - defer bun.WPathBufferPool.put(wbuf); - return chdirOSPath(bun.strings.toWDirPath(wbuf, destination)); + return chdirOSPath(path, destination); } return Maybe(void).todo(); @@ -590,7 +600,7 @@ pub fn stat(path: [:0]const u8) Maybe(bun.Stat) { if (comptime Environment.allow_assert) log("stat({s}) = {d}", .{ bun.asByteSlice(path), rc }); - if (Maybe(bun.Stat).errnoSys(rc, .stat)) |err| return err; + if (Maybe(bun.Stat).errnoSysP(rc, .stat, path)) |err| return err; return Maybe(bun.Stat){ .result = stat_ }; } } @@ -600,7 +610,7 @@ pub fn lstat(path: [:0]const u8) Maybe(bun.Stat) { return sys_uv.lstat(path); } else { var stat_ = mem.zeroes(bun.Stat); - if (Maybe(bun.Stat).errnoSys(C.lstat(path, &stat_), .lstat)) |err| return err; + if (Maybe(bun.Stat).errnoSysP(C.lstat(path, &stat_), .lstat, path)) |err| return err; return Maybe(bun.Stat){ .result = stat_ }; } } @@ -621,7 +631,7 @@ pub fn fstat(fd: bun.FileDescriptor) Maybe(bun.Stat) { if (comptime Environment.allow_assert) log("fstat({}) = {d}", .{ fd, rc }); - if (Maybe(bun.Stat).errnoSys(rc, .fstat)) |err| return err; + if (Maybe(bun.Stat).errnoSysFd(rc, .fstat, fd)) |err| return err; return Maybe(bun.Stat){ .result = stat_ }; } @@ -674,7 +684,7 @@ pub fn fstatat(fd: bun.FileDescriptor, path: [:0]const u8) Maybe(bun.Stat) { }; } var stat_ = mem.zeroes(bun.Stat); - if (Maybe(bun.Stat).errnoSys(syscall.fstatat(fd.int(), path, &stat_, 0), .fstatat)) |err| { + if (Maybe(bun.Stat).errnoSysFP(syscall.fstatat(fd.int(), path, &stat_, 0), .fstatat, fd, path)) |err| { log("fstatat({}, {s}) = {s}", .{ fd, path, @tagName(err.getErrno()) }); return err; } @@ -758,7 +768,7 @@ const fnctl_int = if (Environment.isLinux) usize else c_int; pub fn fcntl(fd: bun.FileDescriptor, cmd: i32, arg: fnctl_int) Maybe(fnctl_int) { while (true) { const result = fcntl_symbol(fd.cast(), cmd, arg); - if (Maybe(fnctl_int).errnoSys(result, .fcntl)) |err| { + if (Maybe(fnctl_int).errnoSysFd(result, .fcntl, fd)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -1278,7 +1288,7 @@ pub fn openatOSPath(dirfd: bun.FileDescriptor, file_path: bun.OSPathSliceZ, flag if (comptime Environment.allow_assert) log("openat({}, {s}) = {d}", .{ dirfd, bun.sliceTo(file_path, 0), rc }); - return Maybe(bun.FileDescriptor).errnoSys(rc, .open) orelse .{ .result = bun.toFD(rc) }; + return Maybe(bun.FileDescriptor).errnoSysFP(rc, .open, dirfd, file_path) orelse .{ .result = bun.toFD(rc) }; } else if (comptime Environment.isWindows) { return openatWindowsT(bun.OSPathChar, dirfd, file_path, flags); } @@ -1620,7 +1630,7 @@ pub fn pread(fd: bun.FileDescriptor, buf: []u8, offset: i64) Maybe(usize) { const ioffset = @as(i64, @bitCast(offset)); // the OS treats this as unsigned while (true) { const rc = pread_sym(fd.cast(), buf.ptr, adjusted_len, ioffset); - if (Maybe(usize).errnoSys(rc, .pread)) |err| { + if (Maybe(usize).errnoSysFd(rc, .pread, fd)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -1732,7 +1742,7 @@ pub fn recv(fd: bun.FileDescriptor, buf: []u8, flag: u32) Maybe(usize) { if (comptime Environment.isMac) { const rc = syscall.@"recvfrom$NOCANCEL"(fd.cast(), buf.ptr, adjusted_len, flag, null, null); - if (Maybe(usize).errnoSys(rc, .recv)) |err| { + if (Maybe(usize).errnoSysFd(rc, .recv, fd)) |err| { log("recv({}, {d}) = {s} {}", .{ fd, adjusted_len, err.err.name(), debug_timer }); return err; } @@ -1763,7 +1773,7 @@ pub fn send(fd: bun.FileDescriptor, buf: []const u8, flag: u32) Maybe(usize) { if (comptime Environment.isMac) { const rc = syscall.@"sendto$NOCANCEL"(fd.cast(), buf.ptr, buf.len, flag, null, 0); - if (Maybe(usize).errnoSys(rc, .send)) |err| { + if (Maybe(usize).errnoSysFd(rc, .send, fd)) |err| { syslog("send({}, {d}) = {s}", .{ fd, buf.len, err.err.name() }); return err; } @@ -1775,7 +1785,7 @@ pub fn send(fd: bun.FileDescriptor, buf: []const u8, flag: u32) Maybe(usize) { while (true) { const rc = linux.sendto(fd.cast(), buf.ptr, buf.len, flag, null, 0); - if (Maybe(usize).errnoSys(rc, .send)) |err| { + if (Maybe(usize).errnoSysFd(rc, .send, fd)) |err| { if (err.getErrno() == .INTR) continue; syslog("send({}, {d}) = {s}", .{ fd, buf.len, err.err.name() }); return err; @@ -1790,7 +1800,7 @@ pub fn send(fd: bun.FileDescriptor, buf: []const u8, flag: u32) Maybe(usize) { pub fn lseek(fd: bun.FileDescriptor, offset: i64, whence: usize) Maybe(usize) { while (true) { const rc = syscall.lseek(fd.cast(), offset, whence); - if (Maybe(usize).errnoSys(rc, .lseek)) |err| { + if (Maybe(usize).errnoSysFd(rc, .lseek, fd)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -1807,7 +1817,7 @@ pub fn readlink(in: [:0]const u8, buf: []u8) Maybe([:0]u8) { while (true) { const rc = syscall.readlink(in, buf.ptr, buf.len); - if (Maybe([:0]u8).errnoSys(rc, .readlink)) |err| { + if (Maybe([:0]u8).errnoSysP(rc, .readlink, in)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -1820,7 +1830,7 @@ pub fn readlinkat(fd: bun.FileDescriptor, in: [:0]const u8, buf: []u8) Maybe([:0 while (true) { const rc = syscall.readlinkat(fd.cast(), in, buf.ptr, buf.len); - if (Maybe([:0]u8).errnoSys(rc, .readlink)) |err| { + if (Maybe([:0]u8).errnoSysFP(rc, .readlink, fd, in)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -1832,14 +1842,14 @@ pub fn readlinkat(fd: bun.FileDescriptor, in: [:0]const u8, buf: []u8) Maybe([:0 pub fn ftruncate(fd: bun.FileDescriptor, size: isize) Maybe(void) { if (comptime Environment.isWindows) { if (kernel32.SetFileValidData(fd.cast(), size) == 0) { - return Maybe(void).errnoSys(0, .ftruncate) orelse Maybe(void).success; + return Maybe(void).errnoSysFd(0, .ftruncate, fd) orelse Maybe(void).success; } return Maybe(void).success; } return while (true) { - if (Maybe(void).errnoSys(syscall.ftruncate(fd.cast(), size), .ftruncate)) |err| { + if (Maybe(void).errnoSysFd(syscall.ftruncate(fd.cast(), size), .ftruncate, fd)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -2013,7 +2023,7 @@ pub fn renameat(from_dir: bun.FileDescriptor, from: [:0]const u8, to_dir: bun.Fi pub fn chown(path: [:0]const u8, uid: posix.uid_t, gid: posix.gid_t) Maybe(void) { while (true) { - if (Maybe(void).errnoSys(C.chown(path, uid, gid), .chown)) |err| { + if (Maybe(void).errnoSysP(C.chown(path, uid, gid), .chown, path)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -2023,7 +2033,7 @@ pub fn chown(path: [:0]const u8, uid: posix.uid_t, gid: posix.gid_t) Maybe(void) pub fn symlink(target: [:0]const u8, dest: [:0]const u8) Maybe(void) { while (true) { - if (Maybe(void).errnoSys(syscall.symlink(target, dest), .symlink)) |err| { + if (Maybe(void).errnoSysPD(syscall.symlink(target, dest), .symlink, target, dest)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -2184,7 +2194,7 @@ pub fn unlink(from: [:0]const u8) Maybe(void) { } while (true) { - if (Maybe(void).errnoSys(syscall.unlink(from), .unlink)) |err| { + if (Maybe(void).errnoSysP(syscall.unlink(from), .unlink, from)) |err| { if (err.getErrno() == .INTR) continue; return err; } @@ -2213,7 +2223,7 @@ pub fn unlinkatWithFlags(dirfd: bun.FileDescriptor, to: anytype, flags: c_uint) } while (true) { - if (Maybe(void).errnoSys(syscall.unlinkat(dirfd.cast(), to, flags), .unlink)) |err| { + if (Maybe(void).errnoSysFP(syscall.unlinkat(dirfd.cast(), to, flags), .unlink, dirfd, to)) |err| { if (err.getErrno() == .INTR) continue; if (comptime Environment.allow_assert) log("unlinkat({}, {s}) = {d}", .{ dirfd, bun.sliceTo(to, 0), @intFromEnum(err.getErrno()) }); @@ -2231,7 +2241,7 @@ pub fn unlinkat(dirfd: bun.FileDescriptor, to: anytype) Maybe(void) { return unlinkatWithFlags(dirfd, to, 0); } while (true) { - if (Maybe(void).errnoSys(syscall.unlinkat(dirfd.cast(), to, 0), .unlink)) |err| { + if (Maybe(void).errnoSysFP(syscall.unlinkat(dirfd.cast(), to, 0), .unlink, dirfd, to)) |err| { if (err.getErrno() == .INTR) continue; if (comptime Environment.allow_assert) log("unlinkat({}, {s}) = {d}", .{ dirfd, bun.sliceTo(to, 0), @intFromEnum(err.getErrno()) }); @@ -2351,7 +2361,7 @@ pub fn setCloseOnExec(fd: bun.FileDescriptor) Maybe(void) { pub fn setsockopt(fd: bun.FileDescriptor, level: c_int, optname: u32, value: i32) Maybe(i32) { while (true) { const rc = syscall.setsockopt(fd.cast(), level, optname, &value, @sizeOf(i32)); - if (Maybe(i32).errnoSys(rc, .setsockopt)) |err| { + if (Maybe(i32).errnoSysFd(rc, .setsockopt, fd)) |err| { if (err.getErrno() == .INTR) continue; log("setsockopt() = {d} {s}", .{ err.err.errno, err.err.name() }); return err; @@ -2497,12 +2507,12 @@ pub fn setPipeCapacityOnLinux(fd: bun.FileDescriptor, capacity: usize) Maybe(usi // We don't use glibc here // It didn't work. Always returned 0. const pipe_len = std.os.linux.fcntl(fd.cast(), F_GETPIPE_SZ, 0); - if (Maybe(usize).errnoSys(pipe_len, .fcntl)) |err| return err; + if (Maybe(usize).errnoSysFd(pipe_len, .fcntl, fd)) |err| return err; if (pipe_len == 0) return Maybe(usize){ .result = 0 }; if (pipe_len >= capacity) return Maybe(usize){ .result = pipe_len }; const new_pipe_len = std.os.linux.fcntl(fd.cast(), F_SETPIPE_SZ, capacity); - if (Maybe(usize).errnoSys(new_pipe_len, .fcntl)) |err| return err; + if (Maybe(usize).errnoSysFd(new_pipe_len, .fcntl, fd)) |err| return err; return Maybe(usize){ .result = new_pipe_len }; } @@ -2892,7 +2902,7 @@ pub fn setFileOffset(fd: bun.FileDescriptor, offset: usize) Maybe(void) { windows.FILE_BEGIN, ); if (rc == windows.FALSE) { - return Maybe(void).errnoSys(0, .lseek) orelse Maybe(void).success; + return Maybe(void).errnoSysFd(0, .lseek, fd) orelse Maybe(void).success; } return Maybe(void).success; } @@ -2903,7 +2913,7 @@ pub fn setFileOffsetToEndWindows(fd: bun.FileDescriptor) Maybe(usize) { var new_ptr: std.os.windows.LARGE_INTEGER = undefined; const rc = kernel32.SetFilePointerEx(fd.cast(), 0, &new_ptr, windows.FILE_END); if (rc == windows.FALSE) { - return Maybe(usize).errnoSys(0, .lseek) orelse Maybe(usize){ .result = 0 }; + return Maybe(usize).errnoSysFd(0, .lseek, fd) orelse Maybe(usize){ .result = 0 }; } return Maybe(usize){ .result = @intCast(new_ptr) }; } @@ -2922,10 +2932,7 @@ pub fn pipe() Maybe([2]bun.FileDescriptor) { var fds: [2]i32 = undefined; const rc = syscall.pipe(&fds); - if (Maybe([2]bun.FileDescriptor).errnoSys( - rc, - .pipe, - )) |err| { + if (Maybe([2]bun.FileDescriptor).errnoSys(rc, .pipe)) |err| { return err; } log("pipe() = [{d}, {d}]", .{ fds[0], fds[1] }); diff --git a/test/bundler/bundler_compile.test.ts b/test/bundler/bundler_compile.test.ts index d4c3610527483a..3949d409ea8d27 100644 --- a/test/bundler/bundler_compile.test.ts +++ b/test/bundler/bundler_compile.test.ts @@ -73,7 +73,7 @@ describe.todoIf(isFlaky && isWindows)("bundler", () => { import {rmSync} from 'fs'; // Verify we're not just importing from the filesystem rmSync("./worker.ts", {force: true}); - + console.log("Hello, world!"); new Worker("./worker"); `, diff --git a/test/cli/hot/hot.test.ts b/test/cli/hot/hot.test.ts index 9b53bb733c3420..8453a87dde99c8 100644 --- a/test/cli/hot/hot.test.ts +++ b/test/cli/hot/hot.test.ts @@ -1,4 +1,4 @@ -import { spawn } from "bun"; +import { spawn, stderr } from "bun"; import { beforeEach, expect, it } from "bun:test"; import { copyFileSync, cpSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "fs"; import { bunEnv, bunExe, isDebug, tmpdirSync, waitForFileToExist } from "harness"; @@ -450,7 +450,7 @@ ${" ".repeat(reloadCounter * 2)}throw new Error(${reloadCounter});`, let it = str.split("\n"); let line; while ((line = it.shift())) { - if (!line.includes("error")) continue; + if (!line.includes("error:")) continue; str = ""; if (reloadCounter === 50) { @@ -530,7 +530,7 @@ ${" ".repeat(reloadCounter * 2)}throw new Error(${reloadCounter});`, let it = str.split("\n"); let line; while ((line = it.shift())) { - if (!line.includes("error")) continue; + if (!line.includes("error:")) continue; str = ""; if (reloadCounter === 50) { diff --git a/test/harness.ts b/test/harness.ts index d83a0372cc20dc..5fe4cf1a18727a 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -1389,9 +1389,9 @@ Object.defineProperty(globalThis, "gc", { configurable: true, }); -export function waitForFileToExist(path: string, interval: number) { +export function waitForFileToExist(path: string, interval_ms: number) { while (!fs.existsSync(path)) { - sleepSync(interval); + sleepSync(interval_ms); } } diff --git a/test/js/bun/net/socket.test.ts b/test/js/bun/net/socket.test.ts index e3735148f3339f..693ce9e808d37b 100644 --- a/test/js/bun/net/socket.test.ts +++ b/test/js/bun/net/socket.test.ts @@ -220,7 +220,8 @@ it("should reject on connection error, calling both connectError() and rejecting expect(socket).toBeDefined(); expect(socket.data).toBe(data); expect(error).toBeDefined(); - expect(error.name).toBe("ECONNREFUSED"); + expect(error.name).toBe("Error"); + expect(error.code).toBe("ECONNREFUSED"); expect(error.message).toBe("Failed to connect"); }, data() { @@ -246,7 +247,8 @@ it("should reject on connection error, calling both connectError() and rejecting () => done(new Error("Promise should reject instead")), err => { expect(err).toBeDefined(); - expect(err.name).toBe("ECONNREFUSED"); + expect(err.name).toBe("Error"); + expect(err.code).toBe("ECONNREFUSED"); expect(err.message).toBe("Failed to connect"); done(); @@ -293,7 +295,7 @@ it("should handle connection error", done => { expect(socket).toBeDefined(); expect(socket.data).toBe(data); expect(error).toBeDefined(); - expect(error.name).toBe("ECONNREFUSED"); + expect(error.name).toBe("Error"); expect(error.message).toBe("Failed to connect"); expect((error as any).code).toBe("ECONNREFUSED"); done(); @@ -595,6 +597,7 @@ it("should not call drain before handshake", async () => { }); it("upgradeTLS handles errors", async () => { using server = Bun.serve({ + port: 0, tls, async fetch(req) { return new Response("Hello World"); @@ -699,6 +702,7 @@ it("upgradeTLS handles errors", async () => { }); it("should be able to upgrade to TLS", async () => { using server = Bun.serve({ + port: 0, tls, async fetch(req) { return new Response("Hello World"); diff --git a/test/js/bun/test/snapshot-tests/snapshots/snapshot.test.ts b/test/js/bun/test/snapshot-tests/snapshots/snapshot.test.ts index fc014b9faf818d..d211fd4c19ebb3 100644 --- a/test/js/bun/test/snapshot-tests/snapshots/snapshot.test.ts +++ b/test/js/bun/test/snapshot-tests/snapshots/snapshot.test.ts @@ -533,7 +533,7 @@ describe("inline snapshots", () => { (\r ${v("", bad, '`"12"`')})\r ; - expect("13").toMatchInlineSnapshot(${v("", bad, '`"13"`')}); expect("14").toMatchInlineSnapshot(${v("", bad, '`"14"`')}); expect("15").toMatchInlineSnapshot(${v("", bad, '`"15"`')}); + expect("13").toMatchInlineSnapshot(${v("", bad, '`"13"`')}); expect("14").toMatchInlineSnapshot(${v("", bad, '`"14"`')}); expect("15").toMatchInlineSnapshot(${v("", bad, '`"15"`')}); expect({a: new Date()}).toMatchInlineSnapshot({a: expect.any(Date)}${v("", ', "bad"', ', `\n{\n "a": Any,\n}\n`')}); expect({a: new Date()}).toMatchInlineSnapshot({a: expect.any(Date)}${v(",", ', "bad"', ', `\n{\n "a": Any,\n}\n`')}); expect({a: new Date()}).toMatchInlineSnapshot({a: expect.any(Date)\n}${v("", ', "bad"', ', `\n{\n "a": Any,\n}\n`')}); diff --git a/test/js/bun/test/stack.test.ts b/test/js/bun/test/stack.test.ts index dd32267d49091b..3b0c3a60618861 100644 --- a/test/js/bun/test/stack.test.ts +++ b/test/js/bun/test/stack.test.ts @@ -113,7 +113,8 @@ test("throwing inside an error suppresses the error and continues printing prope const { stderr, exitCode } = result; - expect(stderr.toString().trim()).toStartWith(`ENOENT: No such file or directory + expect(stderr.toString().trim()).toStartWith(`error: No such file or directory + code: "ENOENT", path: "this-file-path-is-bad", syscall: "open", errno: -2, diff --git a/test/js/bun/util/__snapshots__/inspect-error.test.js.snap b/test/js/bun/util/__snapshots__/inspect-error.test.js.snap index a6a949433dfe1a..eff7103964ba8f 100644 --- a/test/js/bun/util/__snapshots__/inspect-error.test.js.snap +++ b/test/js/bun/util/__snapshots__/inspect-error.test.js.snap @@ -2,7 +2,7 @@ exports[`error.cause 1`] = ` "1 | import { expect, test } from "bun:test"; -2 | +2 | 3 | test("error.cause", () => { 4 | const err = new Error("error 1"); 5 | const err2 = new Error("error 2", { cause: err }); @@ -11,7 +11,7 @@ error: error 2 at [dir]/inspect-error.test.js:5:16 1 | import { expect, test } from "bun:test"; -2 | +2 | 3 | test("error.cause", () => { 4 | const err = new Error("error 1"); ^ @@ -24,7 +24,7 @@ exports[`Error 1`] = ` " 9 | .replaceAll("//", "/"), 10 | ).toMatchSnapshot(); 11 | }); -12 | +12 | 13 | test("Error", () => { 14 | const err = new Error("my message"); ^ @@ -65,7 +65,7 @@ exports[`Error inside minified file (color) 1`] = ` 23 | arguments);c=b;c.s=1;return c.v=g}catch(h){throw g=b,g.s=2,g.v=h,h;}}}; 24 | exports.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+a+".");var f=C({},a.props),d=a.key,e=a.ref,g=a._owner;if(null!=b){void 0!==b.ref&&(e=b.ref,g=K.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var h=a.type.defaultProps;for(k in b)J.call(b,k)&&!L.hasOwnProperty(k)&&(f[k]=void 0===b[k]&&void 0!==h?h[k]:b[k])}var k=arguments.length-2;if(1===k)f.children=c;else if(1 { readdirSync(import.meta.path); throw new Error("should not get here"); } catch (exception: any) { - expect(exception.name).toBe("ENOTDIR"); + expect(exception.name).toBe("Error"); + expect(exception.code).toBe("ENOTDIR"); } }); @@ -1126,7 +1127,8 @@ it("readdirSync throws when given a path that doesn't exist", () => { } catch (exception: any) { // the correct error to return in this case is actually ENOENT (which we do on windows), // but on posix we return ENOTDIR - expect(exception.name).toMatch(/ENOTDIR|ENOENT/); + expect(exception.name).toBe("Error"); + expect(exception.code).toMatch(/ENOTDIR|ENOENT/); } }); @@ -1135,7 +1137,8 @@ it("readdirSync throws when given a file path with trailing slash", () => { readdirSync(import.meta.path + "/"); throw new Error("should not get here"); } catch (exception: any) { - expect(exception.name).toBe("ENOTDIR"); + expect(exception.name).toBe("Error"); + expect(exception.code).toBe("ENOTDIR"); } }); diff --git a/test/js/node/net/node-net-server.test.ts b/test/js/node/net/node-net-server.test.ts index 70034749ed75a4..0572567901611a 100644 --- a/test/js/node/net/node-net-server.test.ts +++ b/test/js/node/net/node-net-server.test.ts @@ -285,7 +285,8 @@ describe("net.createServer listen", () => { expect(err).not.toBeNull(); expect(err!.message).toBe("Failed to connect"); - expect(err!.name).toBe("ECONNREFUSED"); + expect(err!.name).toBe("Error"); + expect(err!.code).toBe("ECONNREFUSED"); server.close(); done(); diff --git a/test/js/node/process/call-constructor.test.js b/test/js/node/process/call-constructor.test.js new file mode 100644 index 00000000000000..7522966572836a --- /dev/null +++ b/test/js/node/process/call-constructor.test.js @@ -0,0 +1,11 @@ +import { expect, test } from "bun:test"; +import process from "process"; + +test("the constructor of process can be called", () => { + let obj = process.constructor.call({ ...process }); + expect(Object.getPrototypeOf(obj)).toEqual(Object.getPrototypeOf(process)); +}); + +test("#14346", () => { + process.__proto__.constructor.call({}); +}); diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index 70555508479ae0..965105f56bf5a9 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -2,7 +2,7 @@ import { spawnSync, which } from "bun"; import { describe, expect, it } from "bun:test"; import { existsSync, readFileSync, writeFileSync } from "fs"; import { bunEnv, bunExe, isWindows, tmpdirSync } from "harness"; -import { basename, join, resolve } from "path"; +import path, { basename, join, resolve } from "path"; import { familySync } from "detect-libc"; expect.extend({ @@ -236,12 +236,16 @@ it("process.uptime()", () => { }); it("process.umask()", () => { - let notNumbers = [265n, "string", true, false, null, {}, [], () => {}, Symbol("symbol"), BigInt(1)]; - for (let notNumber of notNumbers) { - expect(() => { - process.umask(notNumber); - }).toThrow('The "mask" argument must be of type number'); - } + expect(() => process.umask(265n)).toThrow('The "mask" argument must be of type number. Received type bigint (265n)'); + expect(() => process.umask("string")).toThrow(`The argument 'mask' must be a 32-bit unsigned integer or an octal string. Received "string"`); // prettier-ignore + expect(() => process.umask(true)).toThrow('The "mask" argument must be of type number. Received type boolean (true)'); + expect(() => process.umask(false)).toThrow('The "mask" argument must be of type number. Received type boolean (false)'); // prettier-ignore + expect(() => process.umask(null)).toThrow('The "mask" argument must be of type number. Received null'); + expect(() => process.umask({})).toThrow('The "mask" argument must be of type number. Received an instance of Object'); + expect(() => process.umask([])).toThrow('The "mask" argument must be of type number. Received an instance of Array'); + expect(() => process.umask(() => {})).toThrow('The "mask" argument must be of type number. Received function '); + expect(() => process.umask(Symbol("symbol"))).toThrow('The "mask" argument must be of type number. Received type symbol (Symbol(symbol))'); // prettier-ignore + expect(() => process.umask(BigInt(1))).toThrow('The "mask" argument must be of type number. Received type bigint (1n)'); // prettier-ignore let rangeErrors = [NaN, -1.4, Infinity, -Infinity, -1, 1.3, 4294967296]; for (let rangeError of rangeErrors) { @@ -310,20 +314,6 @@ it("process.config", () => { }); }); -it("process.emitWarning", () => { - process.emitWarning("-- Testing process.emitWarning --"); - var called = 0; - process.on("warning", err => { - called++; - expect(err.message).toBe("-- Testing process.on('warning') --"); - }); - process.emitWarning("-- Testing process.on('warning') --"); - expect(called).toBe(1); - expect(process.off("warning")).toBe(process); - process.emitWarning("-- Testing process.on('warning') --"); - expect(called).toBe(1); -}); - it("process.execArgv", () => { expect(process.execArgv instanceof Array).toBe(true); }); @@ -342,11 +332,21 @@ it("process.argv in testing", () => { describe("process.exitCode", () => { it("validates int", () => { - expect(() => (process.exitCode = "potato")).toThrow(`exitCode must be an integer`); - expect(() => (process.exitCode = 1.2)).toThrow("exitCode must be an integer"); - expect(() => (process.exitCode = NaN)).toThrow("exitCode must be an integer"); - expect(() => (process.exitCode = Infinity)).toThrow("exitCode must be an integer"); - expect(() => (process.exitCode = -Infinity)).toThrow("exitCode must be an integer"); + expect(() => (process.exitCode = "potato")).toThrow( + `The "code" argument must be of type number. Received type string ("potato")`, + ); + expect(() => (process.exitCode = 1.2)).toThrow( + `The value of \"code\" is out of range. It must be an integer. Received 1.2`, + ); + expect(() => (process.exitCode = NaN)).toThrow( + `The value of \"code\" is out of range. It must be an integer. Received NaN`, + ); + expect(() => (process.exitCode = Infinity)).toThrow( + `The value of \"code\" is out of range. It must be an integer. Received Infinity`, + ); + expect(() => (process.exitCode = -Infinity)).toThrow( + `The value of \"code\" is out of range. It must be an integer. Received -Infinity`, + ); }); it("works with implicit process.exit", () => { @@ -458,13 +458,13 @@ describe("process.cpuUsage", () => { user: -1, system: 100, }), - ).toThrow("The 'user' property must be a number between 0 and 2^53"); + ).toThrow("The property 'prevValue.user' is invalid. Received -1"); expect(() => process.cpuUsage({ user: 100, system: -1, }), - ).toThrow("The 'system' property must be a number between 0 and 2^53"); + ).toThrow("The property 'prevValue.system' is invalid. Received -1"); }); // Skipped on Windows because it seems UV returns { user: 15000, system: 0 } constantly @@ -684,13 +684,7 @@ it("dlopen accepts file: URLs", () => { }); it("process.constrainedMemory()", () => { - if (process.platform === "linux") { - // On Linux, it returns 0 if the kernel doesn't support it - expect(process.constrainedMemory() >= 0).toBe(true); - } else { - // On unsupported platforms, it returns undefined - expect(process.constrainedMemory()).toBeUndefined(); - } + expect(process.constrainedMemory() >= 0).toBe(true); }); it("process.report", () => { diff --git a/test/js/node/readline/readline.node.test.ts b/test/js/node/readline/readline.node.test.ts index caa38dcfa593e0..fecce0f34d21d1 100644 --- a/test/js/node/readline/readline.node.test.ts +++ b/test/js/node/readline/readline.node.test.ts @@ -306,15 +306,15 @@ describe("readline.cursorTo()", () => { // Verify that cursorTo() throws if x or y is NaN. assert.throws(() => { readline.cursorTo(writable, NaN); - }, /ERR_INVALID_ARG_VALUE/); + }, "ERR_INVALID_ARG_VALUE"); assert.throws(() => { readline.cursorTo(writable, 1, NaN); - }, /ERR_INVALID_ARG_VALUE/); + }, "ERR_INVALID_ARG_VALUE"); assert.throws(() => { readline.cursorTo(writable, NaN, NaN); - }, /ERR_INVALID_ARG_VALUE/); + }, "ERR_INVALID_ARG_VALUE"); }); }); diff --git a/test/js/node/test/common/index.js b/test/js/node/test/common/index.js index 6b5d1079ffad7b..40d0639a002a19 100644 --- a/test/js/node/test/common/index.js +++ b/test/js/node/test/common/index.js @@ -132,11 +132,9 @@ if (process.argv.length === 2 && const options = { encoding: 'utf8', stdio: 'inherit' }; const result = spawnSync(process.execPath, args, options); if (result.signal) { - process.kill(0, result.signal); + process.kill(process.pid, result.signal); } else { - // Ensure we don't call the "exit" callbacks, as that will cause the - // test to fail when it may have passed in the child process. - process.kill(process.pid, result.status); + process.exit(result.status); } } } @@ -900,6 +898,7 @@ function invalidArgTypeHelper(input) { let inspected = inspect(input, { colors: false }); if (inspected.length > 28) { inspected = `${inspected.slice(inspected, 0, 25)}...`; } + if (inspected.startsWith("'") && inspected.endsWith("'")) inspected = `"${inspected.slice(1, inspected.length - 1)}"`; // BUN: util.inspect uses ' but bun uses " for strings return ` Received type ${typeof input} (${inspected})`; } @@ -1218,5 +1217,3 @@ module.exports = new Proxy(common, { return obj[prop]; }, }); - - diff --git a/test/js/node/test/parallel/test-child-process-stdio.js b/test/js/node/test/parallel/test-child-process-stdio.js new file mode 100644 index 00000000000000..15c2770aa29d1a --- /dev/null +++ b/test/js/node/test/parallel/test-child-process-stdio.js @@ -0,0 +1,77 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { spawn } = require('child_process'); + +// Test stdio piping. +{ + const child = spawn(...common.pwdCommand, { stdio: ['pipe'] }); + assert.notStrictEqual(child.stdout, null); + assert.notStrictEqual(child.stderr, null); +} + +// Test stdio ignoring. +{ + const child = spawn(...common.pwdCommand, { stdio: 'ignore' }); + assert.strictEqual(child.stdout, null); + assert.strictEqual(child.stderr, null); +} + +// Asset options invariance. +{ + const options = { stdio: 'ignore' }; + spawn(...common.pwdCommand, options); + assert.deepStrictEqual(options, { stdio: 'ignore' }); +} + +// Test stdout buffering. +{ + let output = ''; + const child = spawn(...common.pwdCommand); + + child.stdout.setEncoding('utf8'); + child.stdout.on('data', function(s) { + output += s; + }); + + child.on('exit', common.mustCall(function(code) { + assert.strictEqual(code, 0); + })); + + child.on('close', common.mustCall(function() { + assert.strictEqual(output.length > 1, true); + assert.strictEqual(output[output.length - 1], '\n'); + })); +} + +// Assert only one IPC pipe allowed. +assert.throws( + () => { + spawn( + ...common.pwdCommand, + { stdio: ['pipe', 'pipe', 'pipe', 'ipc', 'ipc'] } + ); + }, + { code: 'ERR_IPC_ONE_PIPE', name: 'Error' } +); diff --git a/test/js/node/test/parallel/test-console-tty-colors.js b/test/js/node/test/parallel/test-console-tty-colors.js index 969fb53a239883..63ff42935bd4dc 100644 --- a/test/js/node/test/parallel/test-console-tty-colors.js +++ b/test/js/node/test/parallel/test-console-tty-colors.js @@ -60,7 +60,21 @@ check(false, false, false); write: common.mustNotCall() }); - [0, 'true', null, {}, [], () => {}].forEach((colorMode) => { + assert.throws( + () => { + new Console({ + stdout: stream, + ignoreErrors: false, + colorMode: 'true' + }); + }, + { + message: `The argument 'colorMode' must be one of: 'auto', true, false. Received "true"`, + code: 'ERR_INVALID_ARG_VALUE' + } + ); + + [0, null, {}, [], () => {}].forEach((colorMode) => { const received = util.inspect(colorMode); assert.throws( () => { diff --git a/test/js/node/test/parallel/test-process-assert.js b/test/js/node/test/parallel/test-process-assert.js new file mode 100644 index 00000000000000..f740d3d70c7c0f --- /dev/null +++ b/test/js/node/test/parallel/test-process-assert.js @@ -0,0 +1,19 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +assert.strictEqual(process.assert(1, 'error'), undefined); +assert.throws(() => { + process.assert(undefined, 'errorMessage'); +}, { + code: 'ERR_ASSERTION', + name: 'Error', + message: 'errorMessage' +}); +assert.throws(() => { + process.assert(false); +}, { + code: 'ERR_ASSERTION', + name: 'Error', + message: 'assertion error' +}); diff --git a/test/js/node/test/parallel/test-process-available-memory.js b/test/js/node/test/parallel/test-process-available-memory.js new file mode 100644 index 00000000000000..67de5b5e0bb000 --- /dev/null +++ b/test/js/node/test/parallel/test-process-available-memory.js @@ -0,0 +1,5 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const availableMemory = process.availableMemory(); +assert(typeof availableMemory, 'number'); diff --git a/test/js/node/test/parallel/test-process-beforeexit-throw-exit.js b/test/js/node/test/parallel/test-process-beforeexit-throw-exit.js new file mode 100644 index 00000000000000..6e9d764be90baa --- /dev/null +++ b/test/js/node/test/parallel/test-process-beforeexit-throw-exit.js @@ -0,0 +1,12 @@ +'use strict'; +const common = require('../common'); +common.skipIfWorker(); + +// Test that 'exit' is emitted if 'beforeExit' throws. + +process.on('exit', common.mustCall(() => { + process.exitCode = 0; +})); +process.on('beforeExit', common.mustCall(() => { + throw new Error(); +})); diff --git a/test/js/node/test/parallel/test-process-beforeexit.js b/test/js/node/test/parallel/test-process-beforeexit.js new file mode 100644 index 00000000000000..e04b756cade8bc --- /dev/null +++ b/test/js/node/test/parallel/test-process-beforeexit.js @@ -0,0 +1,81 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const net = require('net'); + +process.once('beforeExit', common.mustCall(tryImmediate)); + +function tryImmediate() { + setImmediate(common.mustCall(() => { + process.once('beforeExit', common.mustCall(tryTimer)); + })); +} + +function tryTimer() { + setTimeout(common.mustCall(() => { + process.once('beforeExit', common.mustCall(tryListen)); + }), 1); +} + +function tryListen() { + net.createServer() + .listen(0) + .on('listening', common.mustCall(function() { + this.close(); + process.once('beforeExit', common.mustCall(tryRepeatedTimer)); + })); +} + +// Test that a function invoked from the beforeExit handler can use a timer +// to keep the event loop open, which can use another timer to keep the event +// loop open, etc. +// +// After N times, call function `tryNextTick` to test behaviors of the +// `process.nextTick`. +function tryRepeatedTimer() { + const N = 5; + let n = 0; + const repeatedTimer = common.mustCall(function() { + if (++n < N) + setTimeout(repeatedTimer, 1); + else // n == N + process.once('beforeExit', common.mustCall(tryNextTickSetImmediate)); + }, N); + setTimeout(repeatedTimer, 1); +} + +// Test if the callback of `process.nextTick` can be invoked. +function tryNextTickSetImmediate() { + process.nextTick(common.mustCall(function() { + setImmediate(common.mustCall(() => { + process.once('beforeExit', common.mustCall(tryNextTick)); + })); + })); +} + +// Test that `process.nextTick` won't keep the event loop running by itself. +function tryNextTick() { + process.nextTick(common.mustCall(function() { + process.once('beforeExit', common.mustNotCall()); + })); +} diff --git a/test/js/node/test/parallel/test-process-binding-util.js b/test/js/node/test/parallel/test-process-binding-util.js new file mode 100644 index 00000000000000..a834676e05be45 --- /dev/null +++ b/test/js/node/test/parallel/test-process-binding-util.js @@ -0,0 +1,58 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const util = require('util'); + +const utilBinding = process.binding('util'); +assert.deepStrictEqual( + Object.keys(utilBinding).sort(), + [ + 'isAnyArrayBuffer', + 'isArgumentsObject', + 'isArrayBuffer', + 'isArrayBufferView', + 'isAsyncFunction', + 'isBigInt64Array', + 'isBigIntObject', + 'isBigUint64Array', + 'isBooleanObject', + 'isBoxedPrimitive', + 'isCryptoKey', + 'isDataView', + 'isDate', + 'isEventTarget', + 'isExternal', + 'isFloat16Array', + 'isFloat32Array', + 'isFloat64Array', + 'isGeneratorFunction', + 'isGeneratorObject', + 'isInt16Array', + 'isInt32Array', + 'isInt8Array', + 'isKeyObject', + 'isMap', + 'isMapIterator', + 'isModuleNamespaceObject', + 'isNativeError', + 'isNumberObject', + 'isPromise', + 'isProxy', + 'isRegExp', + 'isSet', + 'isSetIterator', + 'isSharedArrayBuffer', + 'isStringObject', + 'isSymbolObject', + 'isTypedArray', + 'isUint16Array', + 'isUint32Array', + 'isUint8Array', + 'isUint8ClampedArray', + 'isWeakMap', + 'isWeakSet', + ]); + +for (const k of Object.keys(utilBinding)) { + assert.strictEqual(utilBinding[k], util.types[k]); +} diff --git a/test/js/node/test/parallel/test-process-chdir-errormessage.js b/test/js/node/test/parallel/test-process-chdir-errormessage.js new file mode 100644 index 00000000000000..16cdf4aa1deaf3 --- /dev/null +++ b/test/js/node/test/parallel/test-process-chdir-errormessage.js @@ -0,0 +1,20 @@ +'use strict'; + +const common = require('../common'); +if (!common.isMainThread) + common.skip('process.chdir is not available in Workers'); +const assert = require('assert'); + +assert.throws( + () => { + process.chdir('does-not-exist'); + }, + { + name: 'Error', + code: 'ENOENT', + // message: /ENOENT: No such file or directory, chdir .+ -> 'does-not-exist'/, + path: process.cwd(), + syscall: 'chdir', + dest: 'does-not-exist' + } +); diff --git a/test/js/node/test/parallel/test-process-chdir.js b/test/js/node/test/parallel/test-process-chdir.js new file mode 100644 index 00000000000000..ee59df853b24ce --- /dev/null +++ b/test/js/node/test/parallel/test-process-chdir.js @@ -0,0 +1,44 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +if (!common.isMainThread) + common.skip('process.chdir is not available in Workers'); + +const tmpdir = require('../common/tmpdir'); + +process.chdir('..'); +assert.notStrictEqual(process.cwd(), __dirname); +process.chdir(__dirname); +assert.strictEqual(process.cwd(), __dirname); + +let dirName; +if (process.versions.icu) { + // ICU is available, use characters that could possibly be decomposed + dirName = 'weird \uc3a4\uc3ab\uc3af characters \u00e1\u00e2\u00e3'; +} else { + // ICU is unavailable, use characters that can't be decomposed + dirName = 'weird \ud83d\udc04 characters \ud83d\udc05'; +} +const dir = tmpdir.resolve(dirName); + +// Make sure that the tmp directory is clean +tmpdir.refresh(); + +fs.mkdirSync(dir); +process.chdir(dir); +assert.strictEqual(process.cwd().normalize(), dir.normalize()); + +process.chdir('..'); +assert.strictEqual(process.cwd().normalize(), + path.resolve(tmpdir.path).normalize()); + +const err = { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "directory" argument must be of type string/ +}; +assert.throws(function() { process.chdir({}); }, err); +assert.throws(function() { process.chdir(); }, err); diff --git a/test/js/node/test/parallel/test-process-config.js b/test/js/node/test/parallel/test-process-config.js new file mode 100644 index 00000000000000..20ebc36a996385 --- /dev/null +++ b/test/js/node/test/parallel/test-process-config.js @@ -0,0 +1,69 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); + +// Checks that the internal process.config is equivalent to the config.gypi file +// created when we run configure. + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +// Check for existence of `process.config`. +assert(Object.hasOwn(process, 'config')); + +// Ensure that `process.config` is an Object. +assert.strictEqual(Object(process.config), process.config); + +// Ensure that you can't change config values +assert.throws(() => { process.config.variables = 42; }, TypeError); + +const configPath = path.resolve(__dirname, '..', '..', 'config.gypi'); + +if (!fs.existsSync(configPath)) { + common.skip('config.gypi does not exist.'); +} + +let config = fs.readFileSync(configPath, 'utf8'); + +// Clean up comment at the first line. +config = config.split('\n').slice(1).join('\n'); +config = config.replace(/"/g, '\\"'); +config = config.replace(/'/g, '"'); +config = JSON.parse(config, (key, value) => { + if (value === 'true') return true; + if (value === 'false') return false; + return value; +}); + +try { + assert.deepStrictEqual(config, process.config); +} catch (e) { + // If the assert fails, it only shows 3 lines. We need all the output to + // compare. + console.log('config:', config); + console.log('process.config:', process.config); + + throw e; +} diff --git a/test/js/node/test/parallel/test-process-constrained-memory.js b/test/js/node/test/parallel/test-process-constrained-memory.js new file mode 100644 index 00000000000000..03f99b166f72ca --- /dev/null +++ b/test/js/node/test/parallel/test-process-constrained-memory.js @@ -0,0 +1,6 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +const constrainedMemory = process.constrainedMemory(); +assert.strictEqual(typeof constrainedMemory, 'number'); diff --git a/test/js/node/test/parallel/test-process-cpuUsage.js b/test/js/node/test/parallel/test-process-cpuUsage.js new file mode 100644 index 00000000000000..f1580d5f092b72 --- /dev/null +++ b/test/js/node/test/parallel/test-process-cpuUsage.js @@ -0,0 +1,118 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const result = process.cpuUsage(); + +// Validate the result of calling with no previous value argument. +validateResult(result); + +// Validate the result of calling with a previous value argument. +validateResult(process.cpuUsage(result)); + +// Ensure the results are >= the previous. +let thisUsage; +let lastUsage = process.cpuUsage(); +for (let i = 0; i < 10; i++) { + thisUsage = process.cpuUsage(); + validateResult(thisUsage); + assert(thisUsage.user >= lastUsage.user); + assert(thisUsage.system >= lastUsage.system); + lastUsage = thisUsage; +} + +// Ensure that the diffs are >= 0. +let startUsage; +let diffUsage; +for (let i = 0; i < 10; i++) { + startUsage = process.cpuUsage(); + diffUsage = process.cpuUsage(startUsage); + validateResult(startUsage); + validateResult(diffUsage); + assert(diffUsage.user >= 0); + assert(diffUsage.system >= 0); +} + +// Ensure that an invalid shape for the previous value argument throws an error. +assert.throws( + () => process.cpuUsage(1), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "prevValue" argument must be of type object. ' + + 'Received type number (1)' + } +); + +// Check invalid types. +[ + {}, + { user: 'a' }, + { user: null, system: 'c' }, +].forEach((value) => { + assert.throws( + () => process.cpuUsage(value), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "prevValue.user" property must be of type number.' + + common.invalidArgTypeHelper(value.user) + } + ); +}); + +[ + { user: 3, system: 'b' }, + { user: 3, system: null }, +].forEach((value) => { + assert.throws( + () => process.cpuUsage(value), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "prevValue.system" property must be of type number.' + + common.invalidArgTypeHelper(value.system) + } + ); +}); + +// Check invalid values. +[ + { user: -1, system: 2 }, + { user: Number.POSITIVE_INFINITY, system: 4 }, +].forEach((value) => { + assert.throws( + () => process.cpuUsage(value), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'RangeError', + message: "The property 'prevValue.user' is invalid. " + + `Received ${value.user}`, + } + ); +}); + +[ + { user: 3, system: -2 }, + { user: 5, system: Number.NEGATIVE_INFINITY }, +].forEach((value) => { + assert.throws( + () => process.cpuUsage(value), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'RangeError', + message: "The property 'prevValue.system' is invalid. " + + `Received ${value.system}`, + } + ); +}); + +// Ensure that the return value is the expected shape. +function validateResult(result) { + assert.notStrictEqual(result, null); + + assert(Number.isFinite(result.user)); + assert(Number.isFinite(result.system)); + + assert(result.user >= 0); + assert(result.system >= 0); +} diff --git a/test/js/node/test/parallel/test-process-dlopen-error-message-crash.js b/test/js/node/test/parallel/test-process-dlopen-error-message-crash.js new file mode 100644 index 00000000000000..cc93e01abd81df --- /dev/null +++ b/test/js/node/test/parallel/test-process-dlopen-error-message-crash.js @@ -0,0 +1,47 @@ +'use strict'; + +// This is a regression test for some scenarios in which node would pass +// unsanitized user input to a printf-like formatting function when dlopen +// fails, potentially crashing the process. + +const common = require('../common'); +if (common.isWindows) return; // TODO: BUN +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const assert = require('assert'); +const fs = require('fs'); + +// This error message should not be passed to a printf-like function. +assert.throws(() => { + process.dlopen({ exports: {} }, 'foo-%s.node'); +}, ({ name, code, message }) => { + assert.strictEqual(name, 'Error'); + assert.strictEqual(code, 'ERR_DLOPEN_FAILED'); + if (!common.isAIX && !common.isIBMi) { + assert.match(message, /foo-%s\.node/); + } + return true; +}); + +const notBindingDir = 'test/addons/not-a-binding'; +const notBindingPath = `${notBindingDir}/build/Release/binding.node`; +const strangeBindingPath = `${tmpdir.path}/binding-%s.node`; +// Ensure that the addon directory exists, but skip the remainder of the test if +// the addon has not been compiled. +// fs.accessSync(notBindingDir); +// try { +// fs.copyFileSync(notBindingPath, strangeBindingPath); +// } catch (err) { +// if (err.code !== 'ENOENT') throw err; +// common.skip(`addon not found: ${notBindingPath}`); +// } + +// This error message should also not be passed to a printf-like function. +assert.throws(() => { + process.dlopen({ exports: {} }, strangeBindingPath); +}, { + name: 'Error', + code: 'ERR_DLOPEN_FAILED', + message: /binding-%s\.node/ +}); diff --git a/test/js/node/test/parallel/test-process-emitwarning.js b/test/js/node/test/parallel/test-process-emitwarning.js new file mode 100644 index 00000000000000..e1c7473f8aad3d --- /dev/null +++ b/test/js/node/test/parallel/test-process-emitwarning.js @@ -0,0 +1,81 @@ +// Flags: --no-warnings +// The flag suppresses stderr output but the warning event will still emit +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const testMsg = 'A Warning'; +const testCode = 'CODE001'; +const testDetail = 'Some detail'; +const testType = 'CustomWarning'; + +process.on('warning', common.mustCall((warning) => { + assert(warning); + assert.match(warning.name, /^(?:Warning|CustomWarning)/); + assert.strictEqual(warning.message, testMsg); + if (warning.code) assert.strictEqual(warning.code, testCode); + if (warning.detail) assert.strictEqual(warning.detail, testDetail); +}, 15)); + +class CustomWarning extends Error { + constructor() { + super(); + this.name = testType; + this.message = testMsg; + this.code = testCode; + Error.captureStackTrace(this, CustomWarning); + } +} + +[ + [testMsg], + [testMsg, testType], + [testMsg, CustomWarning], + [testMsg, testType, CustomWarning], + [testMsg, testType, testCode], + [testMsg, { type: testType }], + [testMsg, { type: testType, code: testCode }], + [testMsg, { type: testType, code: testCode, detail: testDetail }], + [new CustomWarning()], + // Detail will be ignored for the following. No errors thrown + [testMsg, { type: testType, code: testCode, detail: true }], + [testMsg, { type: testType, code: testCode, detail: [] }], + [testMsg, { type: testType, code: testCode, detail: null }], + [testMsg, { type: testType, code: testCode, detail: 1 }], +].forEach((args) => { + process.emitWarning(...args); +}); + +const warningNoToString = new CustomWarning(); +warningNoToString.toString = null; +process.emitWarning(warningNoToString); + +const warningThrowToString = new CustomWarning(); +warningThrowToString.toString = function() { + throw new Error('invalid toString'); +}; +process.emitWarning(warningThrowToString); + +// TypeError is thrown on invalid input +[ + [1], + [{}], + [true], + [[]], + ['', '', {}], + ['', 1], + ['', '', 1], + ['', true], + ['', '', true], + ['', []], + ['', '', []], + [], + [undefined, 'foo', 'bar'], + [undefined], +].forEach((args) => { + assert.throws( + () => process.emitWarning(...args), + { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' } + ); +}); diff --git a/test/js/node/test/parallel/test-process-euid-egid.js b/test/js/node/test/parallel/test-process-euid-egid.js new file mode 100644 index 00000000000000..06854ba3f574fe --- /dev/null +++ b/test/js/node/test/parallel/test-process-euid-egid.js @@ -0,0 +1,70 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +if (common.isWindows) { + assert.strictEqual(process.geteuid, undefined); + assert.strictEqual(process.getegid, undefined); + assert.strictEqual(process.seteuid, undefined); + assert.strictEqual(process.setegid, undefined); + return; +} + +if (!common.isMainThread) + return; + +assert.throws(() => { + process.seteuid({}); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "id" argument must be of type number or string. ' + + 'Received an instance of Object' +}); + +assert.throws(() => { + process.seteuid('fhqwhgadshgnsdhjsdbkhsdabkfabkveyb'); +}, { + code: 'ERR_UNKNOWN_CREDENTIAL', + message: 'User identifier does not exist: fhqwhgadshgnsdhjsdbkhsdabkfabkveyb' +}); + +// IBMi does not support below operations. +if (common.isIBMi) + return; + +// If we're not running as super user... +if (process.getuid() !== 0) { + // Should not throw. + process.getegid(); + process.geteuid(); + + assert.throws(() => { + process.setegid('nobody'); + }, /(?:EPERM: .+|Group identifier does not exist: nobody)$/); + + assert.throws(() => { + process.seteuid('nobody'); + }, /(?:EPERM: .+|User identifier does not exist: nobody)$/); + + return; +} + +// If we are running as super user... +const oldgid = process.getegid(); +try { + process.setegid('nobody'); +} catch (err) { + if (err.message !== 'Group identifier does not exist: nobody') { + throw err; + } else { + process.setegid('nogroup'); + } +} +const newgid = process.getegid(); +assert.notStrictEqual(newgid, oldgid); + +const olduid = process.geteuid(); +process.seteuid('nobody'); +const newuid = process.geteuid(); +assert.notStrictEqual(newuid, olduid); diff --git a/test/js/node/test/parallel/test-process-exception-capture-errors.js b/test/js/node/test/parallel/test-process-exception-capture-errors.js new file mode 100644 index 00000000000000..8eb825267cf336 --- /dev/null +++ b/test/js/node/test/parallel/test-process-exception-capture-errors.js @@ -0,0 +1,24 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +assert.throws( + () => process.setUncaughtExceptionCaptureCallback(42), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "fn" argument must be of type function or null. ' + + 'Received type number (42)' + } +); + +process.setUncaughtExceptionCaptureCallback(common.mustNotCall()); + +assert.throws( + () => process.setUncaughtExceptionCaptureCallback(common.mustNotCall()), + { + code: 'ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET', + name: 'Error', + message: /setupUncaughtExceptionCapture.*called while a capture callback/ + } +); diff --git a/test/js/node/test/parallel/test-process-exit-code-validation.js b/test/js/node/test/parallel/test-process-exit-code-validation.js new file mode 100644 index 00000000000000..59934fa31dcdab --- /dev/null +++ b/test/js/node/test/parallel/test-process-exit-code-validation.js @@ -0,0 +1,145 @@ +'use strict'; + +require('../common'); + +const invalids = [ + { + code: '', + expected: 1, + pattern: 'Received type string \\(""\\)$', + }, + { + code: '1 one', + expected: 1, + pattern: 'Received type string \\("1 one"\\)$', + }, + { + code: 'two', + expected: 1, + pattern: 'Received type string \\("two"\\)$', + }, + { + code: {}, + expected: 1, + pattern: 'Received an instance of Object$', + }, + { + code: [], + expected: 1, + pattern: 'Received an instance of Array$', + }, + { + code: true, + expected: 1, + pattern: 'Received type boolean \\(true\\)$', + }, + { + code: false, + expected: 1, + pattern: 'Received type boolean \\(false\\)$', + }, + { + code: 2n, + expected: 1, + pattern: 'Received type bigint \\(2n\\)$', + }, + { + code: 2.1, + expected: 1, + pattern: 'Received 2.1$', + }, + { + code: Infinity, + expected: 1, + pattern: 'Received Infinity$', + }, + { + code: NaN, + expected: 1, + pattern: 'Received NaN$', + }, +]; +const valids = [ + { + code: 1, + expected: 1, + }, + { + code: '2', + expected: 2, + }, + { + code: undefined, + expected: 0, + }, + { + code: null, + expected: 0, + }, + { + code: 0, + expected: 0, + }, + { + code: '0', + expected: 0, + }, +]; +const args = [...invalids, ...valids]; + +if (process.argv[2] === undefined) { + const { spawnSync } = require('node:child_process'); + const { inspect, debuglog } = require('node:util'); + const { throws, strictEqual } = require('node:assert'); + + const debug = debuglog('test'); + const node = process.execPath; + const test = (index, useProcessExitCode) => { + const { status: code } = spawnSync(node, [ + __filename, + index, + useProcessExitCode, + ]); + console.log(`actual: ${code}, ${args[index].expected} ${index} ${!!useProcessExitCode} ${args[index].code}`); + debug(`actual: ${code}, ${inspect(args[index])} ${!!useProcessExitCode}`); + strictEqual( + code, + args[index].expected, + `actual: ${code}, ${inspect(args[index])}` + ); + }; + + // Check process.exitCode + for (const arg of invalids) { + debug(`invaild code: ${inspect(arg.code)}`); + throws(() => (process.exitCode = arg.code), new RegExp(arg.pattern)); + } + for (const arg of valids) { + debug(`vaild code: ${inspect(arg.code)}`); + process.exitCode = arg.code; + } + + throws(() => { + delete process.exitCode; + // }, /Cannot delete property 'exitCode' of #/); + }, /Unable to delete property./); + process.exitCode = 0; + + // Check process.exit([code]) + for (const index of args.keys()) { + test(index); + test(index, true); + } +} else { + const index = parseInt(process.argv[2]); + const useProcessExitCode = process.argv[3] !== 'undefined'; + if (Number.isNaN(index)) { + return process.exit(100); + } + + if (useProcessExitCode) { + process.exitCode = args[index].code; + } else { + process.exit(args[index].code); + } +} diff --git a/test/js/node/test/parallel/test-process-hrtime.js b/test/js/node/test/parallel/test-process-hrtime.js new file mode 100644 index 00000000000000..34ef514aac309b --- /dev/null +++ b/test/js/node/test/parallel/test-process-hrtime.js @@ -0,0 +1,74 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +// The default behavior, return an Array "tuple" of numbers +const tuple = process.hrtime(); + +// Validate the default behavior +validateTuple(tuple); + +// Validate that passing an existing tuple returns another valid tuple +validateTuple(process.hrtime(tuple)); + +// Test that only an Array may be passed to process.hrtime() +assert.throws(() => { + process.hrtime(1); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "time" argument must be an instance of Array. Received type ' + + 'number (1)' +}); +assert.throws(() => { + process.hrtime([]); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "time" is out of range. It must be 2. Received 0' +}); +assert.throws(() => { + process.hrtime([1]); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "time" is out of range. It must be 2. Received 1' +}); +assert.throws(() => { + process.hrtime([1, 2, 3]); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "time" is out of range. It must be 2. Received 3' +}); + +function validateTuple(tuple) { + assert(Array.isArray(tuple)); + assert.strictEqual(tuple.length, 2); + assert(Number.isInteger(tuple[0])); + assert(Number.isInteger(tuple[1])); +} + +const diff = process.hrtime([0, 1e9 - 1]); +assert(diff[1] >= 0); // https://github.com/nodejs/node/issues/4751 diff --git a/test/js/node/test/parallel/test-process-kill-pid.js b/test/js/node/test/parallel/test-process-kill-pid.js new file mode 100644 index 00000000000000..1fa1d6c2ab4211 --- /dev/null +++ b/test/js/node/test/parallel/test-process-kill-pid.js @@ -0,0 +1,116 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (common.isWindows) return; // TODO: BUN +const assert = require('assert'); + +// Test variants of pid +// +// null: TypeError +// undefined: TypeError +// +// 'SIGTERM': TypeError +// +// String(process.pid): TypeError +// +// Nan, Infinity, -Infinity: TypeError +// +// 0, String(0): our group process +// +// process.pid, String(process.pid): ourself + +assert.throws(() => process.kill('SIGTERM'), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "pid" argument must be of type number. Received type string ("SIGTERM")' +}); + +[null, undefined, NaN, Infinity, -Infinity].forEach((val) => { + assert.throws(() => process.kill(val), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "pid" argument must be of type number.' + + common.invalidArgTypeHelper(val) + }); +}); + +// Test that kill throws an error for unknown signal names +assert.throws(() => process.kill(0, 'test'), { + code: 'ERR_UNKNOWN_SIGNAL', + name: 'TypeError', + message: 'Unknown signal: test' +}); + +// Test that kill throws an error for invalid signal numbers +assert.throws(() => process.kill(0, 987), { + code: 'EINVAL', + name: 'SystemError', + message: 'kill() failed: EINVAL: Invalid argument' +}); + +// Test kill argument processing in valid cases. +// +// Monkey patch _kill so that we don't actually send any signals, particularly +// that we don't kill our process group, or try to actually send ANY signals on +// windows, which doesn't support them. +function kill(tryPid, trySig, expectPid, expectSig) { + let getPid; + let getSig; + const origKill = process._kill; + process._kill = function(pid, sig) { + getPid = pid; + getSig = sig; + + // un-monkey patch process._kill + process._kill = origKill; + }; + + process.kill(tryPid, trySig); + + assert.strictEqual(getPid.toString(), expectPid.toString()); + assert.strictEqual(getSig, expectSig); +} + +// Note that SIGHUP and SIGTERM map to 1 and 15 respectively, even on Windows +// (for Windows, libuv maps 1 and 15 to the correct behavior). + +kill(0, 'SIGHUP', 0, 1); +kill(0, undefined, 0, 15); +kill('0', 'SIGHUP', 0, 1); +kill('0', undefined, 0, 15); + +// Confirm that numeric signal arguments are supported + +kill(0, 1, 0, 1); +kill(0, 15, 0, 15); + +// Negative numbers are meaningful on unix +kill(-1, 'SIGHUP', -1, 1); +kill(-1, undefined, -1, 15); +kill('-1', 'SIGHUP', -1, 1); +kill('-1', undefined, -1, 15); + +kill(process.pid, 'SIGHUP', process.pid, 1); +kill(process.pid, undefined, process.pid, 15); +kill(String(process.pid), 'SIGHUP', process.pid, 1); +kill(String(process.pid), undefined, process.pid, 15); diff --git a/test/js/node/test/parallel/test-process-no-deprecation.js b/test/js/node/test/parallel/test-process-no-deprecation.js new file mode 100644 index 00000000000000..bcda99de25069d --- /dev/null +++ b/test/js/node/test/parallel/test-process-no-deprecation.js @@ -0,0 +1,32 @@ +'use strict'; +// Flags: --no-warnings + +// The --no-warnings flag only suppresses writing the warning to stderr, not the +// emission of the corresponding event. This test file can be run without it. + +const common = require('../common'); +process.noDeprecation = true; + +const assert = require('assert'); + +function listener() { + assert.fail('received unexpected warning'); +} + +process.addListener('warning', listener); + +process.emitWarning('Something is deprecated.', 'DeprecationWarning'); + +// The warning would be emitted in the next tick, so continue after that. +process.nextTick(common.mustCall(() => { + // Check that deprecations can be re-enabled. + process.noDeprecation = false; + process.removeListener('warning', listener); + + process.addListener('warning', common.mustCall((warning) => { + assert.strictEqual(warning.name, 'DeprecationWarning'); + assert.strictEqual(warning.message, 'Something else is deprecated.'); + })); + + process.emitWarning('Something else is deprecated.', 'DeprecationWarning'); +})); diff --git a/test/js/node/test/parallel/test-process-really-exit.js b/test/js/node/test/parallel/test-process-really-exit.js new file mode 100644 index 00000000000000..8445d220ca88b7 --- /dev/null +++ b/test/js/node/test/parallel/test-process-really-exit.js @@ -0,0 +1,17 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +// Ensure that the reallyExit hook is executed. +// see: https://github.com/nodejs/node/issues/25650 +if (process.argv[2] === 'subprocess') { + process.reallyExit = function() { + console.info('really exited'); + }; + process.exit(); +} else { + const { spawnSync } = require('child_process'); + const out = spawnSync(process.execPath, [__filename, 'subprocess']); + const observed = out.output[1].toString('utf8').trim(); + assert.strictEqual(observed, 'really exited'); +} diff --git a/test/js/node/test/parallel/test-process-release.js b/test/js/node/test/parallel/test-process-release.js new file mode 100644 index 00000000000000..98a089a8f9ef5a --- /dev/null +++ b/test/js/node/test/parallel/test-process-release.js @@ -0,0 +1,32 @@ +'use strict'; + +require('../common'); + +const assert = require('assert'); +const versionParts = process.versions.node.split('.'); + +assert.strictEqual(process.release.name, 'node'); + +// It's expected that future LTS release lines will have additional +// branches in here +if (versionParts[0] === '4' && versionParts[1] >= 2) { + assert.strictEqual(process.release.lts, 'Argon'); +} else if (versionParts[0] === '6' && versionParts[1] >= 9) { + assert.strictEqual(process.release.lts, 'Boron'); +} else if (versionParts[0] === '8' && versionParts[1] >= 9) { + assert.strictEqual(process.release.lts, 'Carbon'); +} else if (versionParts[0] === '10' && versionParts[1] >= 13) { + assert.strictEqual(process.release.lts, 'Dubnium'); +} else if (versionParts[0] === '12' && versionParts[1] >= 13) { + assert.strictEqual(process.release.lts, 'Erbium'); +} else if (versionParts[0] === '14' && versionParts[1] >= 15) { + assert.strictEqual(process.release.lts, 'Fermium'); +} else if (versionParts[0] === '16' && versionParts[1] >= 13) { + assert.strictEqual(process.release.lts, 'Gallium'); +} else if (versionParts[0] === '18' && versionParts[1] >= 12) { + assert.strictEqual(process.release.lts, 'Hydrogen'); +} else if (versionParts[0] === '20' && versionParts[1] >= 9) { + assert.strictEqual(process.release.lts, 'Iron'); +} else { + assert.strictEqual(process.release.lts, undefined); +} diff --git a/test/js/node/test/parallel/test-process-setgroups.js b/test/js/node/test/parallel/test-process-setgroups.js new file mode 100644 index 00000000000000..c26b5dbaf1cfc0 --- /dev/null +++ b/test/js/node/test/parallel/test-process-setgroups.js @@ -0,0 +1,55 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +if (common.isWindows) { + assert.strictEqual(process.setgroups, undefined); + return; +} + +if (!common.isMainThread) + return; + +assert.throws( + () => { + process.setgroups(); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "groups" argument must be an instance of Array. ' + + 'Received undefined' + } +); + +assert.throws( + () => { + process.setgroups([1, -1]); + }, + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + } +); + +[undefined, null, true, {}, [], () => {}].forEach((val) => { + assert.throws( + () => { + process.setgroups([val]); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "groups[0]" argument must be ' + + 'of type number or string.' + + common.invalidArgTypeHelper(val) + } + ); +}); + +assert.throws(() => { + process.setgroups([1, 'fhqwhgadshgnsdhjsdbkhsdabkfabkveyb']); +}, { + code: 'ERR_UNKNOWN_CREDENTIAL', + message: 'Group identifier does not exist: fhqwhgadshgnsdhjsdbkhsdabkfabkveyb' +}); diff --git a/test/js/node/test/parallel/test-process-title-cli.js b/test/js/node/test/parallel/test-process-title-cli.js new file mode 100644 index 00000000000000..98b3da003f77c6 --- /dev/null +++ b/test/js/node/test/parallel/test-process-title-cli.js @@ -0,0 +1,17 @@ +// Flags: --title=foo +'use strict'; + +const common = require('../common'); +if (common.isWindows) return; // TODO: BUN + +if (common.isSunOS) + common.skip(`Unsupported platform [${process.platform}]`); + +if (common.isIBMi) + common.skip('Unsupported platform IBMi'); + +const assert = require('assert'); + +// Verifies that the --title=foo command line flag set the process +// title on startup. +assert.strictEqual(process.title, 'foo'); diff --git a/test/js/node/test/parallel/test-process-uid-gid.js b/test/js/node/test/parallel/test-process-uid-gid.js new file mode 100644 index 00000000000000..0e8e0e89a0b89a --- /dev/null +++ b/test/js/node/test/parallel/test-process-uid-gid.js @@ -0,0 +1,100 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); + +if (common.isWindows) { + // uid/gid functions are POSIX only. + assert.strictEqual(process.getuid, undefined); + assert.strictEqual(process.getgid, undefined); + assert.strictEqual(process.setuid, undefined); + assert.strictEqual(process.setgid, undefined); + return; +} + +if (!common.isMainThread) + return; + +assert.throws(() => { + process.setuid({}); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "id" argument must be of type ' + + 'number or string. Received an instance of Object' +}); + +assert.throws(() => { + process.setuid('fhqwhgadshgnsdhjsdbkhsdabkfabkveyb'); +}, { + code: 'ERR_UNKNOWN_CREDENTIAL', + message: 'User identifier does not exist: fhqwhgadshgnsdhjsdbkhsdabkfabkveyb' +}); + +// Passing -0 shouldn't crash the process +// Refs: https://github.com/nodejs/node/issues/32750 +// And neither should values exceeding 2 ** 31 - 1. +for (const id of [-0, 2 ** 31, 2 ** 32 - 1]) { + for (const fn of [process.setuid, process.setuid, process.setgid, process.setegid]) { + try { fn(id); } catch { + // Continue regardless of error. + } + } +} + +// If we're not running as super user... +if (process.getuid() !== 0) { + // Should not throw. + process.getgid(); + process.getuid(); + + assert.throws( + () => { process.setgid('nobody'); }, + /(?:EPERM: .+|Group identifier does not exist: nobody)$/ + ); + + assert.throws( + () => { process.setuid('nobody'); }, + /(?:EPERM: .+|User identifier does not exist: nobody)$/ + ); + return; +} + +// If we are running as super user... +const oldgid = process.getgid(); +try { + process.setgid('nobody'); +} catch (err) { + if (err.code !== 'ERR_UNKNOWN_CREDENTIAL') { + throw err; + } + process.setgid('nogroup'); +} + +const newgid = process.getgid(); +assert.notStrictEqual(newgid, oldgid); + +const olduid = process.getuid(); +process.setuid('nobody'); +const newuid = process.getuid(); +assert.notStrictEqual(newuid, olduid); diff --git a/test/js/node/test/parallel/test-process-umask-mask.js b/test/js/node/test/parallel/test-process-umask-mask.js new file mode 100644 index 00000000000000..d599379761fd40 --- /dev/null +++ b/test/js/node/test/parallel/test-process-umask-mask.js @@ -0,0 +1,32 @@ +'use strict'; + +// This tests that the lower bits of mode > 0o777 still works in +// process.umask() + +const common = require('../common'); +const assert = require('assert'); + +if (!common.isMainThread) + common.skip('Setting process.umask is not supported in Workers'); + +let mask; + +if (common.isWindows) { + mask = 0o600; +} else { + mask = 0o664; +} + +const maskToIgnore = 0o10000; + +const old = process.umask(); + +function test(input, output) { + process.umask(input); + assert.strictEqual(process.umask(), output); + + process.umask(old); +} + +test(mask | maskToIgnore, mask); +test((mask | maskToIgnore).toString(8), mask); diff --git a/test/js/node/test/parallel/test-process-umask.js b/test/js/node/test/parallel/test-process-umask.js new file mode 100644 index 00000000000000..e90955f394df4e --- /dev/null +++ b/test/js/node/test/parallel/test-process-umask.js @@ -0,0 +1,65 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +if (!common.isMainThread) { + assert.strictEqual(typeof process.umask(), 'number'); + assert.throws(() => { + process.umask('0664'); + }, { code: 'ERR_WORKER_UNSUPPORTED_OPERATION' }); + + common.skip('Setting process.umask is not supported in Workers'); +} + +// Note in Windows one can only set the "user" bits. +let mask; +if (common.isWindows) { + mask = '0600'; +} else { + mask = '0664'; +} + +const old = process.umask(mask); + +assert.strictEqual(process.umask(old), parseInt(mask, 8)); + +// Confirm reading the umask does not modify it. +// 1. If the test fails, this call will succeed, but the mask will be set to 0 +assert.strictEqual(process.umask(), old); +// 2. If the test fails, process.umask() will return 0 +assert.strictEqual(process.umask(), old); + +assert.throws(() => { + process.umask({}); +}, { + code: 'ERR_INVALID_ARG_TYPE', +}); + +['123x', 'abc', '999'].forEach((value) => { + assert.throws(() => { + process.umask(value); + }, { + code: 'ERR_INVALID_ARG_VALUE', + }); +}); diff --git a/test/js/node/test/parallel/test-process-warning.js b/test/js/node/test/parallel/test-process-warning.js new file mode 100644 index 00000000000000..c1fbbf775fb45e --- /dev/null +++ b/test/js/node/test/parallel/test-process-warning.js @@ -0,0 +1,68 @@ +'use strict'; + +const common = require('../common'); +const { + hijackStderr, + restoreStderr +} = require('../common/hijackstdio'); +const assert = require('assert'); + +function test1() { + // Output is skipped if the argument to the 'warning' event is + // not an Error object. + hijackStderr(common.mustNotCall('stderr.write must not be called')); + process.emit('warning', 'test'); + setImmediate(test2); +} + +function test2() { + // Output is skipped if it's a deprecation warning and + // process.noDeprecation = true + process.noDeprecation = true; + process.emitWarning('test', 'DeprecationWarning'); + process.noDeprecation = false; + setImmediate(test3); +} + +function test3() { + restoreStderr(); + // Type defaults to warning when the second argument is an object + process.emitWarning('test', {}); + process.once('warning', common.mustCall((warning) => { + assert.strictEqual(warning.name, 'Warning'); + })); + setImmediate(test4); +} + +function test4() { + // process.emitWarning will throw when process.throwDeprecation is true + // and type is `DeprecationWarning`. + process.throwDeprecation = true; + process.once('uncaughtException', (err) => { + assert.match(err.toString(), /^DeprecationWarning: test$/); + }); + try { + process.emitWarning('test', 'DeprecationWarning'); + } catch { + assert.fail('Unreachable'); + } + process.throwDeprecation = false; + setImmediate(test5); +} + +function test5() { + // Setting toString to a non-function should not cause an error + const err = new Error('test'); + err.toString = 1; + process.emitWarning(err); + setImmediate(test6); +} + +function test6() { + process.emitWarning('test', { detail: 'foo' }); + process.on('warning', (warning) => { + assert.strictEqual(warning.detail, 'foo'); + }); +} + +test1(); diff --git a/test/js/node/test/parallel/test-queue-microtask-uncaught-asynchooks.js b/test/js/node/test/parallel/test-queue-microtask-uncaught-asynchooks.js deleted file mode 100644 index 35b3d9fa309af9..00000000000000 --- a/test/js/node/test/parallel/test-queue-microtask-uncaught-asynchooks.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; -const common = require('../common'); -const assert = require('assert'); -const async_hooks = require('async_hooks'); - -// Regression test for https://github.com/nodejs/node/issues/30080: -// An uncaught exception inside a queueMicrotask callback should not lead -// to multiple after() calls for it. - -let µtaskId; -const events = []; - -async_hooks.createHook({ - init(id, type, triggerId, resource) { - if (type === 'Microtask') { - µtaskId = id; - events.push('init'); - } - }, - before(id) { - if (id === µtaskId) events.push('before'); - }, - after(id) { - if (id === µtaskId) events.push('after'); - }, - destroy(id) { - if (id === µtaskId) events.push('destroy'); - } -}).enable(); - -queueMicrotask(() => { throw new Error(); }); - -process.on('uncaughtException', common.mustCall()); -process.on('exit', () => { - assert.deepStrictEqual(events, ['init', 'after', 'before', 'destroy']); -}); diff --git a/test/js/web/fetch/fetch.stream.test.ts b/test/js/web/fetch/fetch.stream.test.ts index 21a72ede533627..b3414f453bf96d 100644 --- a/test/js/web/fetch/fetch.stream.test.ts +++ b/test/js/web/fetch/fetch.stream.test.ts @@ -1209,12 +1209,15 @@ describe("fetch() with streaming", () => { expect(buffer.toString("utf8")).toBe("unreachable"); } catch (err) { if (compression === "br") { - expect((err as Error).name).toBe("BrotliDecompressionError"); + expect((err as Error).name).toBe("Error"); + expect((err as Error).code).toBe("BrotliDecompressionError"); } else if (compression === "deflate-libdeflate") { // Since the compressed data is different, the error ends up different. - expect((err as Error).name).toBe("ShortRead"); + expect((err as Error).name).toBe("Error"); + expect((err as Error).code).toBe("ShortRead"); } else { - expect((err as Error).name).toBe("ZlibError"); + expect((err as Error).name).toBe("Error"); + expect((err as Error).code).toBe("ZlibError"); } } } @@ -1306,7 +1309,8 @@ describe("fetch() with streaming", () => { gcTick(false); expect(buffer.toString("utf8")).toBe("unreachable"); } catch (err) { - expect((err as Error).name).toBe("ConnectionClosed"); + expect((err as Error).name).toBe("Error"); + expect((err as Error).code).toBe("ConnectionClosed"); } } });