diff options
31 files changed, 617 insertions, 407 deletions
diff --git a/bench/snippets/module-exports-putter.cjs b/bench/snippets/module-exports-putter.cjs new file mode 100644 index 000000000..9bef17b90 --- /dev/null +++ b/bench/snippets/module-exports-putter.cjs @@ -0,0 +1,65 @@ +// This is a stress test of some internals in How Bun does the module.exports assignment. +// If it crashes or throws then this fails +import("./runner.mjs").then(({ bench, run }) => { + bench("Object.defineProperty(module, 'exports', { get() { return 42; } })", () => { + Object.defineProperty(module, "exports", { + get() { + return 42; + }, + set() { + throw new Error("bad"); + }, + configurable: true, + }); + if (module.exports !== 42) throw new Error("bad"); + if (!Object.getOwnPropertyDescriptor(module, "exports").get) throw new Error("bad"); + }); + + bench("Object.defineProperty(module.exports = {})", () => { + Object.defineProperty(module, "exports", { + value: { abc: 123 }, + }); + + if (!module.exports.abc) throw new Error("bad"); + if (Object.getOwnPropertyDescriptor(module, "exports").value !== module.exports) throw new Error("bad"); + }); + + bench("module.exports = {}", () => { + module.exports = { abc: 123 }; + + if (!module.exports.abc) throw new Error("bad"); + if (Object.getOwnPropertyDescriptor(module, "exports").value !== module.exports) throw new Error("bad"); + }); + + run().then(() => { + module.exports = { + a: 1, + }; + + console.log( + module?.exports, + require.cache[module.id].exports, + module?.exports === require.cache[module.id], + __dirname, + Object.keys(require(module.id)), + require(module.id), + ); + + module.exports = function lol() { + return 42; + }; + + console.log(module.exports, module.exports()); + + queueMicrotask(() => { + console.log( + module?.exports, + require.cache[module.id].exports, + module?.exports === require.cache[module.id]?.exports, + __dirname, + Object.keys(require(module.id)), + require(module.id), + ); + }); + }); +}); diff --git a/bench/websocket-server/chat-server.bun.js b/bench/websocket-server/chat-server.bun.js index b4c71a6dc..9d45b4622 100644 --- a/bench/websocket-server/chat-server.bun.js +++ b/bench/websocket-server/chat-server.bun.js @@ -32,6 +32,7 @@ const server = Bun.serve({ }, perMessageDeflate: false, + publishToSelf: true }, fetch(req, server) { diff --git a/docs/cli/run.md b/docs/cli/run.md index 7a5f8e9d2..1398e10ca 100644 --- a/docs/cli/run.md +++ b/docs/cli/run.md @@ -102,7 +102,7 @@ To debug environment variables, run `bun run env` to view a list of resolved env Bun is designed to start fast and run fast. -Under the hood Bun uses the [JavaScriptCore engine](https://developer.apple.com/documentation/javascriptcore), which is developed by Apple for Safari. In most cases, the startup and running performance is faster than V8, the engine used by Node.js and Chromium-based browsers. It's transpiler and runtime are written in Zig, a modern, high-performance language. On Linux, this translates into startup times [4x faster](https://twitter.com/jarredsumner/status/1499225725492076544) than Node.js. +Under the hood Bun uses the [JavaScriptCore engine](https://developer.apple.com/documentation/javascriptcore), which is developed by Apple for Safari. In most cases, the startup and running performance is faster than V8, the engine used by Node.js and Chromium-based browsers. Its transpiler and runtime are written in Zig, a modern, high-performance language. On Linux, this translates into startup times [4x faster](https://twitter.com/jarredsumner/status/1499225725492076544) than Node.js. {% image src="/images/bun-run-speed.jpeg" caption="Bun vs Node.js vs Deno running Hello World" /%} diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts index bf89b808d..05fd0eac0 100644 --- a/packages/bun-types/bun-test.d.ts +++ b/packages/bun-types/bun-test.d.ts @@ -27,43 +27,11 @@ declare module "bun:test" { export const mock: { <T extends AnyFunction>(Function: T): Mock<T>; - - mockClear(): typeof mock; - mockReset(): typeof mock; - mockRestore(): void; - mockReturnValue<T extends JestMock.FunctionLike = JestMock.UnknownFunction>( - value: ReturnType<T>, - ): JestMock.MockInstance<T>; - mockReturnValueOnce< - T extends JestMock.FunctionLike = JestMock.UnknownFunction, - >( - value: ReturnType<T>, - ): JestMock.MockInstance<T>; - mockResolvedValue< - T extends JestMock.FunctionLike = JestMock.UnknownFunction, - >( - value: JestMock.ResolveType<T>, - ): JestMock.MockInstance<T>; - mockResolvedValueOnce< - T extends JestMock.FunctionLike = JestMock.UnknownFunction, - >( - value: JestMock.ResolveType<T>, - ): JestMock.MockInstance<T>; - mockRejectedValue< - T extends JestMock.FunctionLike = JestMock.UnknownFunction, - >( - value: JestMock.RejectType<T>, - ): JestMock.MockInstance<T>; - mockRejectedValueOnce< - T extends JestMock.FunctionLike = JestMock.UnknownFunction, - >( - value: JestMock.RejectType<T>, - ): JestMock.MockInstance<T>; }; interface Jest { restoreAllMocks(): void; - fn<T extends AnyFunction>(func: T): Mock<T>; + fn<T extends AnyFunction>(func?: T): Mock<T>; } export const jest: Jest; export namespace jest { @@ -473,6 +441,24 @@ declare module "bun:test" { */ toBe(expected: T): void; /** + * Asserts that a number is odd. + * + * @link https://jest-extended.jestcommunity.dev/docs/matchers/number/#tobeodd + * @example + * expect(1).toBeOdd(); + * expect(2).not.toBeOdd(); + */ + toBeOdd(): void; + /** + * Asserts that a number is even. + * + * @link https://jest-extended.jestcommunity.dev/docs/matchers/number/#tobeeven + * @example + * expect(2).toBeEven(); + * expect(1).not.toBeEven(); + */ + toBeEven(): void; + /** * Asserts that value is close to the expected by floating point precision. * * For example, the following fails because arithmetic on decimal (base 10) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 6987a9226..306971d4c 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -51,6 +51,10 @@ declare module "bun" { * */ export const env: Env; + /** + * The raw arguments passed to the process, including flags passed to Bun. If you want to easily read flags passed to your script, consider using `process.argv` instead. + */ + export const argv: string[]; export const origin: string; /** diff --git a/packages/bun-types/scripts/bundle.ts b/packages/bun-types/scripts/bundle.ts index aa91a3dfa..ce1c5ff4d 100644 --- a/packages/bun-types/scripts/bundle.ts +++ b/packages/bun-types/scripts/bundle.ts @@ -75,6 +75,8 @@ const tsConfig = { skipLibCheck: true, jsx: "react-jsx", allowImportingTsExtensions: true, + emitDeclarationOnly: true, + composite: true, allowSyntheticDefaultImports: true, forceConsistentCasingInFileNames: true, allowJs: true, diff --git a/packages/bun-types/tests/mocks.test-d.ts b/packages/bun-types/tests/mocks.test-d.ts index f4fe47d4f..9bb8a9070 100644 --- a/packages/bun-types/tests/mocks.test-d.ts +++ b/packages/bun-types/tests/mocks.test-d.ts @@ -14,4 +14,18 @@ declare var arg2: arg2; arg2.mock.calls[0]; mock; -type _arg3 = jest.Mock<() => number>; +// @ts-expect-error +jest.fn<() => Promise<string>>().mockReturnValue("asdf"); +// @ts-expect-error +jest.fn<() => string>().mockReturnValue(24); +jest.fn<() => string>().mockReturnValue("24"); + +jest.fn<() => Promise<string>>().mockResolvedValue("asdf"); +// @ts-expect-error +jest.fn<() => string>().mockResolvedValue(24); +// @ts-expect-error +jest.fn<() => string>().mockResolvedValue("24"); + +jest.fn().mockClear(); +jest.fn().mockReset(); +jest.fn().mockRejectedValueOnce(new Error()); diff --git a/src/build-id b/src/build-id index 45a4fb75d..f599e28b8 100644 --- a/src/build-id +++ b/src/build-id @@ -1 +1 @@ -8 +10 diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index d3ef6919a..5580e8840 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -440,7 +440,7 @@ pub fn getAssetPrefix( _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - return ZigString.init(VirtualMachine.get().bundler.options.routes.asset_prefix_path).toValue(ctx.ptr()).asRef(); + return ZigString.init(VirtualMachine.get().bundler.options.routes.asset_prefix_path).toValueGC(ctx.ptr()).asRef(); } pub fn getArgv( @@ -450,19 +450,8 @@ pub fn getArgv( _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - if (comptime Environment.isWindows) { - @compileError("argv not supported on windows"); - } - - var argv_list = std.heap.stackFallback(128, getAllocator(ctx)); - var allocator = argv_list.get(); - var argv = allocator.alloc(ZigString, std.os.argv.len) catch unreachable; - defer if (argv.len > 128) allocator.free(argv); - for (std.os.argv, 0..) |arg, i| { - argv[i] = ZigString.init(std.mem.span(arg)); - } - - return JSValue.createStringArray(ctx.ptr(), argv.ptr, argv.len, true).asObjectRef(); + // TODO: cache this + return JSC.Node.Process.getArgv(ctx).asObjectRef(); } pub fn getRoutesDir( diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 780bfcb14..48bfe4218 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -254,19 +254,17 @@ pub const SocketConfig = struct { var ssl: ?JSC.API.ServerConfig.SSLConfig = null; var default_data = JSValue.zero; - if (opts.getTruthy(globalObject, "tls")) |tls| outer: { + if (opts.getTruthy(globalObject, "tls")) |tls| { if (tls.isBoolean()) { if (tls.toBoolean()) { ssl = JSC.API.ServerConfig.SSLConfig.zero; } - - break :outer; - } - - if (JSC.API.ServerConfig.SSLConfig.inJS(globalObject, tls, exception)) |ssl_config| { - ssl = ssl_config; - } else if (exception.* != null) { - return null; + } else { + if (JSC.API.ServerConfig.SSLConfig.inJS(globalObject, tls, exception)) |ssl_config| { + ssl = ssl_config; + } else if (exception.* != null) { + return null; + } } } diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 8fd9acde7..37bc601a5 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -270,6 +270,11 @@ pub const ServerConfig = struct { pub fn inJS(global: *JSC.JSGlobalObject, obj: JSC.JSValue, exception: JSC.C.ExceptionRef) ?SSLConfig { var result = zero; + if (!obj.isObject()) { + JSC.throwInvalidArguments("tls option expects an object", .{}, global, exception); + return null; + } + var any = false; // Required @@ -688,7 +693,7 @@ pub const ServerConfig = struct { } if (arguments.next()) |arg| { - if (arg.isUndefinedOrNull() or !arg.isObject()) { + if (!arg.isObject()) { JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global, exception); return args; } @@ -812,6 +817,9 @@ pub const ServerConfig = struct { } return args; } + } else { + JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global, exception); + return args; } if (args.base_uri.len > 0) { diff --git a/src/bun.js/bindings/BunPlugin.cpp b/src/bun.js/bindings/BunPlugin.cpp index ad4039336..066cf82fd 100644 --- a/src/bun.js/bindings/BunPlugin.cpp +++ b/src/bun.js/bindings/BunPlugin.cpp @@ -49,7 +49,7 @@ static EncodedJSValue jsFunctionAppendOnLoadPluginBody(JSC::JSGlobalObject* glob auto clientData = WebCore::clientData(vm); auto& builtinNames = clientData->builtinNames(); JSC::RegExpObject* filter = nullptr; - if (JSValue filterValue = filterObject->getIfPropertyExists(globalObject, builtinNames.filterPublicName())) { + if (JSValue filterValue = filterObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "filter"_s))) { if (filterValue.isCell() && filterValue.asCell()->inherits<JSC::RegExpObject>()) filter = jsCast<JSC::RegExpObject*>(filterValue); } @@ -101,7 +101,7 @@ static EncodedJSValue jsFunctionAppendOnResolvePluginBody(JSC::JSGlobalObject* g auto clientData = WebCore::clientData(vm); auto& builtinNames = clientData->builtinNames(); JSC::RegExpObject* filter = nullptr; - if (JSValue filterValue = filterObject->getIfPropertyExists(globalObject, builtinNames.filterPublicName())) { + if (JSValue filterValue = filterObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "filter"_s))) { if (filterValue.isCell() && filterValue.asCell()->inherits<JSC::RegExpObject>()) filter = jsCast<JSC::RegExpObject*>(filterValue); } diff --git a/src/bun.js/bindings/CallSitePrototype.cpp b/src/bun.js/bindings/CallSitePrototype.cpp index faaf571e2..f3e365cb0 100644 --- a/src/bun.js/bindings/CallSitePrototype.cpp +++ b/src/bun.js/bindings/CallSitePrototype.cpp @@ -89,7 +89,7 @@ const JSC::ClassInfo CallSitePrototype::s_info = { "CallSite"_s, &Base::s_info, void CallSitePrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) { Base::finishCreation(vm); - ASSERT(inherits(vm, info())); + ASSERT(inherits(info())); reifyStaticProperties(vm, CallSite::info(), CallSitePrototypeTableValues, *this); JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index 31c24bb66..1cee1091b 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -68,9 +68,6 @@ namespace Bun { using namespace JSC; -static Structure* internalCreateCommonJSModuleStructure( - Zig::GlobalObject* globalObject); - class JSCommonJSModule final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; @@ -78,15 +75,13 @@ public: mutable JSC::WriteBarrier<JSC::Unknown> m_exportsObject; mutable JSC::WriteBarrier<JSC::JSString> m_id; - mutable JSC::WriteBarrier<JSC::EvalExecutable> m_executable; - void finishCreation(JSC::VM& vm, JSC::JSValue exportsObject, JSC::JSString* id, JSC::JSString* filename, JSC::JSValue requireFunction, JSC::EvalExecutable* executable) + void finishCreation(JSC::VM& vm, JSC::JSValue exportsObject, JSC::JSString* id, JSC::JSString* filename, JSC::JSString* dirname, JSC::JSValue requireFunction) { Base::finishCreation(vm); ASSERT(inherits(vm, info())); m_exportsObject.set(vm, this, exportsObject); m_id.set(vm, this, id); - m_executable.set(vm, this, executable); this->putDirectOffset( vm, @@ -101,15 +96,83 @@ public: this->putDirectOffset( vm, 2, - id); + filename); + this->putDirectOffset( vm, 3, - filename); + jsBoolean(false)); + this->putDirectOffset( vm, 4, - requireFunction); + dirname); + + this->putDirectOffset( + vm, + 5, + jsUndefined()); + } + + static JSC::Structure* createStructure( + JSC::JSGlobalObject* globalObject) + { + auto& vm = globalObject->vm(); + JSC::Structure* structure = JSC::Structure::create( + vm, + globalObject, + globalObject->objectPrototype(), + JSC::TypeInfo(JSC::ObjectType, JSCommonJSModule::StructureFlags), + JSCommonJSModule::info(), + JSC::NonArray, + 6); + + JSC::PropertyOffset offset; + auto clientData = WebCore::clientData(vm); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "exports"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "id"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "filename"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "loaded"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "path"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "require"_s), + 0, + offset); + + return structure; } static JSCommonJSModule* create( @@ -118,11 +181,11 @@ public: JSC::JSValue exportsObject, JSC::JSString* id, JSC::JSString* filename, - JSC::JSValue requireFunction, - JSC::EvalExecutable* executable) + JSC::JSString* dirname, + JSC::JSValue requireFunction) { JSCommonJSModule* cell = new (NotNull, JSC::allocateCell<JSCommonJSModule>(vm)) JSCommonJSModule(vm, structure); - cell->finishCreation(vm, exportsObject, id, filename, requireFunction, executable); + cell->finishCreation(vm, exportsObject, id, filename, dirname, requireFunction); return cell; } @@ -145,34 +208,34 @@ public: JSC::JSValue value, JSC::PutPropertySlot& slot) { - JSCommonJSModule* thisObject = jsCast<JSCommonJSModule*>(cell); - ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + auto& vm = globalObject->vm(); + auto* clientData = WebCore::clientData(vm); auto throwScope = DECLARE_THROW_SCOPE(vm); - auto* clientData = WebCore::clientData(vm); - bool result = Base::put(thisObject, globalObject, propertyName, value, slot); - if (result) { - // Whenever you call module.exports = ... in a module, we need to: - // - // - Update the internal exports object - // - Update the require map - // - if (propertyName == clientData->builtinNames().exportsPublicName()) { - thisObject->m_exportsObject.set(vm, thisObject, value); - Zig::GlobalObject* zigGlobalObject = jsCast<Zig::GlobalObject*>(globalObject); - zigGlobalObject->requireMap()->set(globalObject, thisObject->id(), value); - RETURN_IF_EXCEPTION(throwScope, false); + if (propertyName == clientData->builtinNames().exportsPublicName()) { + JSCommonJSModule* thisObject = jsCast<JSCommonJSModule*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + + // It will crash if we attempt to assign Object.defineProperty() result to a JSMap*. + if (UNLIKELY(slot.thisValue() != thisObject)) + RELEASE_AND_RETURN(throwScope, JSObject::definePropertyOnReceiver(globalObject, propertyName, value, slot)); + + JSValue prevValue = thisObject->m_exportsObject.get(); + + // TODO: refactor this to not go through ESM path and we don't need to do this check. + // IF we do this on every call, it causes GC to happen in a place that it may not be able to. + // This breaks loading Bluebird in some cases, for example. + // We need to update the require map "live" because otherwise the code in Discord.js will break + // The bug is something to do with exception handling which causes GC to happen in the error path and then boom. + if (prevValue != value && (!prevValue.isCell() || !value.isCell() || prevValue.asCell()->type() != value.asCell()->type())) { + jsCast<Zig::GlobalObject*>(globalObject)->requireMap()->set(globalObject, thisObject->id(), value); } + + thisObject->m_exportsObject.set(vm, thisObject, value); } - RELEASE_AND_RETURN(throwScope, result); - } - - static JSC::Structure* createStructure( - JSC::JSGlobalObject* globalObject) - { - return internalCreateCommonJSModuleStructure(reinterpret_cast<Zig::GlobalObject*>(globalObject)); + RELEASE_AND_RETURN(throwScope, Base::put(cell, globalObject, propertyName, value, slot)); } DECLARE_INFO; @@ -200,53 +263,6 @@ Structure* createCommonJSModuleStructure( return JSCommonJSModule::createStructure(globalObject); } -static Structure* internalCreateCommonJSModuleStructure( - Zig::GlobalObject* globalObject) -{ - auto& vm = globalObject->vm(); - JSC::Structure* structure = JSC::Structure::create( - vm, - globalObject, - globalObject->objectPrototype(), - JSC::TypeInfo(JSC::ObjectType, JSCommonJSModule::StructureFlags), - JSCommonJSModule::info(), - JSC::NonArray, - 4); - - JSC::PropertyOffset offset; - auto clientData = WebCore::clientData(vm); - - structure = structure->addPropertyTransition( - vm, - structure, - JSC::Identifier::fromString(vm, "exports"_s), - 0, - offset); - - structure = structure->addPropertyTransition( - vm, - structure, - JSC::Identifier::fromString(vm, "id"_s), - 0, - offset); - - structure = structure->addPropertyTransition( - vm, - structure, - JSC::Identifier::fromString(vm, "filename"_s), - 0, - offset); - - structure = structure->addPropertyTransition( - vm, - structure, - JSC::Identifier::fromString(vm, "require"_s), - JSC::PropertyAttribute::Builtin | JSC::PropertyAttribute::Function | 0, - offset); - - return structure; -} - template<typename Visitor> void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) { @@ -255,34 +271,11 @@ void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) Base::visitChildren(thisObject, visitor); visitor.append(thisObject->m_exportsObject); visitor.append(thisObject->m_id); - visitor.append(thisObject->m_executable); } DEFINE_VISIT_CHILDREN(JSCommonJSModule); const JSC::ClassInfo JSCommonJSModule::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCommonJSModule) }; -JSCommonJSModule* createCommonJSModuleObject( - Zig::GlobalObject* globalObject, - const ResolvedSource& source, - const WTF::String& sourceURL, - JSC::JSValue exportsObjectValue, - JSC::JSValue requireFunctionValue, - JSC::EvalExecutable* executable, - JSC::JSString* filename) -{ - auto& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto* jsSourceURL = JSC::jsString(vm, sourceURL); - - JSCommonJSModule* moduleObject = JSCommonJSModule::create( - vm, - globalObject->CommonJSModuleObjectStructure(), - exportsObjectValue, - jsSourceURL, filename, requireFunctionValue, executable); - - return moduleObject; -} - static bool canPerformFastEnumeration(Structure* s) { if (s->typeInfo().overridesGetOwnPropertySlot()) @@ -300,172 +293,155 @@ static bool canPerformFastEnumeration(Structure* s) return true; } -JSC::SourceCode createCommonJSModule( +JSValue evaluateCommonJSModule( Zig::GlobalObject* globalObject, + Ref<Zig::SourceProvider> sourceProvider, + const WTF::String& sourceURL, ResolvedSource source) { - auto sourceURL = Zig::toStringCopy(source.source_url); - auto sourceProvider = Zig::SourceProvider::create(globalObject, source, JSC::SourceProviderSourceType::Program); + auto& vm = globalObject->vm(); - return JSC::SourceCode( - JSC::SyntheticSourceProvider::create( - [source, sourceProvider = WTFMove(sourceProvider), sourceURL](JSC::JSGlobalObject* lexicalGlobalObject, - JSC::Identifier moduleKey, - Vector<JSC::Identifier, 4>& exportNames, - JSC::MarkedArgumentBuffer& exportValues) -> void { - auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject); - auto& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* requireMapKey = jsString(vm, sourceURL); + + JSC::JSObject* exportsObject = source.commonJSExportsLen < 64 + ? JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), source.commonJSExportsLen) + : JSC::constructEmptyObject(globalObject, globalObject->objectPrototype()); + auto index = sourceURL.reverseFind('/', sourceURL.length()); + JSString* dirname = jsEmptyString(vm); + JSString* filename = requireMapKey; + if (index != WTF::notFound) { + dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index); + } - auto throwScope = DECLARE_THROW_SCOPE(vm); - auto* requireMapKey = jsString(vm, sourceURL); - - JSC::JSObject* exportsObject = source.commonJSExportsLen < 64 - ? JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), source.commonJSExportsLen) - : JSC::constructEmptyObject(globalObject, globalObject->objectPrototype()); - auto index = sourceURL.reverseFind('/', sourceURL.length()); - JSString* dirname = jsEmptyString(vm); - JSString* filename = requireMapKey; - if (index != WTF::notFound) { - dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index); - } + globalObject->requireMap()->set(globalObject, requireMapKey, exportsObject); + auto* requireFunction = Zig::ImportMetaObject::createRequireFunction(vm, globalObject, sourceURL); - globalObject->requireMap()->set(globalObject, requireMapKey, exportsObject); - JSC::SourceCode inputSource( - WTFMove(sourceProvider)); + JSC::SourceCode inputSource( + WTFMove(sourceProvider)); - JSC::Structure* scopeExtensionObjectStructure = globalObject->commonJSFunctionArgumentsStructure(); - JSC::JSObject* scopeExtensionObject = JSC::constructEmptyObject( - vm, - scopeExtensionObjectStructure); + auto* moduleObject = JSCommonJSModule::create( + vm, + globalObject->CommonJSModuleObjectStructure(), + exportsObject, + requireMapKey, filename, dirname, requireFunction); - auto* requireFunction = Zig::ImportMetaObject::createRequireFunction(vm, globalObject, sourceURL); - auto* executable = JSC::DirectEvalExecutable::create( - globalObject, inputSource, DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None, - false, false, EvalContextType::None, nullptr, nullptr, ECMAMode::sloppy()); + if (UNLIKELY(throwScope.exception())) { + globalObject->requireMap()->remove(globalObject, requireMapKey); + RELEASE_AND_RETURN(throwScope, JSValue()); + } - if (UNLIKELY(!executable && !throwScope.exception())) { - // I'm not sure if this case happens, but it's better to be safe than sorry. - throwSyntaxError(globalObject, throwScope, "Failed to compile CommonJS module."_s); - } + JSC::Structure* thisObjectStructure = globalObject->commonJSFunctionArgumentsStructure(); + JSC::JSObject* thisObject = JSC::constructEmptyObject( + vm, + thisObjectStructure); + thisObject->putDirectOffset( + vm, + 0, + moduleObject); - if (UNLIKELY(throwScope.exception())) { - globalObject->requireMap()->remove(globalObject, requireMapKey); - throwScope.release(); - return; - } + thisObject->putDirectOffset( + vm, + 1, + exportsObject); - auto* moduleObject = createCommonJSModuleObject(globalObject, - source, - sourceURL, - exportsObject, - requireFunction, executable, filename); - - scopeExtensionObject->putDirectOffset( - vm, - 0, - moduleObject); - - scopeExtensionObject->putDirectOffset( - vm, - 1, - exportsObject); - - scopeExtensionObject->putDirectOffset( - vm, - 2, - dirname); - - scopeExtensionObject->putDirectOffset( - vm, - 3, - filename); - - scopeExtensionObject->putDirectOffset( - vm, - 4, - requireFunction); - - if (UNLIKELY(throwScope.exception())) { - globalObject->requireMap()->remove(globalObject, requireMapKey); - throwScope.release(); - return; - } + thisObject->putDirectOffset( + vm, + 2, + dirname); - auto catchScope = DECLARE_CATCH_SCOPE(vm); - - // Where the magic happens. - // - // A `with` scope is created containing { module, exports, require }. - // We eval() the CommonJS module code - // with that scope. - // - // Doing it that way saves us a roundtrip through C++ <> JS. - // - // Sidenote: another implementation could use - // FunctionExecutable. It looks like there are lots of arguments - // to pass to that and it isn't used directly much, so that - // seems harder to do correctly. - { - // We must use a global scope extension or else the JSWithScope will be collected unexpectedly. - // https://github.com/oven-sh/bun/issues/3161 - globalObject->clearGlobalScopeExtension(); - - JSWithScope* withScope = JSWithScope::create(vm, globalObject, globalObject->globalScope(), scopeExtensionObject); - globalObject->setGlobalScopeExtension(withScope); - vm.interpreter.executeEval(executable, globalObject, globalObject->globalScope()); - globalObject->clearGlobalScopeExtension(); - - if (UNLIKELY(catchScope.exception())) { - auto returnedException = catchScope.exception(); - catchScope.clearException(); - JSC::throwException(globalObject, throwScope, returnedException); - } - } + thisObject->putDirectOffset( + vm, + 3, + filename); - if (throwScope.exception()) { - globalObject->requireMap()->remove(globalObject, requireMapKey); - throwScope.release(); - return; - } + thisObject->putDirectOffset( + vm, + 4, + requireFunction); - JSValue result = moduleObject->exportsObject(); - - // The developer can do something like: - // - // Object.defineProperty(module, 'exports', {get: getter}) - // - // In which case, the exports object is now a GetterSetter object. - // - // We can't return a GetterSetter object to ESM code, so we need to call it. - if (!result.isEmpty() && (result.isGetterSetter() || result.isCustomGetterSetter())) { - auto* clientData = WebCore::clientData(vm); - - // TODO: is there a faster way to call these getters? We shouldn't need to do a full property lookup. - // - // we use getIfPropertyExists just incase a pathological devleoper did: - // - // - Object.defineProperty(module, 'exports', {get: getter}) - // - delete module.exports - // - if (result.isGetterSetter()) { - JSC::GetterSetter* getter = jsCast<JSC::GetterSetter*>(result); - result = getter->callGetter(globalObject, moduleObject); - } else { - result = moduleObject->getIfPropertyExists(globalObject, clientData->builtinNames().exportsPublicName()); - } + { + WTF::NakedPtr<Exception> exception; + globalObject->m_BunCommonJSModuleValue.set(vm, globalObject, thisObject); + JSC::evaluate(globalObject, inputSource, globalObject->globalThis(), exception); + + if (exception.get()) { + throwScope.throwException(globalObject, exception->value()); + exception.clear(); + RELEASE_AND_RETURN(throwScope, JSValue()); + } + } - if (UNLIKELY(throwScope.exception())) { - // Unlike getters on properties of the exports object - // When the exports object itself is a getter and it throws - // There's not a lot we can do - // so we surface that error - globalObject->requireMap()->remove(globalObject, requireMapKey); - throwScope.release(); - return; - } + if (UNLIKELY(throwScope.exception())) { + globalObject->requireMap()->remove(globalObject, requireMapKey); + RELEASE_AND_RETURN(throwScope, JSValue()); + } + + JSValue result = moduleObject->exportsObject(); + + // The developer can do something like: + // + // Object.defineProperty(module, 'exports', {get: getter}) + // + // In which case, the exports object is now a GetterSetter object. + // + // We can't return a GetterSetter object to ESM code, so we need to call it. + if (!result.isEmpty() && (result.isGetterSetter() || result.isCustomGetterSetter())) { + auto* clientData = WebCore::clientData(vm); + + // TODO: is there a faster way to call these getters? We shouldn't need to do a full property lookup. + // + // we use getIfPropertyExists just incase a pathological devleoper did: + // + // - Object.defineProperty(module, 'exports', {get: getter}) + // - delete module.exports + // + if (result.isGetterSetter()) { + JSC::GetterSetter* getter = jsCast<JSC::GetterSetter*>(result); + result = getter->callGetter(globalObject, moduleObject); + } else { + result = moduleObject->getIfPropertyExists(globalObject, clientData->builtinNames().exportsPublicName()); + } + + if (UNLIKELY(throwScope.exception())) { + // Unlike getters on properties of the exports object + // When the exports object itself is a getter and it throws + // There's not a lot we can do + // so we surface that error + globalObject->requireMap()->remove(globalObject, requireMapKey); + RELEASE_AND_RETURN(throwScope, JSValue()); + } + } + + globalObject->requireMap()->set(globalObject, requireMapKey, result); + + return result; +} + +JSC::SourceCode createCommonJSModule( + Zig::GlobalObject* globalObject, + ResolvedSource source) +{ + auto sourceURL = Zig::toStringCopy(source.source_url); + auto sourceProvider = Zig::SourceProvider::create(globalObject, source, JSC::SourceProviderSourceType::Program); + + return JSC::SourceCode( + JSC::SyntheticSourceProvider::create( + [source, sourceProvider = WTFMove(sourceProvider), sourceURL](JSC::JSGlobalObject* globalObject, + JSC::Identifier moduleKey, + Vector<JSC::Identifier, 4>& exportNames, + JSC::MarkedArgumentBuffer& exportValues) -> void { + JSValue result = evaluateCommonJSModule( + jsCast<Zig::GlobalObject*>(globalObject), + WTFMove(sourceProvider), + sourceURL, + source); + + if (!result) { + return; } - globalObject->requireMap()->set(globalObject, requireMapKey, result); + auto& vm = globalObject->vm(); exportNames.append(vm.propertyNames->defaultKeyword); exportValues.append(result); @@ -474,9 +450,8 @@ JSC::SourceCode createCommonJSModule( exportNames.append(Identifier::fromUid(vm.symbolRegistry().symbolForKey("CommonJS"_s))); exportValues.append(jsNumber(0)); - moduleObject->m_executable.clear(); - if (result.isObject()) { + DeferGCForAWhile deferGC(vm); auto* exports = asObject(result); auto* structure = exports->structure(); @@ -498,22 +473,20 @@ JSC::SourceCode createCommonJSModule( return true; }); } else { + auto catchScope = DECLARE_CATCH_SCOPE(vm); JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude); exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude); - if (throwScope.exception()) { - throwScope.release(); + if (catchScope.exception()) { + catchScope.clearExceptionExceptTermination(); return; } for (auto property : properties) { - if (UNLIKELY(property.isEmpty() || property.isNull())) + if (UNLIKELY(property.isEmpty() || property.isNull() || property.isPrivateName() || property.isSymbol())) continue; // ignore constructor - if (property == vm.propertyNames->constructor) - continue; - - if (property.isSymbol() || property.isPrivateName() || property == vm.propertyNames->defaultKeyword) + if (property == vm.propertyNames->constructor || property == vm.propertyNames->defaultKeyword) continue; JSC::PropertySlot slot(exports, PropertySlot::InternalMethodType::Get); diff --git a/src/bun.js/bindings/ErrorStackTrace.h b/src/bun.js/bindings/ErrorStackTrace.h index a8a6a192f..1284376a4 100644 --- a/src/bun.js/bindings/ErrorStackTrace.h +++ b/src/bun.js/bindings/ErrorStackTrace.h @@ -94,7 +94,6 @@ public: bool hasBytecodeIndex() const { return (m_bytecodeIndex.offset() != UINT_MAX) && !m_isWasmFrame; } JSC::BytecodeIndex bytecodeIndex() const { - ASSERT(hasBytecodeOffset()); return m_bytecodeIndex; } diff --git a/src/bun.js/bindings/ExceptionOr.h b/src/bun.js/bindings/ExceptionOr.h index f3378466e..3bbc641a1 100644 --- a/src/bun.js/bindings/ExceptionOr.h +++ b/src/bun.js/bindings/ExceptionOr.h @@ -116,25 +116,21 @@ template<typename ReturnType> inline bool ExceptionOr<ReturnType>::hasException( template<typename ReturnType> inline const Exception& ExceptionOr<ReturnType>::exception() const { - ASSERT(!m_wasReleased); return m_value.error(); } template<typename ReturnType> inline Exception ExceptionOr<ReturnType>::releaseException() { - ASSERT(!std::exchange(m_wasReleased, true)); return WTFMove(m_value.error()); } template<typename ReturnType> inline const ReturnType& ExceptionOr<ReturnType>::returnValue() const { - ASSERT(!m_wasReleased); return m_value.value(); } template<typename ReturnType> inline ReturnType ExceptionOr<ReturnType>::releaseReturnValue() { - ASSERT(!std::exchange(m_wasReleased, true)); return WTFMove(m_value.value()); } @@ -185,13 +181,11 @@ inline bool ExceptionOr<void>::hasException() const inline const Exception& ExceptionOr<void>::exception() const { - ASSERT(!m_wasReleased); return m_value.error(); } inline Exception ExceptionOr<void>::releaseException() { - ASSERT(!std::exchange(m_wasReleased, true)); return WTFMove(m_value.error()); } diff --git a/src/bun.js/bindings/JSBundlerPlugin.cpp b/src/bun.js/bindings/JSBundlerPlugin.cpp index e93556963..cae6a4b22 100644 --- a/src/bun.js/bindings/JSBundlerPlugin.cpp +++ b/src/bun.js/bindings/JSBundlerPlugin.cpp @@ -239,7 +239,7 @@ JSC_DEFINE_HOST_FUNCTION(jsBundlerPluginFunction_onResolveAsync, (JSC::JSGlobalO void JSBundlerPlugin::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); - ASSERT(inherits(vm, info())); + ASSERT(inherits(info())); this->onLoadFunction.initLater( [](const JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction>::Initializer& init) { auto& vm = init.vm; diff --git a/src/bun.js/bindings/Process.cpp b/src/bun.js/bindings/Process.cpp index ad8e5d073..69ee11e60 100644 --- a/src/bun.js/bindings/Process.cpp +++ b/src/bun.js/bindings/Process.cpp @@ -825,16 +825,12 @@ JSC_DEFINE_CUSTOM_GETTER(Process_getArgv, (JSC::JSGlobalObject * globalObject, J if (!thisObject) { return JSValue::encode(JSC::jsUndefined()); } - auto clientData = WebCore::clientData(vm); - - if (JSC::JSValue argv = thisObject->getIfPropertyExists( - globalObject, clientData->builtinNames().argvPrivateName())) { - return JSValue::encode(argv); - } JSC::EncodedJSValue argv_ = Bun__Process__getArgv(globalObject); - thisObject->putDirect(vm, clientData->builtinNames().argvPrivateName(), - JSC::JSValue::decode(argv_)); + auto clientData = WebCore::clientData(vm); + + thisObject->putDirect(vm, clientData->builtinNames().argvPublicName(), + JSC::JSValue::decode(argv_), 0); return argv_; } @@ -852,7 +848,7 @@ JSC_DEFINE_CUSTOM_SETTER(Process_setArgv, auto clientData = WebCore::clientData(vm); - return thisObject->putDirect(vm, clientData->builtinNames().argvPrivateName(), + return thisObject->putDirect(vm, clientData->builtinNames().argvPublicName(), JSC::JSValue::decode(value)); } diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index f2f5a372b..299ad7a8c 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -3168,6 +3168,15 @@ extern "C" void Bun__setOnEachMicrotaskTick(JSC::VM* vm, void* ptr, void (*callb }); } +JSC_DEFINE_CUSTOM_GETTER(BunCommonJSModule_getter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + Zig::GlobalObject* bunGlobalObject = jsCast<Zig::GlobalObject*>(globalObject); + JSValue returnValue = bunGlobalObject->m_BunCommonJSModuleValue.get(); + if (!returnValue) { + returnValue = jsUndefined(); + } + return JSValue::encode(returnValue); +} // This implementation works the same as setTimeout(myFunction, 0) // TODO: make it more efficient // https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate @@ -3526,6 +3535,9 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "CloseEvent"_s), JSC::CustomGetterSetter::create(vm, JSCloseEvent_getter, nullptr), JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "$_BunCommonJSModule_$"_s), JSC::CustomGetterSetter::create(vm, BunCommonJSModule_getter, nullptr), + JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + auto bufferAccessor = JSC::CustomGetterSetter::create(vm, JSBuffer_getter, JSBuffer_setter); auto realBufferAccessor = JSC::CustomGetterSetter::create(vm, JSBuffer_privateGetter, nullptr); @@ -3556,7 +3568,6 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().WritableStreamDefaultControllerPrivateName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_WritableStreamDefaultControllerConstructor, nullptr), attributesForStructure(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly)); putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().WritableStreamDefaultWriterPrivateName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_WritableStreamDefaultWriterConstructor, nullptr), attributesForStructure(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly)); putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().AbortSignalPrivateName(), CustomGetterSetter::create(vm, JSDOMAbortSignal_getter, nullptr), JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); - putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().TransformStreamDefaultControllerPublicName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_TransformStreamDefaultControllerConstructor, nullptr), static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontEnum)); putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().ReadableByteStreamControllerPublicName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_ReadableByteStreamControllerConstructor, nullptr), JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().ReadableStreamPublicName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_ReadableStreamConstructor, nullptr), JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().ReadableStreamBYOBReaderPublicName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_ReadableStreamBYOBReaderConstructor, nullptr), JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); @@ -3936,6 +3947,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) } thisObject->visitGeneratedLazyClasses<Visitor>(thisObject, visitor); + visitor.append(thisObject->m_BunCommonJSModuleValue); ScriptExecutionContext* context = thisObject->scriptExecutionContext(); visitor.addOpaqueRoot(context); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index d5f933540..dda1c8330 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -363,6 +363,7 @@ public: mutable WriteBarrier<Unknown> m_JSURLSearchParamsSetterValue; mutable WriteBarrier<Unknown> m_JSWebSocketSetterValue; mutable WriteBarrier<Unknown> m_JSDOMFormDataSetterValue; + mutable WriteBarrier<Unknown> m_BunCommonJSModuleValue; mutable WriteBarrier<JSFunction> m_thenables[promiseFunctionsSize + 1]; diff --git a/src/bun.js/bindings/webcore/EventEmitter.cpp b/src/bun.js/bindings/webcore/EventEmitter.cpp index 4ea10587e..0650d624c 100644 --- a/src/bun.js/bindings/webcore/EventEmitter.cpp +++ b/src/bun.js/bindings/webcore/EventEmitter.cpp @@ -171,7 +171,6 @@ Vector<JSObject*> EventEmitter::getListeners(const Identifier& eventType) // https://dom.spec.whatwg.org/#concept-event-listener-invoke void EventEmitter::fireEventListeners(const Identifier& eventType, const MarkedArgumentBuffer& arguments) { - ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::isEventAllowedInMainThread()); auto* data = eventTargetData(); if (!data) diff --git a/src/bun.js/bindings/webcore/EventTarget.cpp b/src/bun.js/bindings/webcore/EventTarget.cpp index cc2113ec9..9fb875595 100644 --- a/src/bun.js/bindings/webcore/EventTarget.cpp +++ b/src/bun.js/bindings/webcore/EventTarget.cpp @@ -261,7 +261,6 @@ static const AtomString& legacyType(const Event& event) // https://dom.spec.whatwg.org/#concept-event-listener-invoke void EventTarget::fireEventListeners(Event& event, EventInvokePhase phase) { - ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::isEventAllowedInMainThread()); ASSERT(event.isInitialized()); auto* data = eventTargetData(); diff --git a/src/bun.js/bindings/webcore/Node.h b/src/bun.js/bindings/webcore/Node.h index df9917a25..509e04192 100644 --- a/src/bun.js/bindings/webcore/Node.h +++ b/src/bun.js/bindings/webcore/Node.h @@ -75,42 +75,20 @@ public: // mutable OptionSet<NodeFlag> m_nodeFlags; }; -#if ASSERT_ENABLED - -inline void adopted(Node* node) -{ - if (!node) - return; - ASSERT(!node->m_deletionHasBegun); - ASSERT(!node->m_inRemovedLastRefFunction); - node->m_adoptionIsRequired = false; -} - -#endif // ASSERT_ENABLED - ALWAYS_INLINE void Node::ref() const { - ASSERT(isMainThread()); - ASSERT(!m_deletionHasBegun); - ASSERT(!m_inRemovedLastRefFunction); - ASSERT(!m_adoptionIsRequired); + m_refCountAndParentBit += s_refCountIncrement; } ALWAYS_INLINE void Node::deref() const { - ASSERT(isMainThread()); - ASSERT(refCount()); - ASSERT(!m_deletionHasBegun); - ASSERT(!m_inRemovedLastRefFunction); - ASSERT(!m_adoptionIsRequired); + auto updatedRefCount = m_refCountAndParentBit - s_refCountIncrement; if (!updatedRefCount) { // Don't update m_refCountAndParentBit to avoid double destruction through use of Ref<T>/RefPtr<T>. // (This is a security mitigation in case of programmer error. It will ASSERT in debug builds.) -#if ASSERT_ENABLED - m_inRemovedLastRefFunction = true; -#endif + const_cast<Node&>(*this).removedLastRef(); return; } @@ -119,8 +97,7 @@ ALWAYS_INLINE void Node::deref() const ALWAYS_INLINE bool Node::hasOneRef() const { - ASSERT(!m_deletionHasBegun); - ASSERT(!m_inRemovedLastRefFunction); + return refCount() == 1; } diff --git a/src/bun.js/bindings/webcore/WebSocket.cpp b/src/bun.js/bindings/webcore/WebSocket.cpp index e19145fc7..a346175df 100644 --- a/src/bun.js/bindings/webcore/WebSocket.cpp +++ b/src/bun.js/bindings/webcore/WebSocket.cpp @@ -267,7 +267,7 @@ static String resourceName(const URL& url) static String hostName(const URL& url, bool secure) { - ASSERT(url.protocolIs("wss") == secure); + // ASSERT(url.protocolIs("wss"_s) == secure); if (url.port() && ((!secure && url.port().value() != 80) || (secure && url.port().value() != 443))) return makeString(asASCIILowercase(url.host()), ':', url.port().value()); return url.host().convertToASCIILowercase(); @@ -280,7 +280,7 @@ ExceptionOr<void> WebSocket::connect(const String& url, const Vector<String>& pr ExceptionOr<void> WebSocket::connect(const String& url, const Vector<String>& protocols, std::optional<FetchHeaders::Init>&& headersInit) { - LOG(Network, "WebSocket %p connect() url='%s'", this, url.utf8().data()); + // LOG(Network, "WebSocket %p connect() url='%s'", this, url.utf8().data()); m_url = URL { url }; ASSERT(scriptExecutionContext()); @@ -446,7 +446,7 @@ ExceptionOr<void> WebSocket::connect(const String& url, const Vector<String>& pr ExceptionOr<void> WebSocket::send(const String& message) { - LOG(Network, "WebSocket %p send() Sending String '%s'", this, message.utf8().data()); + // LOG(Network, "WebSocket %p send() Sending String '%s'", this, message.utf8().data()); if (m_state == CONNECTING) return Exception { InvalidStateError }; // No exception is raised if the connection was once established but has subsequently been closed. @@ -466,7 +466,7 @@ ExceptionOr<void> WebSocket::send(const String& message) ExceptionOr<void> WebSocket::send(ArrayBuffer& binaryData) { - LOG(Network, "WebSocket %p send() Sending ArrayBuffer %p", this, &binaryData); + // LOG(Network, "WebSocket %p send() Sending ArrayBuffer %p", this, &binaryData); if (m_state == CONNECTING) return Exception { InvalidStateError }; if (m_state == CLOSING || m_state == CLOSED) { @@ -484,7 +484,7 @@ ExceptionOr<void> WebSocket::send(ArrayBuffer& binaryData) ExceptionOr<void> WebSocket::send(ArrayBufferView& arrayBufferView) { - LOG(Network, "WebSocket %p send() Sending ArrayBufferView %p", this, &arrayBufferView); + // LOG(Network, "WebSocket %p send() Sending ArrayBufferView %p", this, &arrayBufferView); if (m_state == CONNECTING) return Exception { InvalidStateError }; @@ -506,7 +506,7 @@ ExceptionOr<void> WebSocket::send(ArrayBufferView& arrayBufferView) // ExceptionOr<void> WebSocket::send(Blob& binaryData) // { -// LOG(Network, "WebSocket %p send() Sending Blob '%s'", this, binaryData.url().stringCenterEllipsizedToLength().utf8().data()); +// LOG(Network, "WebSocket %p send() Sending Blob '%s'", this, binaryData.url().stringCenterEllipsizedToLength().utf8().data()); // if (m_state == CONNECTING) // return Exception { InvalidStateError }; // if (m_state == CLOSING || m_state == CLOSED) { @@ -587,10 +587,10 @@ void WebSocket::sendWebSocketString(const String& message) ExceptionOr<void> WebSocket::close(std::optional<unsigned short> optionalCode, const String& reason) { int code = optionalCode ? optionalCode.value() : static_cast<int>(0); - if (code == 0) - LOG(Network, "WebSocket %p close() without code and reason", this); - else { - LOG(Network, "WebSocket %p close() code=%d reason='%s'", this, code, reason.utf8().data()); + if (code == 0) { + // LOG(Network, "WebSocket %p close() without code and reason", this); + } else { + // LOG(Network, "WebSocket %p close() code=%d reason='%s'", this, code, reason.utf8().data()); // if (!(code == WebSocketChannel::CloseEventCodeNormalClosure || (WebSocketChannel::CloseEventCodeMinimumUserDefined <= code && code <= WebSocketChannel::CloseEventCodeMaximumUserDefined))) // return Exception { InvalidAccessError }; if (reason.length() > maxReasonSizeInBytes) { @@ -718,7 +718,7 @@ ScriptExecutionContext* WebSocket::scriptExecutionContext() const // void WebSocket::contextDestroyed() // { -// LOG(Network, "WebSocket %p contextDestroyed()", this); +// LOG(Network, "WebSocket %p contextDestroyed()", this); // ASSERT(!m_channel); // ASSERT(m_state == CLOSED); // // ActiveDOMObject::contextDestroyed(); @@ -763,7 +763,7 @@ void WebSocket::didConnect() { // from new WebSocket() -> connect() - LOG(Network, "WebSocket %p didConnect()", this); + // LOG(Network, "WebSocket %p didConnect()", this); // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this] { if (m_state == CLOSED) return; @@ -797,7 +797,7 @@ void WebSocket::didConnect() void WebSocket::didReceiveMessage(String&& message) { - LOG(Network, "WebSocket %p didReceiveMessage() Text message '%s'", this, message.utf8().data()); + // LOG(Network, "WebSocket %p didReceiveMessage() Text message '%s'", this, message.utf8().data()); // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this, message = WTFMove(message)]() mutable { if (m_state != OPEN) return; @@ -831,7 +831,7 @@ void WebSocket::didReceiveMessage(String&& message) void WebSocket::didReceiveBinaryData(Vector<uint8_t>&& binaryData) { - LOG(Network, "WebSocket %p didReceiveBinaryData() %u byte binary message", this, static_cast<unsigned>(binaryData.size())); + // LOG(Network, "WebSocket %p didReceiveBinaryData() %u byte binary message", this, static_cast<unsigned>(binaryData.size())); // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this, binaryData = WTFMove(binaryData)]() mutable { if (m_state != OPEN) return; @@ -916,7 +916,7 @@ void WebSocket::didReceiveBinaryData(Vector<uint8_t>&& binaryData) void WebSocket::didReceiveMessageError(unsigned short code, WTF::String reason) { - LOG(Network, "WebSocket %p didReceiveErrorMessage()", this); + // LOG(Network, "WebSocket %p didReceiveErrorMessage()", this); // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this, reason = WTFMove(reason)] { if (m_state == CLOSED) return; @@ -931,7 +931,7 @@ void WebSocket::didReceiveMessageError(unsigned short code, WTF::String reason) void WebSocket::didUpdateBufferedAmount(unsigned bufferedAmount) { - LOG(Network, "WebSocket %p didUpdateBufferedAmount() New bufferedAmount is %u", this, bufferedAmount); + // LOG(Network, "WebSocket %p didUpdateBufferedAmount() New bufferedAmount is %u", this, bufferedAmount); if (m_state == CLOSED) return; m_bufferedAmount = bufferedAmount; @@ -939,7 +939,7 @@ void WebSocket::didUpdateBufferedAmount(unsigned bufferedAmount) void WebSocket::didStartClosingHandshake() { - LOG(Network, "WebSocket %p didStartClosingHandshake()", this); + // LOG(Network, "WebSocket %p didStartClosingHandshake()", this); // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this] { if (m_state == CLOSED) return; @@ -950,7 +950,7 @@ void WebSocket::didStartClosingHandshake() void WebSocket::didClose(unsigned unhandledBufferedAmount, unsigned short code, const String& reason) { - LOG(Network, "WebSocket %p didClose()", this); + // LOG(Network, "WebSocket %p didClose()", this); if (this->m_connectedWebSocketKind == ConnectedWebSocketKind::None) return; diff --git a/src/cli/init_command.zig b/src/cli/init_command.zig index bc8867368..c7f566836 100644 --- a/src/cli/init_command.zig +++ b/src/cli/init_command.zig @@ -300,13 +300,7 @@ pub const InitCommand = struct { if (needs_dev_dependencies) { var dev_dependencies = fields.object.get("devDependencies") orelse js_ast.Expr.init(js_ast.E.Object, js_ast.E.Object{}, logger.Loc.Empty); - const version = comptime brk: { - var base = Global.version; - base.patch = 0; - break :brk base; - }; - - try dev_dependencies.data.e_object.putString(alloc, "bun-types", comptime std.fmt.comptimePrint("^{any}", .{version.fmt("")})); + try dev_dependencies.data.e_object.putString(alloc, "bun-types", "latest"); try fields.object.put(alloc, "devDependencies", dev_dependencies); } diff --git a/src/js_parser.zig b/src/js_parser.zig index 7c227ce75..5a9bca91a 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -5798,8 +5798,7 @@ fn NewParser_( if (p.options.features.inlining) { if (p.const_values.get(ref)) |replacement| { - // TODO: - // p.ignoreUsage(ref); + p.ignoreUsage(ref); return replacement; } } @@ -20427,7 +20426,8 @@ fn NewParser_( var end: usize = 0; for (decls) |decl| { if (decl.binding.data == .b_identifier) { - if (p.const_values.contains(decl.binding.data.b_identifier.ref)) { + const symbol = p.symbols.items[decl.binding.data.b_identifier.ref.innerIndex()]; + if (p.const_values.contains(decl.binding.data.b_identifier.ref) and symbol.use_count_estimate == 0) { continue; } } @@ -21180,17 +21180,75 @@ fn NewParser_( }, logger.Loc.Empty, ); + const cjsGlobal = p.newSymbol(.unbound, "$_BunCommonJSModule_$") catch unreachable; var call_args = allocator.alloc(Expr, 6) catch unreachable; + const this_module = p.newExpr( + E.Dot{ + .name = "module", + .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ); // - // (function(module, exports, require, __dirname, __filename) {}).call(exports, module, exports, require, __dirname, __filename) + // (function(module, exports, require, __dirname, __filename) {}).call(this.exports, this.module, this.exports, this.require, __dirname, __filename) call_args[0..6].* = .{ - p.newExpr(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), - p.newExpr(E.Identifier{ .ref = p.module_ref }, logger.Loc.Empty), - p.newExpr(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), - p.newExpr(E.Identifier{ .ref = p.require_ref }, logger.Loc.Empty), - p.newExpr(E.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty), - p.newExpr(E.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty), + p.newExpr( + E.Dot{ + .name = "exports", + .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + this_module, + p.newExpr( + E.Dot{ + .name = "exports", + .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + p.newExpr( + E.Binary{ + .left = p.newExpr( + E.Dot{ + .name = "require", + .target = this_module, + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + .op = .bin_assign, + .right = p.newExpr( + E.Dot{ + .name = "require", + .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + }, + logger.Loc.Empty, + ), + p.newExpr( + E.Dot{ + .name = "__dirname", + .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + p.newExpr( + E.Dot{ + .name = "__filename", + .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), }; const call = p.newExpr( diff --git a/src/string.zig b/src/string.zig index 62cdc5462..f09428f69 100644 --- a/src/string.zig +++ b/src/string.zig @@ -293,6 +293,7 @@ pub const String = extern struct { ) String; pub fn createExternal(bytes: []const u8, isLatin1: bool, ctx: ?*anyopaque, callback: ?*const fn (*anyopaque, *anyopaque, u32) callconv(.C) void) String { + JSC.markBinding(@src()); return BunString__createExternal(bytes.ptr, bytes.len, isLatin1, ctx, callback); } @@ -348,6 +349,8 @@ pub const String = extern struct { } pub fn toWTF(this: *String) void { + JSC.markBinding(@src()); + BunString__toWTFString(this); } diff --git a/test/bundler/esbuild/default.test.ts b/test/bundler/esbuild/default.test.ts index 215276139..0d1775606 100644 --- a/test/bundler/esbuild/default.test.ts +++ b/test/bundler/esbuild/default.test.ts @@ -6520,4 +6520,46 @@ describe("bundler", () => { `, }, }); + itBundled("default/ConstDeclNotRemovedIfReferencedBeforeDecl", { + files: { + "/entry.js": ` + { + const foo = () => { + return data; + } + const data = 123; + + console.log(foo()); + } + `, + }, + minifySyntax: true, + run: { + stdout: "123", + }, + onAfterBundle(api) { + api.expectFile("/out.js").toContain("data = 123"); + }, + }); + itBundled("default/ConstDeclRemovedIfReferencedBeforeAllUses", { + files: { + "/entry.js": ` + { + const data = 123; + const foo = () => { + return data; + } + + console.log(foo()); + } + `, + }, + minifySyntax: true, + run: { + stdout: "123", + }, + onAfterBundle(api) { + api.expectFile("/out.js").not.toContain("data = 123"); + }, + }); }); diff --git a/test/js/bun/http/bun-server.test.ts b/test/js/bun/http/bun-server.test.ts index 7d0915b89..37675c25d 100644 --- a/test/js/bun/http/bun-server.test.ts +++ b/test/js/bun/http/bun-server.test.ts @@ -1,6 +1,54 @@ import { describe, expect, test } from "bun:test"; describe("Server", () => { + test("should not allow Bun.serve without first argument being a object", () => { + expect(() => { + //@ts-ignore + const server = Bun.serve(); + server.stop(true); + }).toThrow("Bun.serve expects an object"); + + [undefined, null, 1, "string", true, false, Symbol("symbol")].forEach(value => { + expect(() => { + //@ts-ignore + const server = Bun.serve(value); + server.stop(true); + }).toThrow("Bun.serve expects an object"); + }); + }); + + test("should not allow Bun.serve with invalid tls option", () => { + [1, "string", true, Symbol("symbol"), false].forEach(value => { + expect(() => { + const server = Bun.serve({ + //@ts-ignore + tls: value, + fetch() { + return new Response("Hello"); + }, + port: 0, + }); + server.stop(true); + }).toThrow("tls option expects an object"); + }); + }); + + test("should allow Bun.serve using null or undefined tls option", () => { + [null, undefined].forEach(value => { + expect(() => { + const server = Bun.serve({ + //@ts-ignore + tls: value, + fetch() { + return new Response("Hello"); + }, + port: 0, + }); + server.stop(true); + }).not.toThrow("tls option expects an object"); + }); + }); + test("returns active port when initializing server with 0 port", () => { const server = Bun.serve({ fetch() { diff --git a/test/js/bun/net/tcp-server.test.ts b/test/js/bun/net/tcp-server.test.ts index d029d9273..18fa29231 100644 --- a/test/js/bun/net/tcp-server.test.ts +++ b/test/js/bun/net/tcp-server.test.ts @@ -58,6 +58,44 @@ it("remoteAddress works", async () => { await prom; }); +it("should not allow invalid tls option", () => { + [1, "string", Symbol("symbol")].forEach(value => { + expect(() => { + // @ts-ignore + const server = Bun.listen({ + socket: { + open(ws) {}, + close() {}, + data() {}, + }, + port: 0, + hostname: "localhost", + tls: value, + }); + server.stop(true); + }).toThrow("tls option expects an object"); + }); +}); + +it("should allow using false, null or undefined tls option", () => { + [false, null, undefined].forEach(value => { + expect(() => { + // @ts-ignore + const server = Bun.listen({ + socket: { + open(ws) {}, + close() {}, + data() {}, + }, + port: 0, + hostname: "localhost", + tls: value, + }); + server.stop(true); + }).not.toThrow("tls option expects an object"); + }); +}); + it("echo server 1 on 1", async () => { // wrap it in a separate closure so the GC knows to clean it up // the sockets & listener don't escape the closure diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index f701be1b3..ee181e70c 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -1,6 +1,7 @@ import { resolveSync, which } from "bun"; import { describe, expect, it } from "bun:test"; import { existsSync, readFileSync, realpathSync } from "fs"; +import { bunExe } from "harness"; import { basename, resolve } from "path"; it("process", () => { @@ -224,3 +225,12 @@ it("process.execArgv", () => { it("process.binding", () => { expect(() => process.binding("buffer")).toThrow(); }); + +it("process.argv", () => { + expect(process.argv).toBeInstanceOf(Array); + expect(process.argv[0]).toBe(bunExe()); + expect(process.argv).toEqual(Bun.argv); + + // assert we aren't creating a new process.argv each call + expect(process.argv).toBe(process.argv); +}); |