From 0f2a79b9c1a5b0142f8099e94712799607e8990f Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 26 May 2023 03:32:28 -0700 Subject: [PATCH] Fix crash in test.todo + remove JSC C API usages in bun:test (#3079) * Fix crash in test.todo * remove usages of JSC C API in bun:test * Remove additional JSC-C API usages * fix `make headers` * URLSearchParams.length * FormData length * URLSearchParams length * Fix `make headers` * very fancy length * Fix bug with exceptions being ignored sometimes * Add tests for extension toHaveLength --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> --- src/bun.js/api/JSTranspiler.zig | 6 +- src/bun.js/api/bun.zig | 2 +- src/bun.js/api/filesystem_router.zig | 2 +- src/bun.js/api/server.zig | 6 +- src/bun.js/base.zig | 1 - src/bun.js/bindings/URLSearchParams.h | 1 + src/bun.js/bindings/ZigGeneratedClasses.h | 60 ++ src/bun.js/bindings/ZigGlobalObject.cpp | 32 +- src/bun.js/bindings/ZigGlobalObject.h | 4 + src/bun.js/bindings/bindings.cpp | 76 ++- src/bun.js/bindings/bindings.zig | 57 +- src/bun.js/bindings/exports.zig | 4 +- src/bun.js/bindings/headers-handwritten.h | 2 + src/bun.js/bindings/headers.h | 10 +- src/bun.js/bindings/headers.zig | 2 +- src/bun.js/bindings/webcore/JSDOMFormData.cpp | 13 + src/bun.js/bindings/webcore/JSFetchHeaders.h | 2 +- .../bindings/webcore/JSURLSearchParams.cpp | 11 + src/bun.js/bindings/webcore/WebSocket.cpp | 16 +- src/bun.js/node/node_os.zig | 2 +- src/bun.js/test/jest.zig | 590 +++++++++--------- src/bun.js/test/pretty_format.zig | 4 +- src/bun.js/webcore/blob.zig | 31 +- src/cli/test_command.zig | 2 +- src/http/websocket_http_client.zig | 19 +- src/js_ast.zig | 4 +- src/napi/napi.zig | 4 +- test/js/bun/test/test-test.test.ts | 46 ++ 28 files changed, 645 insertions(+), 364 deletions(-) diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index fecce60cdc43f3..8a59f59e7c7169 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -385,7 +385,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std single_external[0] = std.fmt.allocPrint(allocator, "{}", .{external}) catch unreachable; transpiler.transform.external = single_external; } else if (toplevel_type.isArray()) { - const count = external.getLengthOfArray(globalThis); + const count = external.getLength(globalThis); if (count == 0) break :external; var externals = allocator.alloc(string, count) catch unreachable; @@ -606,7 +606,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std var length_iter = iter; while (length_iter.next()) |value| { if (value.isString()) { - const length = @truncate(u32, value.getLengthOfArray(globalThis)); + const length = @truncate(u32, value.getLength(globalThis)); string_count += @as(u32, @boolToInt(length > 0)); total_name_buf_len += length; } @@ -679,7 +679,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std continue; } - if (value.isObject() and value.getLengthOfArray(globalObject) == 2) { + if (value.isObject() and value.getLength(globalObject) == 2) { const replacementValue = JSC.JSObject.getIndex(value, globalThis, 1); if (exportReplacementValue(replacementValue, globalThis)) |to_replace| { const replacementKey = JSC.JSObject.getIndex(value, globalThis, 0); diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 298c41e632325f..f4900d0d2b9669 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -2933,7 +2933,7 @@ pub const Timer = struct { args_buf[0] = arguments; args = args_buf[0..1]; } else { - const count = arguments.getLengthOfArray(globalThis); + const count = arguments.getLength(globalThis); if (count > 0) { if (count > args_buf.len) { args = bun.default_allocator.alloc(JSC.JSValue, count) catch unreachable; diff --git a/src/bun.js/api/filesystem_router.zig b/src/bun.js/api/filesystem_router.zig index ec95ec3739ce06..d926ca7b383fe1 100644 --- a/src/bun.js/api/filesystem_router.zig +++ b/src/bun.js/api/filesystem_router.zig @@ -233,7 +233,7 @@ pub const FileSystemRouter = struct { globalThis.allocator().destroy(arena); return null; } - if (val.getLengthOfArray(globalThis) == 0) continue; + if (val.getLength(globalThis) == 0) continue; extensions.appendAssumeCapacity((val.toSlice(globalThis, allocator).clone(allocator) catch unreachable).slice()[1..]); } } diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index bb48f578663ec1..9af44deba9e5fc 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -288,7 +288,7 @@ pub const ServerConfig = struct { if (obj.getTruthy(global, "key")) |js_obj| { if (js_obj.jsType().isArray()) { - const count = js_obj.getLengthOfArray(global); + const count = js_obj.getLength(global); if (count > 0) { const native_array = bun.default_allocator.alloc([*c]const u8, count) catch unreachable; @@ -389,7 +389,7 @@ pub const ServerConfig = struct { if (obj.getTruthy(global, "cert")) |js_obj| { if (js_obj.jsType().isArray()) { - const count = js_obj.getLengthOfArray(global); + const count = js_obj.getLength(global); if (count > 0) { const native_array = bun.default_allocator.alloc([*c]const u8, count) catch unreachable; @@ -504,7 +504,7 @@ pub const ServerConfig = struct { if (obj.getTruthy(global, "ca")) |js_obj| { if (js_obj.jsType().isArray()) { - const count = js_obj.getLengthOfArray(global); + const count = js_obj.getLength(global); if (count > 0) { const native_array = bun.default_allocator.alloc([*c]const u8, count) catch unreachable; diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index 66c83dd5ff1be5..9b9cacbe7c551b 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -2202,7 +2202,6 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{ Comment, DebugServer, DebugSSLServer, - DescribeScope, DocEnd, DocType, Element, diff --git a/src/bun.js/bindings/URLSearchParams.h b/src/bun.js/bindings/URLSearchParams.h index 486098adc21db6..7f9926a3a9afdc 100644 --- a/src/bun.js/bindings/URLSearchParams.h +++ b/src/bun.js/bindings/URLSearchParams.h @@ -52,6 +52,7 @@ class URLSearchParams : public RefCounted { String toString() const; void updateFromAssociatedURL(); void sort(); + size_t size() const { return m_pairs.size(); } class Iterator { public: diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h index cf5446a1aea7a0..a27d2bae9438d9 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.h +++ b/src/bun.js/bindings/ZigGeneratedClasses.h @@ -56,6 +56,8 @@ class JSBlob final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSBlob(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -112,6 +114,8 @@ class JSBuildArtifact final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSBuildArtifact(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -173,6 +177,8 @@ class JSBuildMessage final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSBuildMessage(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -231,6 +237,8 @@ class JSCryptoHasher final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSCryptoHasher(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -288,6 +296,8 @@ class JSDirent final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSDirent(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -344,6 +354,8 @@ class JSExpect final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSExpect(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -401,6 +413,8 @@ class JSExpectAny final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSExpectAny(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -457,6 +471,8 @@ class JSFileSystemRouter final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSFileSystemRouter(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -515,6 +531,8 @@ class JSListener final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSListener(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -572,6 +590,8 @@ class JSMD4 final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSMD4(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -622,6 +642,8 @@ class JSMD5 final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSMD5(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -672,6 +694,8 @@ class JSMatchedRoute final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSMatchedRoute(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -734,6 +758,8 @@ class JSNodeJSFS final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSNodeJSFS(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -784,6 +810,8 @@ class JSRequest final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSRequest(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -843,6 +871,8 @@ class JSResolveMessage final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSResolveMessage(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -904,6 +934,8 @@ class JSResponse final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSResponse(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -963,6 +995,8 @@ class JSSHA1 final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSSHA1(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1013,6 +1047,8 @@ class JSSHA224 final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSSHA224(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1063,6 +1099,8 @@ class JSSHA256 final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSSHA256(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1113,6 +1151,8 @@ class JSSHA384 final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSSHA384(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1163,6 +1203,8 @@ class JSSHA512 final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSSHA512(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1213,6 +1255,8 @@ class JSSHA512_256 final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSSHA512_256(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1263,6 +1307,8 @@ class JSServerWebSocket final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSServerWebSocket(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1320,6 +1366,8 @@ class JSStats final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSStats(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1378,6 +1426,8 @@ class JSSubprocess final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSSubprocess(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1463,6 +1513,8 @@ class JSTCPSocket final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSTCPSocket(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1547,6 +1599,8 @@ class JSTLSSocket final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSTLSSocket(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1631,6 +1685,8 @@ class JSTextDecoder final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSTextDecoder(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1687,6 +1743,8 @@ class JSTimeout final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSTimeout(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { @@ -1744,6 +1802,8 @@ class JSTranspiler final : public JSC::JSDestructibleObject { void* m_ctx { nullptr }; + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + JSTranspiler(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) : Base(vm, structure) { diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 92568f1a94102a..f0ff9cf169f85a 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2041,6 +2041,17 @@ JSC_DEFINE_HOST_FUNCTION(functionConcatTypedArraysFromIterator, (JSGlobalObject return flattenArrayOfBuffersIntoArrayBuffer(globalObject, iter->getDirect(vm, vm.propertyNames->value)); } +extern "C" JSC__JSValue Bun__Jest__createTestModuleObject(JSC::JSGlobalObject*); +extern "C" JSC__JSValue Bun__Jest__createTestPreloadObject(JSC::JSGlobalObject*); +extern "C" JSC__JSValue Bun__Jest__testPreloadObject(Zig::GlobalObject* globalObject) +{ + return JSValue::encode(globalObject->lazyPreloadTestModuleObject()); +} +extern "C" JSC__JSValue Bun__Jest__testModuleObject(Zig::GlobalObject* globalObject) +{ + return JSValue::encode(globalObject->lazyTestModuleObject()); +} + static inline JSC__JSValue ZigGlobalObject__readableStreamToArrayBufferBody(Zig::GlobalObject* globalObject, JSC__JSValue readableStreamValue); static inline JSC__JSValue ZigGlobalObject__readableStreamToArrayBufferBody(Zig::GlobalObject* globalObject, JSC__JSValue readableStreamValue) { @@ -2578,6 +2589,24 @@ void GlobalObject::finishCreation(VM& vm) init.set(result.toObject(globalObject)); }); + m_lazyTestModuleObject.initLater( + [](const Initializer& init) { + JSC::VM& vm = init.vm; + JSC::JSGlobalObject* globalObject = init.owner; + + JSValue result = JSValue::decode(Bun__Jest__createTestModuleObject(globalObject)); + init.set(result.toObject(globalObject)); + }); + + m_lazyPreloadTestModuleObject.initLater( + [](const Initializer& init) { + JSC::VM& vm = init.vm; + JSC::JSGlobalObject* globalObject = init.owner; + + JSValue result = JSValue::decode(Bun__Jest__createTestPreloadObject(globalObject)); + init.set(result.toObject(globalObject)); + }); + // Change prototype from null to object for synthetic modules. m_moduleNamespaceObjectStructure.initLater( [](const Initializer& init) { @@ -3767,7 +3796,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_lazyRequireCacheObject.visit(visitor); thisObject->m_vmModuleContextMap.visit(visitor); thisObject->m_bunSleepThenCallback.visit(visitor); - + thisObject->m_lazyTestModuleObject.visit(visitor); + thisObject->m_lazyPreloadTestModuleObject.visit(visitor); thisObject->m_cachedGlobalObjectStructure.visit(visitor); thisObject->m_cachedGlobalProxyStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index c7c792a2c79544..67c3196c7e9c08 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -258,6 +258,8 @@ class GlobalObject : public JSC::JSGlobalObject { Structure* globalObjectStructure() { return m_cachedGlobalObjectStructure.getInitializedOnMainThread(this); } Structure* globalProxyStructure() { return m_cachedGlobalProxyStructure.getInitializedOnMainThread(this); } + JSObject* lazyTestModuleObject() { return m_lazyTestModuleObject.getInitializedOnMainThread(this); } + JSObject* lazyPreloadTestModuleObject() { return m_lazyPreloadTestModuleObject.getInitializedOnMainThread(this); } JSWeakMap* vmModuleContextMap() { return m_vmModuleContextMap.getInitializedOnMainThread(this); } @@ -469,6 +471,8 @@ class GlobalObject : public JSC::JSGlobalObject { LazyProperty m_dnsObject; LazyProperty m_vmModuleContextMap; LazyProperty m_lazyRequireCacheObject; + LazyProperty m_lazyTestModuleObject; + LazyProperty m_lazyPreloadTestModuleObject; LazyProperty m_bunSleepThenCallback; LazyProperty m_cachedGlobalObjectStructure; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index a72fba99240305..1e6da1e71ceb58 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -92,6 +92,9 @@ #include "ZigGeneratedClasses.h" #include "JavaScriptCore/JSMapInlines.h" +#include +#include "JSURLSearchParams.h" + template static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) { @@ -1118,11 +1121,78 @@ JSC__JSValue JSC__JSValue__createEmptyObject(JSC__JSGlobalObject* globalObject, JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), initialCapacity)); } -uint64_t JSC__JSValue__getLengthOfArray(JSC__JSValue value, JSC__JSGlobalObject* globalObject) +extern "C" uint64_t Bun__Blob__getSizeForBindings(void* blob); + +double JSC__JSValue__getLengthIfPropertyExistsInternal(JSC__JSValue value, JSC__JSGlobalObject* globalObject) { JSC::JSValue jsValue = JSC::JSValue::decode(value); - JSC::JSObject* object = jsValue.toObject(globalObject); - return JSC::toLength(globalObject, object); + if (!jsValue || !jsValue.isCell()) + return 0; + JSCell* cell = jsValue.asCell(); + JSC::JSType type = cell->type(); + + switch (type) { + case JSC::JSType::StringType: + return static_cast(jsValue.toString(globalObject)->length()); + case JSC::JSType::ArrayType: + return static_cast(jsCast(cell)->length()); + + case JSC::JSType::Int8ArrayType: + case JSC::JSType::Uint8ArrayType: + case JSC::JSType::Uint8ClampedArrayType: + case JSC::JSType::Int16ArrayType: + case JSC::JSType::Uint16ArrayType: + case JSC::JSType::Int32ArrayType: + case JSC::JSType::Uint32ArrayType: + case JSC::JSType::Float32ArrayType: + case JSC::JSType::Float64ArrayType: + case JSC::JSType::BigInt64ArrayType: + case JSC::JSType::BigUint64ArrayType: + return static_cast(jsCast(cell)->length()); + + case JSC::JSType::JSMapType: + return static_cast(jsCast(cell)->size()); + + case JSC::JSType::JSSetType: + return static_cast(jsCast(cell)->size()); + + case JSC::JSType::JSWeakMapType: + return static_cast(jsCast(cell)->size()); + + case JSC::JSType::ArrayBufferType: { + auto* arrayBuffer = jsCast(cell); + if (auto* impl = arrayBuffer->impl()) { + return static_cast(impl->byteLength()); + } + + return 0; + } + + case JSC::JSType(JSDOMWrapperType): { + if (auto* headers = jsDynamicCast(cell)) + return static_cast(jsCast(cell)->wrapped().size()); + + if (auto* blob = jsDynamicCast(cell)) { + uint64_t size = Bun__Blob__getSizeForBindings(blob->wrapped()); + if (size == std::numeric_limits::max()) + return std::numeric_limits::max(); + return static_cast(size); + } + } + + default: { + + if (auto* object = jsDynamicCast(cell)) { + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + if (JSValue lengthValue = object->getIfPropertyExists(globalObject, globalObject->vm().propertyNames->length)) { + RETURN_IF_EXCEPTION(scope, 0); + RELEASE_AND_RETURN(scope, lengthValue.toNumber(globalObject)); + } + } + } + } + + return std::numeric_limits::infinity(); } void JSC__JSObject__putRecord(JSC__JSObject* object, JSC__JSGlobalObject* global, ZigString* key, diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 038a81b1ed73b9..42f0c53444a3e4 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -2401,7 +2401,7 @@ pub const JSGlobalObject = extern struct { ) JSC.JSValue { return JSC.toTypeErrorWithCode( "NOT_ENOUGH_ARGUMENTS", - "Not enough arguments to '" ++ name_ ++ "''. Expected {d}, got {d}.", + "Not enough arguments to '" ++ name_ ++ "'. Expected {d}, got {d}.", .{ expected, got }, this, ); @@ -2778,7 +2778,7 @@ pub const JSArrayIterator = struct { return .{ .array = value, .global = global, - .len = @truncate(u32, value.getLengthOfArray(global)), + .len = @truncate(u32, value.getLength(global)), }; } @@ -4140,7 +4140,7 @@ pub const JSValue = enum(JSValueReprInt) { return error.JSError; } - if (prop.getLengthOfArray(globalThis) == 0) { + if (prop.getLength(globalThis) == 0) { return null; } @@ -4366,8 +4366,53 @@ pub const JSValue = enum(JSValueReprInt) { return @intCast(u32, @max(this.toInt32(), 0)); } - pub fn getLengthOfArray(this: JSValue, globalThis: *JSGlobalObject) u64 { - return cppFn("getLengthOfArray", .{ + /// This function supports: + /// - Array, DerivedArray & friends + /// - String, DerivedString & friends + /// - TypedArray + /// - Map (size) + /// - WeakMap (size) + /// - Set (size) + /// - WeakSet (size) + /// - ArrayBuffer (byteLength) + /// - anything with a .length property returning a number + /// + /// If the "length" property does not exist, this function will return 0. + pub fn getLength(this: JSValue, globalThis: *JSGlobalObject) u64 { + const len = this.getLengthIfPropertyExistsInternal(globalThis); + if (len == std.math.f64_max) { + return 0; + } + + return @floatToInt(u64, @max(len, 0)); + } + + /// This function supports: + /// - Array, DerivedArray & friends + /// - String, DerivedString & friends + /// - TypedArray + /// - Map (size) + /// - WeakMap (size) + /// - Set (size) + /// - WeakSet (size) + /// - ArrayBuffer (byteLength) + /// - anything with a .length property returning a number + /// + /// If the "length" property does not exist, this function will return null. + pub fn tryGetLength(this: JSValue, globalThis: *JSGlobalObject) ?f64 { + const len = this.getLengthIfPropertyExistsInternal(globalThis); + if (len == std.math.f64_max) { + return null; + } + + return @floatToInt(u64, @max(len, 0)); + } + + /// Do not use this directly! + /// + /// If the property does not exist, this function will return max(f64) instead of 0. + pub fn getLengthIfPropertyExistsInternal(this: JSValue, globalThis: *JSGlobalObject) f64 { + return cppFn("getLengthIfPropertyExistsInternal", .{ this, globalThis, }); @@ -4479,7 +4524,7 @@ pub const JSValue = enum(JSValueReprInt) { "getIfExists", "getIfPropertyExistsFromPath", "getIfPropertyExistsImpl", - "getLengthOfArray", + "getLengthIfPropertyExistsInternal", "getNameProperty", "getPropertyByPropertyName", "getPropertyNames", diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 666adf5955f81d..bfea93306d9a2f 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -2061,7 +2061,7 @@ pub const ZigConsoleClient = struct { writer.print(comptime Output.prettyFmt("[Getter]", enable_ansi_colors), .{}); }, .Array => { - const len = @truncate(u32, value.getLengthOfArray(this.globalThis)); + const len = @truncate(u32, value.getLength(this.globalThis)); if (len == 0) { writer.writeAll("[]"); this.addForNewLine(2); @@ -2573,7 +2573,7 @@ pub const ZigConsoleClient = struct { this.writeIndent(Writer, writer_) catch unreachable; }, .Array => { - const length = children.getLengthOfArray(this.globalThis); + const length = children.getLength(this.globalThis); if (length == 0) break :print_children; writer.writeAll(">\n"); diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 24a54032090698..673b366d101d53 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -2,6 +2,8 @@ typedef uint16_t ZigErrorCode; typedef struct VirtualMachine VirtualMachine; +// exists to make headers.h happy +typedef struct CppWebSocket CppWebSocket; typedef struct ZigString { const unsigned char* ptr; diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index c7612938c9589b..5034b7652f5d4c 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -330,7 +330,7 @@ CPP_DECL void JSC__JSValue__getClassName(JSC__JSValue JSValue0, JSC__JSGlobalObj CPP_DECL JSC__JSValue JSC__JSValue__getErrorsProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL JSC__JSValue JSC__JSValue__getIfPropertyExistsFromPath(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, JSC__JSValue JSValue2); CPP_DECL JSC__JSValue JSC__JSValue__getIfPropertyExistsImpl(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, const unsigned char* arg2, uint32_t arg3); -CPP_DECL uint64_t JSC__JSValue__getLengthOfArray(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); +CPP_DECL double JSC__JSValue__getLengthIfPropertyExistsInternal(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__JSValue__getNameProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); CPP_DECL JSC__JSValue JSC__JSValue__getPrototype(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__JSValue__getSymbolDescription(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); @@ -708,7 +708,7 @@ ZIG_DECL JSC__JSValue FileSink__write(JSC__JSGlobalObject* arg0, JSC__CallFrame* #ifdef __cplusplus ZIG_DECL void Bun__WebSocketHTTPClient__cancel(WebSocketHTTPClient* arg0); -ZIG_DECL WebSocketHTTPClient* Bun__WebSocketHTTPClient__connect(JSC__JSGlobalObject* arg0, void* arg1, void* arg2, const ZigString* arg3, uint16_t arg4, const ZigString* arg5, const ZigString* arg6, ZigString* arg7, ZigString* arg8, size_t arg9); +ZIG_DECL WebSocketHTTPClient* Bun__WebSocketHTTPClient__connect(JSC__JSGlobalObject* arg0, void* arg1, CppWebSocket* arg2, const ZigString* arg3, uint16_t arg4, const ZigString* arg5, const ZigString* arg6, ZigString* arg7, ZigString* arg8, size_t arg9); ZIG_DECL void Bun__WebSocketHTTPClient__register(JSC__JSGlobalObject* arg0, void* arg1, void* arg2); #endif @@ -716,7 +716,7 @@ ZIG_DECL void Bun__WebSocketHTTPClient__register(JSC__JSGlobalObject* arg0, void #ifdef __cplusplus ZIG_DECL void Bun__WebSocketHTTPSClient__cancel(WebSocketHTTPSClient* arg0); -ZIG_DECL WebSocketHTTPSClient* Bun__WebSocketHTTPSClient__connect(JSC__JSGlobalObject* arg0, void* arg1, void* arg2, const ZigString* arg3, uint16_t arg4, const ZigString* arg5, const ZigString* arg6, ZigString* arg7, ZigString* arg8, size_t arg9); +ZIG_DECL WebSocketHTTPSClient* Bun__WebSocketHTTPSClient__connect(JSC__JSGlobalObject* arg0, void* arg1, CppWebSocket* arg2, const ZigString* arg3, uint16_t arg4, const ZigString* arg5, const ZigString* arg6, ZigString* arg7, ZigString* arg8, size_t arg9); ZIG_DECL void Bun__WebSocketHTTPSClient__register(JSC__JSGlobalObject* arg0, void* arg1, void* arg2); #endif @@ -725,7 +725,7 @@ ZIG_DECL void Bun__WebSocketHTTPSClient__register(JSC__JSGlobalObject* arg0, voi ZIG_DECL void Bun__WebSocketClient__close(WebSocketClient* arg0, uint16_t arg1, const ZigString* arg2); ZIG_DECL void Bun__WebSocketClient__finalize(WebSocketClient* arg0); -ZIG_DECL void* Bun__WebSocketClient__init(void* arg0, void* arg1, void* arg2, JSC__JSGlobalObject* arg3, unsigned char* arg4, size_t arg5); +ZIG_DECL void* Bun__WebSocketClient__init(CppWebSocket* arg0, void* arg1, void* arg2, JSC__JSGlobalObject* arg3, unsigned char* arg4, size_t arg5); ZIG_DECL void Bun__WebSocketClient__register(JSC__JSGlobalObject* arg0, void* arg1, void* arg2); ZIG_DECL void Bun__WebSocketClient__writeBinaryData(WebSocketClient* arg0, const unsigned char* arg1, size_t arg2); ZIG_DECL void Bun__WebSocketClient__writeString(WebSocketClient* arg0, const ZigString* arg1); @@ -736,7 +736,7 @@ ZIG_DECL void Bun__WebSocketClient__writeString(WebSocketClient* arg0, const Zig ZIG_DECL void Bun__WebSocketClientTLS__close(WebSocketClientTLS* arg0, uint16_t arg1, const ZigString* arg2); ZIG_DECL void Bun__WebSocketClientTLS__finalize(WebSocketClientTLS* arg0); -ZIG_DECL void* Bun__WebSocketClientTLS__init(void* arg0, void* arg1, void* arg2, JSC__JSGlobalObject* arg3, unsigned char* arg4, size_t arg5); +ZIG_DECL void* Bun__WebSocketClientTLS__init(CppWebSocket* arg0, void* arg1, void* arg2, JSC__JSGlobalObject* arg3, unsigned char* arg4, size_t arg5); ZIG_DECL void Bun__WebSocketClientTLS__register(JSC__JSGlobalObject* arg0, void* arg1, void* arg2); ZIG_DECL void Bun__WebSocketClientTLS__writeBinaryData(WebSocketClientTLS* arg0, const unsigned char* arg1, size_t arg2); ZIG_DECL void Bun__WebSocketClientTLS__writeString(WebSocketClientTLS* arg0, const ZigString* arg1); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index 3f943ab27bc6ee..b1f1a197470901 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -231,7 +231,7 @@ pub extern fn JSC__JSValue__getClassName(JSValue0: JSC__JSValue, arg1: *bindings pub extern fn JSC__JSValue__getErrorsProperty(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject) JSC__JSValue; pub extern fn JSC__JSValue__getIfPropertyExistsFromPath(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, JSValue2: JSC__JSValue) JSC__JSValue; pub extern fn JSC__JSValue__getIfPropertyExistsImpl(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: [*c]const u8, arg3: u32) JSC__JSValue; -pub extern fn JSC__JSValue__getLengthOfArray(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject) u64; +pub extern fn JSC__JSValue__getLengthIfPropertyExistsInternal(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject) f64; pub extern fn JSC__JSValue__getNameProperty(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: [*c]ZigString) void; pub extern fn JSC__JSValue__getPrototype(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject) JSC__JSValue; pub extern fn JSC__JSValue__getSymbolDescription(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: [*c]ZigString) void; diff --git a/src/bun.js/bindings/webcore/JSDOMFormData.cpp b/src/bun.js/bindings/webcore/JSDOMFormData.cpp index e221288c175133..181b20e45be746 100644 --- a/src/bun.js/bindings/webcore/JSDOMFormData.cpp +++ b/src/bun.js/bindings/webcore/JSDOMFormData.cpp @@ -108,6 +108,18 @@ static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_toJSON); static JSC_DECLARE_CUSTOM_GETTER(jsDOMFormDataConstructor); +JSC_DEFINE_CUSTOM_GETTER(jsDOMFormDataPrototype_getLength, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + auto throwScope = DECLARE_THROW_SCOPE(vm); + if (UNLIKELY(!thisObject)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + + size_t length = thisObject->wrapped().count(); + return JSValue::encode(jsNumber(length)); +} + class JSDOMFormDataPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; @@ -196,6 +208,7 @@ static const HashTableValue JSDOMFormDataPrototypeTableValues[] = { { "values"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_values, 0 } }, { "forEach"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_forEach, 1 } }, { "toJSON"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_toJSON, 1 } }, + { "length"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsDOMFormDataPrototype_getLength, 0 } } }; const ClassInfo JSDOMFormDataPrototype::s_info = { "FormData"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFormDataPrototype) }; diff --git a/src/bun.js/bindings/webcore/JSFetchHeaders.h b/src/bun.js/bindings/webcore/JSFetchHeaders.h index 74fe246016802c..772adc546998b2 100644 --- a/src/bun.js/bindings/webcore/JSFetchHeaders.h +++ b/src/bun.js/bindings/webcore/JSFetchHeaders.h @@ -45,7 +45,7 @@ class JSFetchHeaders : public JSDOMWrapper { static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), JSC::NonArray); + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::JSType(JSDOMWrapperType), StructureFlags), info(), JSC::NonArray); } static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*); diff --git a/src/bun.js/bindings/webcore/JSURLSearchParams.cpp b/src/bun.js/bindings/webcore/JSURLSearchParams.cpp index 5d3ed056aa22ae..d803046840f189 100644 --- a/src/bun.js/bindings/webcore/JSURLSearchParams.cpp +++ b/src/bun.js/bindings/webcore/JSURLSearchParams.cpp @@ -152,6 +152,16 @@ template<> void JSURLSearchParamsDOMConstructor::initializeProperties(VM& vm, JS putDirect(vm, vm.propertyNames->prototype, JSURLSearchParams::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete); } +JSC_DEFINE_CUSTOM_GETTER(jsURLSearchParamsPrototype_getLength, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + return JSValue::encode(jsNumber(thisObject->wrapped().size())); +} + /* Hash table for prototype */ static const HashTableValue JSURLSearchParamsPrototypeTableValues[] = { @@ -168,6 +178,7 @@ static const HashTableValue JSURLSearchParamsPrototypeTableValues[] = { { "values"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLSearchParamsPrototypeFunction_values, 0 } }, { "forEach"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLSearchParamsPrototypeFunction_forEach, 1 } }, { "toString"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLSearchParamsPrototypeFunction_toString, 0 } }, + { "length"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsURLSearchParamsPrototype_getLength, 0 } }, }; const ClassInfo JSURLSearchParamsPrototype::s_info = { "URLSearchParams"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSURLSearchParamsPrototype) }; diff --git a/src/bun.js/bindings/webcore/WebSocket.cpp b/src/bun.js/bindings/webcore/WebSocket.cpp index 4af556a7ba0628..a93bb8726e23ec 100644 --- a/src/bun.js/bindings/webcore/WebSocket.cpp +++ b/src/bun.js/bindings/webcore/WebSocket.cpp @@ -169,19 +169,19 @@ WebSocket::~WebSocket() if (m_upgradeClient != nullptr) { void* upgradeClient = m_upgradeClient; if (m_isSecure) { - Bun__WebSocketHTTPSClient__cancel(upgradeClient); + Bun__WebSocketHTTPSClient__cancel(reinterpret_cast(upgradeClient)); } else { - Bun__WebSocketHTTPClient__cancel(upgradeClient); + Bun__WebSocketHTTPClient__cancel(reinterpret_cast(upgradeClient)); } } switch (m_connectedWebSocketKind) { case ConnectedWebSocketKind::Client: { - Bun__WebSocketClient__finalize(this->m_connectedWebSocket.client); + Bun__WebSocketClient__finalize(reinterpret_cast(this->m_connectedWebSocket.client)); break; } case ConnectedWebSocketKind::ClientSSL: { - Bun__WebSocketClientTLS__finalize(this->m_connectedWebSocket.clientSSL); + Bun__WebSocketClientTLS__finalize(reinterpret_cast(this->m_connectedWebSocket.clientSSL)); break; } // case ConnectedWebSocketKind::Server: { @@ -409,11 +409,11 @@ ExceptionOr WebSocket::connect(const String& url, const Vector& pr if (is_secure) { us_socket_context_t* ctx = scriptExecutionContext()->webSocketContext(); RELEASE_ASSERT(ctx); - this->m_upgradeClient = Bun__WebSocketHTTPSClient__connect(scriptExecutionContext()->jsGlobalObject(), ctx, this, &host, port, &path, &clientProtocolString, headerNames.data(), headerValues.data(), headerNames.size()); + this->m_upgradeClient = Bun__WebSocketHTTPSClient__connect(scriptExecutionContext()->jsGlobalObject(), ctx, reinterpret_cast(this), &host, port, &path, &clientProtocolString, headerNames.data(), headerValues.data(), headerNames.size()); } else { us_socket_context_t* ctx = scriptExecutionContext()->webSocketContext(); RELEASE_ASSERT(ctx); - this->m_upgradeClient = Bun__WebSocketHTTPClient__connect(scriptExecutionContext()->jsGlobalObject(), ctx, this, &host, port, &path, &clientProtocolString, headerNames.data(), headerValues.data(), headerNames.size()); + this->m_upgradeClient = Bun__WebSocketHTTPClient__connect(scriptExecutionContext()->jsGlobalObject(), ctx, reinterpret_cast(this), &host, port, &path, &clientProtocolString, headerNames.data(), headerValues.data(), headerNames.size()); } headerValues.clear(); @@ -1022,11 +1022,11 @@ void WebSocket::didConnect(us_socket_t* socket, char* bufferedData, size_t buffe this->m_upgradeClient = nullptr; if (m_isSecure) { us_socket_context_t* ctx = (us_socket_context_t*)this->scriptExecutionContext()->connectedWebSocketContext(); - this->m_connectedWebSocket.clientSSL = Bun__WebSocketClientTLS__init(this, socket, ctx, this->scriptExecutionContext()->jsGlobalObject(), reinterpret_cast(bufferedData), bufferedDataSize); + this->m_connectedWebSocket.clientSSL = Bun__WebSocketClientTLS__init(reinterpret_cast(this), socket, ctx, this->scriptExecutionContext()->jsGlobalObject(), reinterpret_cast(bufferedData), bufferedDataSize); this->m_connectedWebSocketKind = ConnectedWebSocketKind::ClientSSL; } else { us_socket_context_t* ctx = (us_socket_context_t*)this->scriptExecutionContext()->connectedWebSocketContext(); - this->m_connectedWebSocket.client = Bun__WebSocketClient__init(this, socket, ctx, this->scriptExecutionContext()->jsGlobalObject(), reinterpret_cast(bufferedData), bufferedDataSize); + this->m_connectedWebSocket.client = Bun__WebSocketClient__init(reinterpret_cast(this), socket, ctx, this->scriptExecutionContext()->jsGlobalObject(), reinterpret_cast(bufferedData), bufferedDataSize); this->m_connectedWebSocketKind = ConnectedWebSocketKind::Client; } diff --git a/src/bun.js/node/node_os.zig b/src/bun.js/node/node_os.zig index 7b292ae361149c..4b37640cf5a6cc 100644 --- a/src/bun.js/node/node_os.zig +++ b/src/bun.js/node/node_os.zig @@ -531,7 +531,7 @@ pub const Os = struct { // Does this entry already exist? if (ret.get(globalThis, interface_name)) |array| { // Add this interface entry to the existing array - const next_index = @intCast(u32, array.getLengthOfArray(globalThis)); + const next_index = @intCast(u32, array.getLength(globalThis)); array.putIndex(globalThis, next_index, interface); } else { // Add it as an array with this interface as an element diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index e22c2a0fd4d203..ad9cf9b5be0628 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -28,12 +28,9 @@ const default_allocator = @import("root").bun.default_allocator; const FeatureFlags = @import("root").bun.FeatureFlags; const ArrayBuffer = @import("../base.zig").ArrayBuffer; const Properties = @import("../base.zig").Properties; -const NewClass = @import("../base.zig").NewClass; const d = @import("../base.zig").d; const castObj = @import("../base.zig").castObj; const getAllocator = @import("../base.zig").getAllocator; -const JSPrivateDataPtr = @import("../base.zig").JSPrivateDataPtr; -const GetJSPrivateData = @import("../base.zig").GetJSPrivateData; const ZigString = JSC.ZigString; const JSInternalPromise = JSC.JSInternalPromise; @@ -49,22 +46,6 @@ const Task = @import("../javascript.zig").Task; const Fs = @import("../../fs.zig"); const is_bindgen: bool = std.meta.globalOption("bindgen", bool) orelse false; -fn notImplementedFn(_: *anyopaque, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, _: []const js.JSValueRef, exception: js.ExceptionRef) js.JSValueRef { - JSError(getAllocator(ctx), "Not implemented yet!", .{}, ctx, exception); - return null; -} - -fn notImplementedProp( - _: anytype, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - exception: js.ExceptionRef, -) js.JSValueRef { - JSError(getAllocator(ctx), "Property not implemented yet!", .{}, ctx, exception); - return null; -} - pub const DiffFormatter = struct { received_string: ?string = null, expected_string: ?string = null, @@ -837,7 +818,7 @@ pub const Jest = struct { return .zero; } - if (function.getLengthOfArray(globalThis) > 0) { + if (function.getLength(globalThis) > 0) { globalThis.throw("done() callback is not implemented in global hooks yet. Please make your function take no arguments", .{}); return .zero; } @@ -852,6 +833,118 @@ pub const Jest = struct { }.appendGlobalFunctionCallback; } + pub fn Bun__Jest__createTestPreloadObject(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { + JSC.markBinding(@src()); + + var global_hooks_object = JSC.JSValue.createEmptyObject(globalObject, 8); + global_hooks_object.ensureStillAlive(); + + const notSupportedHereFn = struct { + pub fn notSupportedHere( + globalThis: *JSC.JSGlobalObject, + _: *JSC.CallFrame, + ) callconv(.C) JSValue { + globalThis.throw("This function can only be used in a test.", .{}); + return .zero; + } + }.notSupportedHere; + const notSupportedHere = JSC.NewFunction(globalObject, null, 0, notSupportedHereFn, false); + notSupportedHere.ensureStillAlive(); + + inline for (.{ + "expect", + "describe", + "it", + "test", + }) |name| { + global_hooks_object.put(globalObject, ZigString.static(name), notSupportedHere); + } + + inline for (.{ "beforeAll", "beforeEach", "afterAll", "afterEach" }) |name| { + const function = JSC.NewFunction(globalObject, null, 1, globalHook(name), false); + function.ensureStillAlive(); + global_hooks_object.put(globalObject, ZigString.static(name), function); + } + return global_hooks_object; + } + + pub fn Bun__Jest__createTestModuleObject(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { + JSC.markBinding(@src()); + + const module = JSC.JSValue.createEmptyObject(globalObject, 7); + + const test_fn = JSC.NewFunction(globalObject, ZigString.static("test"), 2, TestScope.call, false); + module.put( + globalObject, + ZigString.static("test"), + test_fn, + ); + test_fn.put( + globalObject, + ZigString.static("todo"), + JSC.NewFunction(globalObject, ZigString.static("todo"), 2, TestScope.todo, false), + ); + test_fn.put( + globalObject, + ZigString.static("skip"), + JSC.NewFunction(globalObject, ZigString.static("skip"), 2, TestScope.skip, false), + ); + test_fn.put( + globalObject, + ZigString.static("only"), + JSC.NewFunction(globalObject, ZigString.static("only"), 2, TestScope.only, false), + ); + + module.put( + globalObject, + ZigString.static("it"), + test_fn, + ); + const describe = JSC.NewFunction(globalObject, ZigString.static("describe"), 2, DescribeScope.describe, false); + describe.put( + globalObject, + ZigString.static("skip"), + JSC.NewFunction(globalObject, ZigString.static("skip"), 2, DescribeScope.skip, false), + ); + + module.put( + globalObject, + ZigString.static("describe"), + describe, + ); + + module.put( + globalObject, + ZigString.static("beforeAll"), + JSC.NewRuntimeFunction(globalObject, ZigString.static("beforeAll"), 1, DescribeScope.beforeAll, false), + ); + module.put( + globalObject, + ZigString.static("beforeEach"), + JSC.NewRuntimeFunction(globalObject, ZigString.static("beforeEach"), 1, DescribeScope.beforeEach, false), + ); + module.put( + globalObject, + ZigString.static("afterAll"), + JSC.NewRuntimeFunction(globalObject, ZigString.static("afterAll"), 1, DescribeScope.afterAll, false), + ); + module.put( + globalObject, + ZigString.static("afterEach"), + JSC.NewRuntimeFunction(globalObject, ZigString.static("afterEach"), 1, DescribeScope.afterEach, false), + ); + module.put( + globalObject, + ZigString.static("expect"), + Expect.getConstructor(globalObject), + ); + + return module; + } + + extern fn Bun__Jest__testPreloadObject(*JSC.JSGlobalObject) JSC.JSValue; + extern fn Bun__Jest__testModuleObject(*JSC.JSGlobalObject) JSC.JSValue; + pub fn call( _: void, ctx: js.JSContextRef, @@ -860,6 +953,7 @@ pub const Jest = struct { arguments_: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSValueRef { + JSC.markBinding(@src()); var runner_ = runner orelse { JSError(getAllocator(ctx), "Run \"bun test\" to run a test", .{}, ctx, exception); return js.JSValueMakeUndefined(ctx); @@ -880,36 +974,7 @@ pub const Jest = struct { } var vm = ctx.bunVM(); if (vm.is_in_preload) { - var global_hooks_object = JSC.JSValue.createEmptyObject(ctx, 8); - global_hooks_object.ensureStillAlive(); - - const notSupportedHereFn = struct { - pub fn notSupportedHere( - globalThis: *JSC.JSGlobalObject, - _: *JSC.CallFrame, - ) callconv(.C) JSValue { - globalThis.throw("This function can only be used in a test.", .{}); - return .zero; - } - }.notSupportedHere; - const notSupportedHere = JSC.NewFunction(ctx, null, 0, notSupportedHereFn, false); - notSupportedHere.ensureStillAlive(); - - inline for (.{ - "expect", - "describe", - "it", - "test", - }) |name| { - global_hooks_object.put(ctx, ZigString.static(name), notSupportedHere); - } - - inline for (.{ "beforeAll", "beforeEach", "afterAll", "afterEach" }) |name| { - const function = JSC.NewFunction(ctx, null, 1, globalHook(name), false); - function.ensureStillAlive(); - global_hooks_object.put(ctx, ZigString.static(name), function); - } - return global_hooks_object.asObjectRef(); + return Bun__Jest__testPreloadObject(ctx).asObjectRef(); } var filepath = Fs.FileSystem.instance.filename_store.append([]const u8, slice) catch unreachable; @@ -917,7 +982,15 @@ pub const Jest = struct { var scope = runner_.getOrPutFile(filepath); DescribeScope.active = scope; DescribeScope.module = scope; - return DescribeScope.Class.make(ctx, scope); + + return Bun__Jest__testModuleObject(ctx).asObjectRef(); + } + + comptime { + if (!JSC.is_bindgen) { + @export(Bun__Jest__createTestModuleObject, .{ .name = "Bun__Jest__createTestModuleObject" }); + @export(Bun__Jest__createTestPreloadObject, .{ .name = "Bun__Jest__createTestPreloadObject" }); + } } }; @@ -1229,27 +1302,19 @@ pub const Expect = struct { const not = this.op.contains(.not); var pass = false; - var actual_length: f64 = undefined; - if (value.jsType() == .String) { - actual_length = @intToFloat(f64, value.asString().length()); - if (actual_length == expected_length) pass = true; - } else { - const length_value: JSValue = value.getIfPropertyExistsImpl(globalObject, "length", "length".len); + const actual_length = value.getLengthIfPropertyExistsInternal(globalObject); - if (length_value.isEmpty()) { - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; - globalObject.throw("Received value does not have a length property: {any}", .{value.toFmt(globalObject, &fmt)}); - return .zero; - } else if (!length_value.isNumber()) { - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; - globalObject.throw("Received value has non-number length property: {any}", .{length_value.toFmt(globalObject, &fmt)}); - return .zero; - } + if (actual_length == std.math.f64_max) { + var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; + globalObject.throw("Received value does not have a length property: {any}", .{value.toFmt(globalObject, &fmt)}); + return .zero; + } else if (std.math.isNan(actual_length)) { + globalObject.throw("Received value has non-number length property: {}", .{actual_length}); + return .zero; + } - actual_length = length_value.asNumber(); - if (@round(actual_length) == actual_length) { - if (actual_length == expected_length) pass = true; - } + if (actual_length == expected_length) { + pass = true; } if (not) pass = !pass; @@ -3232,7 +3297,7 @@ pub const Expect = struct { pub const TestScope = struct { label: string = "", parent: *DescribeScope, - callback: js.JSValueRef, + callback: JSC.JSValue, id: TestRunner.Test.ID = 0, promise: ?*JSInternalPromise = null, ran: bool = false, @@ -3242,81 +3307,59 @@ pub const TestScope = struct { snapshot_count: usize = 0, timeout_millis: u32 = 0, - pub const Class = NewClass( - void, - .{ .name = "test" }, - .{ - .call = call, - .only = only, - .skip = skip, - .todo = todo, - }, - .{}, - ); - pub const Counter = struct { expected: u32 = 0, actual: u32 = 0, }; pub fn only( - // the DescribeScope here is the top of the file, not the real one - _: void, - ctx: js.JSContextRef, - this: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return prepare(this, ctx, arguments, exception, .only); + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const thisValue = callframe.this(); + const args = callframe.arguments(3); + prepare(globalThis, args.ptr[0..args.len], .only); + return thisValue; } pub fn skip( - // the DescribeScope here is the top of the file, not the real one - _: void, - ctx: js.JSContextRef, - this: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return prepare(this, ctx, arguments, exception, .skip); + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const thisValue = callframe.this(); + const args = callframe.arguments(3); + prepare(globalThis, args.ptr[0..args.len], .skip); + return thisValue; } pub fn call( - // the DescribeScope here is the top of the file, not the real one - _: void, - ctx: js.JSContextRef, - this: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return prepare(this, ctx, arguments, exception, .call); + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const thisValue = callframe.this(); + const args = callframe.arguments(3); + prepare(globalThis, args.ptr[0..args.len], .call); + return thisValue; } pub fn todo( - _: void, - ctx: js.JSContextRef, - this: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return prepare(this, ctx, arguments, exception, .todo); + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const thisValue = callframe.this(); + const args = callframe.arguments(3); + prepare(globalThis, args.ptr[0..args.len], .todo); + return thisValue; } - fn prepare( - this: js.JSObjectRef, - ctx: js.JSContextRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, + inline fn prepare( + globalThis: *JSC.JSGlobalObject, + args: []const JSC.JSValue, comptime tag: @Type(.EnumLiteral), - ) js.JSObjectRef { - var args = bun.cast([]const JSC.JSValue, arguments[0..@min(arguments.len, 3)]); + ) void { var label: string = ""; if (args.len == 0) { - return this; + return; } var label_value = args[0]; @@ -3328,21 +3371,21 @@ pub const TestScope = struct { } if (label_value != .zero) { - const allocator = getAllocator(ctx); - label = (label_value.toSlice(ctx, allocator).cloneIfNeeded(allocator) catch unreachable).slice(); + const allocator = getAllocator(globalThis); + label = (label_value.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch unreachable).slice(); } if (tag == .todo and label_value == .zero) { - JSError(getAllocator(ctx), "test.todo() requires a description", .{}, ctx, exception); - return this; + globalThis.throw("test.todo() requires a description", .{}); + return; } const function = function_value; - if (function.isEmptyOrUndefinedOrNull() or !function.isCell() or !function.isCallable(ctx.vm())) { + if (function.isEmptyOrUndefinedOrNull() or !function.isCell() or !function.isCallable(globalThis.vm())) { // a callback is not required for .todo if (tag != .todo) { - JSError(getAllocator(ctx), "test() expects a function", .{}, ctx, exception); - return this; + globalThis.throw("test() expects a function", .{}); + return; } } @@ -3351,35 +3394,37 @@ pub const TestScope = struct { } if (tag == .todo) { + if (function != .zero) + function.protect(); DescribeScope.active.todo_counter += 1; - DescribeScope.active.tests.append(getAllocator(ctx), TestScope{ + DescribeScope.active.tests.append(getAllocator(globalThis), TestScope{ .label = label, .parent = DescribeScope.active, .is_todo = true, - .callback = if (function == .zero) null else function.asObjectRef(), + .callback = function, }) catch unreachable; - return this; + return; } if (tag == .skip or (tag != .only and Jest.runner.?.only)) { DescribeScope.active.skipped_counter += 1; - DescribeScope.active.tests.append(getAllocator(ctx), TestScope{ + DescribeScope.active.tests.append(getAllocator(globalThis), TestScope{ .label = label, .parent = DescribeScope.active, .skipped = true, - .callback = null, + .callback = .zero, }) catch unreachable; - return this; + return; } - js.JSValueProtect(ctx, function.asObjectRef()); + function.protect(); - DescribeScope.active.tests.append(getAllocator(ctx), TestScope{ + DescribeScope.active.tests.append(getAllocator(globalThis), TestScope{ .label = label, - .callback = function.asObjectRef(), + .callback = function, .parent = DescribeScope.active, - .timeout_millis = if (arguments.len > 2) @intCast(u32, @max(args[2].coerce(i32, ctx), 0)) else Jest.runner.?.default_timeout_ms, + .timeout_millis = if (args.len > 2) @intCast(u32, @max(args[2].coerce(i32, globalThis), 0)) else Jest.runner.?.default_timeout_ms, }) catch unreachable; if (test_elapsed_timer == null) create_tiemr: { @@ -3387,8 +3432,6 @@ pub const TestScope = struct { timer.* = std.time.Timer.start() catch break :create_tiemr; test_elapsed_timer = timer; } - - return this; } pub fn onReject(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { @@ -3442,16 +3485,16 @@ pub const TestScope = struct { ) Result { if (comptime is_bindgen) return undefined; var vm = VirtualMachine.get(); - var callback = this.callback; + const callback = this.callback; Jest.runner.?.did_pending_test_fail = false; defer { - js.JSValueUnprotect(vm.global, callback); - this.callback = null; + callback.unprotect(); + this.callback = .zero; vm.autoGarbageCollect(); } JSC.markBinding(@src()); - const callback_length = JSValue.fromRef(callback).getLengthOfArray(vm.global); + const callback_length = callback.getLength(vm.global); var initial_value = JSValue.zero; if (test_elapsed_timer) |timer| { @@ -3474,9 +3517,9 @@ pub const TestScope = struct { task, ); task.done_callback_state = .pending; - initial_value = JSValue.fromRef(callback).call(vm.global, &.{callback_func}); + initial_value = callback.call(vm.global, &.{callback_func}); } else { - initial_value = js.JSObjectCallAsFunctionReturnValue(vm.global, callback, null, 0, null); + initial_value = callback.call(vm.global, &.{}); } if (initial_value.isAnyError()) { @@ -3539,8 +3582,6 @@ pub const TestScope = struct { return .{ .pending = {} }; } - this.callback = null; - if (active_test_expectation_counter.expected > 0 and active_test_expectation_counter.expected < active_test_expectation_counter.actual) { Output.prettyErrorln("Test fail: {d} / {d} expectations\n (make this better!)", .{ active_test_expectation_counter.actual, @@ -3614,73 +3655,36 @@ pub const DescribeScope = struct { afterAll, }; - pub const TestEntry = struct { - label: string, - callback: js.JSValueRef, - - pub const List = std.MultiArrayList(TestEntry); - }; - pub threadlocal var active: *DescribeScope = undefined; pub threadlocal var module: *DescribeScope = undefined; const CallbackFn = *const fn ( - void, - js.JSContextRef, - js.JSObjectRef, - js.JSObjectRef, - []const js.JSValueRef, - js.ExceptionRef, - ) js.JSObjectRef; + *JSC.JSGlobalObject, + *JSC.CallFrame, + ) callconv(.C) JSC.JSValue; fn createCallback(comptime hook: LifecycleHook) CallbackFn { return struct { const this_hook = hook; pub fn run( - _: void, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - if (arguments.len == 0 or !JSC.JSValue.c(arguments[0]).isObject() or !JSC.JSValue.c(arguments[0]).isCallable(ctx.vm())) { - JSC.throwInvalidArguments("Expected callback", .{}, ctx, exception); - return null; + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const arguments_ = callframe.arguments(2); + const arguments = arguments_.ptr[0..arguments_.len]; + if (arguments.len == 0 or !arguments[0].isObject() or !arguments[0].isCallable(globalThis.vm())) { + globalThis.throwInvalidArgumentType(@tagName(this_hook), "callback", "function"); + return .zero; } - JSC.JSValue.c(arguments[0]).protect(); + arguments[0].protect(); const name = comptime @as(string, @tagName(this_hook)); - @field(DescribeScope.active, name).append(getAllocator(ctx), JSC.JSValue.c(arguments[0])) catch unreachable; - return JSC.JSValue.jsBoolean(true).asObjectRef(); + @field(DescribeScope.active, name).append(getAllocator(globalThis), arguments[0]) catch unreachable; + return JSC.JSValue.jsBoolean(true); } }.run; } - pub const Class = NewClass( - DescribeScope, - .{ - .name = "describe", - .read_only = true, - }, - .{ - .call = describe, - .afterAll = .{ .rfn = createCallback(.afterAll), .name = "afterAll" }, - .afterEach = .{ .rfn = createCallback(.afterEach), .name = "afterEach" }, - .beforeAll = .{ .rfn = createCallback(.beforeAll), .name = "beforeAll" }, - .beforeEach = .{ .rfn = createCallback(.beforeEach), .name = "beforeEach" }, - .skip = skip, - }, - .{ - .expect = .{ .get = createExpect, .name = "expect" }, - // kind of a mindfuck but - // describe("foo", () => {}).describe("bar") will wrok - .describe = .{ .get = createDescribe, .name = "describe" }, - .it = .{ .get = createTest, .name = "it" }, - .@"test" = .{ .get = createTest, .name = "test" }, - }, - ); - pub fn onDone( ctx: js.JSContextRef, callframe: *JSC.CallFrame, @@ -3704,7 +3708,12 @@ pub const DescribeScope = struct { return JSValue.jsUndefined(); } - pub fn execCallback(this: *DescribeScope, ctx: js.JSContextRef, comptime hook: LifecycleHook) JSValue { + pub const afterAll = createCallback(.afterAll); + pub const afterEach = createCallback(.afterEach); + pub const beforeAll = createCallback(.beforeAll); + pub const beforeEach = createCallback(.beforeEach); + + pub fn execCallback(this: *DescribeScope, globalObject: *JSC.JSGlobalObject, comptime hook: LifecycleHook) JSValue { const name = comptime @as(string, @tagName(hook)); var hooks: []JSC.JSValue = @field(this, name).items; for (hooks, 0..) |cb, i| { @@ -3718,28 +3727,28 @@ pub const DescribeScope = struct { Jest.runner.?.did_pending_test_fail = false; const vm = VirtualMachine.get(); - var result: JSC.JSValue = if (cb.getLengthOfArray(ctx) > 0) brk: { + var result: JSC.JSValue = if (cb.getLength(globalObject) > 0) brk: { this.done = false; const done_func = JSC.NewFunctionWithData( - ctx, + globalObject, ZigString.static("done"), 0, DescribeScope.onDone, false, this, ); - var result = cb.call(ctx, &.{done_func}); + var result = cb.call(globalObject, &.{done_func}); vm.waitFor(&this.done); break :brk result; - } else cb.call(ctx, &.{}); + } else cb.call(globalObject, &.{}); if (result.asAnyPromise()) |promise| { - if (promise.status(ctx.vm()) == .Pending) { + if (promise.status(globalObject.vm()) == .Pending) { result.protect(); vm.waitForPromise(promise); result.unprotect(); } - result = promise.result(ctx.vm()); + result = promise.result(globalObject.vm()); } Jest.runner.?.pending_test = pending_test; @@ -3791,71 +3800,64 @@ pub const DescribeScope = struct { return null; } - pub fn runCallback(this: *DescribeScope, ctx: js.JSContextRef, comptime hook: LifecycleHook) JSValue { - if (runGlobalCallbacks(ctx, hook)) |err| { + pub fn runCallback(this: *DescribeScope, globalObject: *JSC.JSGlobalObject, comptime hook: LifecycleHook) JSValue { + if (runGlobalCallbacks(globalObject, hook)) |err| { return err; } var parent = this.parent; while (parent) |scope| { - const ret = scope.execCallback(ctx, hook); + const ret = scope.execCallback(globalObject, hook); if (!ret.isEmpty()) { return ret; } parent = scope.parent; } - return this.execCallback(ctx, hook); + return this.execCallback(globalObject, hook); } pub fn skip( - this: *DescribeScope, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return this.runDescribe(ctx, null, null, arguments, exception, true); + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const arguments = callframe.arguments(3); + var this: *DescribeScope = DescribeScope.module; + return runDescribe(this, globalThis, arguments.ptr[0..arguments.len], true); } pub fn describe( - this: *DescribeScope, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return runDescribe(this, ctx, null, null, arguments, exception, false); + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const arguments = callframe.arguments(3); + var this: *DescribeScope = DescribeScope.module; + return runDescribe(this, globalThis, arguments.ptr[0..arguments.len], false); } fn runDescribe( this: *DescribeScope, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, + globalThis: *JSC.JSGlobalObject, + arguments: []const JSC.JSValue, skipped: bool, - ) js.JSObjectRef { + ) JSC.JSValue { if (arguments.len == 0 or arguments.len > 2) { - JSError(getAllocator(ctx), "describe() requires 1-2 arguments", .{}, ctx, exception); - return js.JSValueMakeUndefined(ctx); + globalThis.throwNotEnoughArguments("describe", 2, arguments.len); + return .zero; } var label = ZigString.init(""); var args = arguments; - const allocator = getAllocator(ctx); + const allocator = getAllocator(globalThis); - if (js.JSValueIsString(ctx, arguments[0])) { - JSC.JSValue.fromRef(arguments[0]).toZigString(&label, ctx.ptr()); + if (arguments[0].isString()) { + arguments[0].toZigString(&label, globalThis); args = args[1..]; } - if (args.len == 0 or !js.JSObjectIsFunction(ctx, args[0])) { - JSError(allocator, "describe() requires a callback function", .{}, ctx, exception); - return js.JSValueMakeUndefined(ctx); + if (args.len == 0 or !args[0].isCallable(globalThis.vm())) { + globalThis.throwInvalidArgumentType("describe", "callback", "function"); + return .zero; } var callback = args[0]; @@ -3867,15 +3869,14 @@ pub const DescribeScope = struct { .file_id = this.file_id, .skipped = skipped or active.skipped, }; - var new_this = DescribeScope.Class.make(ctx, scope); - return scope.run(new_this, ctx, callback, exception); + return scope.run(globalThis, callback); } - pub fn run(this: *DescribeScope, thisObject: js.JSObjectRef, ctx: js.JSContextRef, callback: js.JSObjectRef, _: js.ExceptionRef) js.JSObjectRef { + pub fn run(this: *DescribeScope, globalObject: *JSC.JSGlobalObject, callback: JSC.JSValue) JSC.JSValue { if (comptime is_bindgen) return undefined; - js.JSValueProtect(ctx, callback); - defer js.JSValueUnprotect(ctx, callback); + callback.protect(); + defer callback.unprotect(); var original_active = active; defer active = original_active; if (this != module) @@ -3884,34 +3885,34 @@ pub const DescribeScope = struct { { JSC.markBinding(@src()); - ctx.clearTerminationException(); - var result = js.JSObjectCallAsFunctionReturnValue(ctx, callback, thisObject, 0, null); + globalObject.clearTerminationException(); + var result = callback.call(globalObject, &.{}); if (result.asAnyPromise()) |prom| { - ctx.bunVM().waitForPromise(prom); - switch (prom.status(ctx.ptr().vm())) { + globalObject.bunVM().waitForPromise(prom); + switch (prom.status(globalObject.ptr().vm())) { JSPromise.Status.Fulfilled => {}, else => { - ctx.bunVM().runErrorHandlerWithDedupe(prom.result(ctx.ptr().vm()), null); - return JSC.JSValue.jsUndefined().asObjectRef(); + globalObject.bunVM().runErrorHandlerWithDedupe(prom.result(globalObject.ptr().vm()), null); + return .undefined; }, } } else if (result.toError()) |err| { - ctx.bunVM().runErrorHandlerWithDedupe(err, null); - return JSC.JSValue.jsUndefined().asObjectRef(); + globalObject.bunVM().runErrorHandlerWithDedupe(err, null); + return .undefined; } } - this.runTests(thisObject.?.value(), ctx); - return js.JSValueMakeUndefined(ctx); + this.runTests(globalObject); + return .undefined; } - pub fn runTests(this: *DescribeScope, this_object: JSC.JSValue, ctx: js.JSContextRef) void { + pub fn runTests(this: *DescribeScope, globalObject: *JSC.JSGlobalObject) void { // Step 1. Initialize the test block - ctx.clearTerminationException(); + globalObject.clearTerminationException(); const file = this.file_id; - const allocator = getAllocator(ctx); + const allocator = getAllocator(globalObject); var tests: []TestScope = this.tests.items; const end = @truncate(TestRunner.Test.ID, tests.len); this.pending_tests = std.DynamicBitSetUnmanaged.initFull(allocator, end) catch unreachable; @@ -3926,8 +3927,8 @@ pub const DescribeScope = struct { var i: TestRunner.Test.ID = 0; if (!this.isAllSkipped()) { - const beforeAll = this.runCallback(ctx, .beforeAll); - if (!beforeAll.isEmpty()) { + const beforeAllCallback = this.runCallback(globalObject, .beforeAll); + if (!beforeAllCallback.isEmpty()) { while (i < end) { Jest.runner.?.reportFailure(i + this.test_id_start, source.path.text, tests[i].label, 0, 0, this); i += 1; @@ -3943,11 +3944,10 @@ pub const DescribeScope = struct { runner.* = .{ .test_id = i, .describe = this, - .globalThis = ctx, + .globalThis = globalObject, .source_file_path = source.path.text, - .value = JSC.Strong.create(this_object, ctx), }; - runner.ref.ref(ctx.bunVM()); + runner.ref.ref(globalObject.bunVM()); Jest.runner.?.enqueue(runner); } @@ -3959,9 +3959,9 @@ pub const DescribeScope = struct { this.pending_tests.unset(test_id); if (!skipped) { - const afterEach = this.runCallback(globalThis, .afterEach); - if (!afterEach.isEmpty()) { - globalThis.bunVM().runErrorHandler(afterEach, null); + const afterEach_result = this.runCallback(globalThis, .afterEach); + if (!afterEach_result.isEmpty()) { + globalThis.bunVM().runErrorHandler(afterEach_result, null); } } @@ -3972,9 +3972,9 @@ pub const DescribeScope = struct { if (!this.isAllSkipped()) { // Run the afterAll callbacks, in reverse order // unless there were no tests for this scope - const afterAll = this.execCallback(globalThis, .afterAll); - if (!afterAll.isEmpty()) { - globalThis.bunVM().runErrorHandler(afterAll, null); + const afterAll_result = this.execCallback(globalThis, .afterAll); + if (!afterAll_result.isEmpty()) { + globalThis.bunVM().runErrorHandler(afterAll_result, null); } } @@ -3999,35 +3999,6 @@ pub const DescribeScope = struct { // // } // } - pub fn createExpect( - _: *DescribeScope, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSObjectRef { - return JSC.Jest.Expect.getConstructor(ctx).asObjectRef(); - } - - pub fn createTest( - _: *DescribeScope, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSObjectRef { - return js.JSObjectMake(ctx, TestScope.Class.get().*, null); - } - - pub fn createDescribe( - this: *DescribeScope, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSObjectRef { - return DescribeScope.Class.make(ctx, this); - } }; var active_test_expectation_counter: TestScope.Counter = undefined; @@ -4037,7 +4008,6 @@ pub const TestRunnerTask = struct { describe: *DescribeScope, globalThis: *JSC.JSGlobalObject, source_file_path: string = "", - value: JSC.Strong = .{}, needs_before_each: bool = true, ref: JSC.Ref = JSC.Ref.init(), @@ -4081,18 +4051,20 @@ pub const TestRunnerTask = struct { pub fn run(this: *TestRunnerTask) bool { var describe = this.describe; + var globalThis = this.globalThis; + var jsc_vm = globalThis.bunVM(); // reset the global state for each test // prior to the run DescribeScope.active = describe; active_test_expectation_counter = .{}; + jsc_vm.last_reported_error_for_dedupe = .zero; const test_id = this.test_id; var test_: TestScope = this.describe.tests.items[test_id]; describe.current_test_id = test_id; - var globalThis = this.globalThis; - if (!describe.skipped and test_.is_todo and test_.callback == null) { + if (!describe.skipped and test_.is_todo and test_.callback.isEmpty()) { this.processTestResult(globalThis, .{ .todo = {} }, test_, test_id, describe); this.deinit(); return false; @@ -4104,7 +4076,7 @@ pub const TestRunnerTask = struct { return false; } - globalThis.bunVM().onUnhandledRejectionCtx = this; + jsc_vm.onUnhandledRejectionCtx = this; if (this.needs_before_each) { this.needs_before_each = false; @@ -4114,7 +4086,7 @@ pub const TestRunnerTask = struct { if (!beforeEach.isEmpty()) { Jest.runner.?.reportFailure(test_id, this.source_file_path, label, 0, 0, this.describe); - globalThis.bunVM().runErrorHandler(beforeEach, null); + jsc_vm.runErrorHandler(beforeEach, null); return false; } } @@ -4129,7 +4101,6 @@ pub const TestRunnerTask = struct { if (result == .pending and this.sync_state == .pending and (this.done_callback_state == .pending or this.promise_state == .pending)) { this.sync_state = .fulfilled; - this.value.set(globalThis, this.describe.value); return true; } @@ -4254,7 +4225,6 @@ pub const TestRunnerTask = struct { } } - this.value.deinit(); this.ref.unref(vm); // there is a double free here involving async before/after callbacks diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index 400b0b4e1e819d..47a267b7f11502 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -1137,7 +1137,7 @@ pub const JestPrettyFormat = struct { writer.writeAll("[Function]"); }, .Array => { - const len = @truncate(u32, value.getLengthOfArray(this.globalThis)); + const len = @truncate(u32, value.getLength(this.globalThis)); if (len == 0) { writer.writeAll("[]"); this.addForNewLine(2); @@ -1614,7 +1614,7 @@ pub const JestPrettyFormat = struct { this.writeIndent(Writer, writer_) catch unreachable; }, .Array => { - const length = children.getLengthOfArray(this.globalThis); + const length = children.getLength(this.globalThis); if (length == 0) break :print_children; writer.writeAll(">\n"); diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 547dc73f15c8d9..591150e12105a1 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -593,7 +593,7 @@ pub const Blob = struct { var needs_async = false; if (data.isString()) { - const len = data.getLengthOfArray(ctx); + const len = data.getLength(ctx); if (len < 256 * 1024 or bun.isMissingIOUring()) { const str = data.getZigString(ctx); @@ -2596,6 +2596,35 @@ pub const Blob = struct { return JSValue.jsNumber(init_timestamp); } + pub fn getSizeForBindings(this: *Blob) u64 { + if (this.size == Blob.max_size) { + this.resolveSize(); + } + + // If the file doesn't exist or is not seekable + // signal that the size is unknown. + if (this.store != null and this.store.?.data == .file and + !(this.store.?.data.file.seekable orelse false)) + { + return std.math.maxInt(u64); + } + + if (this.size == Blob.max_size) + return std.math.maxInt(u64); + + return this.size; + } + + export fn Bun__Blob__getSizeForBindings(this: *Blob) callconv(.C) u64 { + return this.getSizeForBindings(); + } + + comptime { + if (!JSC.is_bindgen) { + _ = Bun__Blob__getSizeForBindings; + } + } + pub fn getSize(this: *Blob, _: *JSC.JSGlobalObject) callconv(.C) JSValue { if (this.size == Blob.max_size) { this.resolveSize(); diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index d4a26d0f800dba..0e337ebf00e4c3 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -755,7 +755,7 @@ pub const TestCommand = struct { vm.onUnhandledRejectionCtx = null; vm.onUnhandledRejection = jest.TestRunnerTask.onUnhandledRejection; - module.runTests(JSC.JSValue.zero, vm.global); + module.runTests(vm.global); vm.eventLoop().tick(); var prev_unhandled_count = vm.unhandled_error_counter; diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig index 3ce8f91185848c..9f83e550eac656 100644 --- a/src/http/websocket_http_client.zig +++ b/src/http/websocket_http_client.zig @@ -130,16 +130,16 @@ const ErrorCode = enum(i32) { invalid_utf8, }; -pub const JSWebSocket = opaque { +const CppWebSocket = opaque { extern fn WebSocket__didConnect( - websocket_context: *JSWebSocket, + websocket_context: *CppWebSocket, socket: *uws.Socket, buffered_data: ?[*]u8, buffered_len: usize, ) void; - extern fn WebSocket__didCloseWithErrorCode(websocket_context: *JSWebSocket, reason: ErrorCode) void; - extern fn WebSocket__didReceiveText(websocket_context: *JSWebSocket, clone: bool, text: *const JSC.ZigString) void; - extern fn WebSocket__didReceiveBytes(websocket_context: *JSWebSocket, bytes: [*]const u8, byte_len: usize) void; + extern fn WebSocket__didCloseWithErrorCode(websocket_context: *CppWebSocket, reason: ErrorCode) void; + extern fn WebSocket__didReceiveText(websocket_context: *CppWebSocket, clone: bool, text: *const JSC.ZigString) void; + extern fn WebSocket__didReceiveBytes(websocket_context: *CppWebSocket, bytes: [*]const u8, byte_len: usize) void; pub const didConnect = WebSocket__didConnect; pub const didCloseWithErrorCode = WebSocket__didCloseWithErrorCode; @@ -157,7 +157,7 @@ pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { return struct { pub const Socket = uws.NewSocketHandler(ssl); tcp: Socket, - outgoing_websocket: ?*JSWebSocket, + outgoing_websocket: ?*CppWebSocket, input_body_buf: []u8 = &[_]u8{}, client_protocol: []const u8 = "", to_send: []const u8 = "", @@ -210,7 +210,7 @@ pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { pub fn connect( global: *JSC.JSGlobalObject, socket_ctx: *anyopaque, - websocket: *JSWebSocket, + websocket: *CppWebSocket, host: *const JSC.ZigString, port: u16, pathname: *const JSC.ZigString, @@ -844,7 +844,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { return struct { pub const Socket = uws.NewSocketHandler(ssl); tcp: Socket, - outgoing_websocket: ?*JSWebSocket = null, + outgoing_websocket: ?*CppWebSocket = null, receive_state: ReceiveState = ReceiveState.need_header, receive_header: WebsocketHeader = @bitCast(WebsocketHeader, @as(u16, 0)), @@ -934,6 +934,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { _ = socket; _ = ssl_error; log("WebSocket.onHandshake({d})", .{success}); + JSC.markBinding(@src()); if (success == 0) { if (this.outgoing_websocket) |ws| { this.outgoing_websocket = null; @@ -1520,7 +1521,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { } pub fn init( - outgoing: *JSWebSocket, + outgoing: *CppWebSocket, input_socket: *anyopaque, socket_ctx: *anyopaque, globalThis: *JSC.JSGlobalObject, diff --git a/src/js_ast.zig b/src/js_ast.zig index 9bf76a37fa5596..83a035a4ce30d4 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -8986,7 +8986,7 @@ pub const Macro = struct { while (i < count) { var nextArg = writer.eatArg() orelse return false; if (js.JSValueIsArray(writer.ctx, nextArg.asRef())) { - const extras = @truncate(u32, nextArg.getLengthOfArray(writer.ctx.ptr())); + const extras = @truncate(u32, nextArg.getLength(writer.ctx.ptr())); count += std.math.max(@truncate(@TypeOf(count), extras), 1) - 1; items.ensureUnusedCapacity(extras) catch unreachable; items.expandToCapacity(); @@ -9367,7 +9367,7 @@ pub const Macro = struct { .allocator = JSCBase.getAllocator(ctx), .exception = exception, .args_value = args_value, - .args_len = @truncate(u32, args_value.getLengthOfArray(ctx.ptr())), + .args_len = @truncate(u32, args_value.getLength(ctx.ptr())), .args_i = 0, .errored = false, }; diff --git a/src/napi/napi.zig b/src/napi/napi.zig index ceee2caa426eac..227a80d05e4ab9 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -571,7 +571,7 @@ pub export fn napi_has_element(env: napi_env, object: napi_value, index: c_uint, return .array_expected; } - result.* = object.getLengthOfArray(env) > index; + result.* = object.getLength(env) > index; return .ok; } pub export fn napi_get_element(env: napi_env, object: napi_value, index: u32, result: *napi_value) napi_status { @@ -595,7 +595,7 @@ pub export fn napi_get_array_length(env: napi_env, value: napi_value, result: [* return .array_expected; } - result.* = @truncate(u32, value.getLengthOfArray(env)); + result.* = @truncate(u32, value.getLength(env)); return .ok; } pub export fn napi_strict_equals(env: napi_env, lhs: napi_value, rhs: napi_value, result: *bool) napi_status { diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts index 5d45ef48ec8e76..4d31c9cb46855d 100644 --- a/test/js/bun/test/test-test.test.ts +++ b/test/js/bun/test/test-test.test.ts @@ -1765,6 +1765,52 @@ test("toHaveLength()", () => { expect("123").not.toHaveLength(-0); }); +test("toHaveLength() extended", () => { + // Headers + expect(new Headers()).toHaveLength(0); + expect(new Headers({ a: "1" })).toHaveLength(1); + + // FormData + const form = new FormData(); + expect(form).toHaveLength(0); + form.append("a", "1"); + expect(form).toHaveLength(1); + + // URLSearchParams + expect(new URLSearchParams()).toHaveLength(0); + expect(new URLSearchParams("a=1")).toHaveLength(1); + expect(new URLSearchParams([["a", "1"]])).toHaveLength(1); + + // files + const thisFile = Bun.file(import.meta.path); + const thisFileSize = thisFile.size; + + expect(thisFile).toHaveLength(thisFileSize); + expect(thisFile).toHaveLength(Bun.file(import.meta.path).size); + + // empty file should have length 0 + writeFileSync("/tmp/empty.txt", ""); + expect(Bun.file("/tmp/empty.txt")).toHaveLength(0); + + // if a file doesn't exist, it should throw (not return 0 size) + expect(() => expect(Bun.file("/does-not-exist/file.txt")).toHaveLength(0)).toThrow(); + + // Blob + expect(new Blob([1, 2, 3])).toHaveLength(3); + expect(new Blob()).toHaveLength(0); + + // Set + expect(new Set()).toHaveLength(0); + expect(new Set([1, 2, 3])).toHaveLength(3); + + // Map + expect(new Map()).toHaveLength(0); + expect(new Map([["a", 1]])).toHaveLength(1); + + // WeakMap + expect(new WeakMap([[globalThis, 1]])).toHaveLength(1); +}); + test("toContain()", () => { const s1 = new String("123"); expect(s1).not.toContain("12");