diff --git a/src/bun.js/bindings/JSObject.zig b/src/bun.js/bindings/JSObject.zig index d7956ec1d0..b4e3de76dc 100644 --- a/src/bun.js/bindings/JSObject.zig +++ b/src/bun.js/bindings/JSObject.zig @@ -14,6 +14,18 @@ pub const JSObject = extern struct { return JSValue.fromCell(obj); } + /// Non-objects will be runtime-coerced to objects. + /// + /// For cells this is `toObjectSlow`, for other types it's `toObjectSlowCase`. + pub fn fromJS(value: JSValue, globalThis: JSValue) *JSObject { + return JSValue.toObject(value, globalThis); + } + + /// Returns `null` if the value is not an object. + pub fn tryFromJS(maybe_obj: JSValue, globalThis: *JSC.JSGlobalObject) ?*JSObject { + return JSValue.asObject(maybe_obj, globalThis); + } + /// Marshall a struct instance into a JSObject, copying its properties. /// /// Each field will be encoded with `JSC.toJS`. Fields whose types have a @@ -84,6 +96,50 @@ pub const JSObject = extern struct { } } + /// Equivalent to `target[property]`. Calls userland getters/proxies. Can + /// throw. Null indicates the property does not exist. JavaScript undefined + /// and JavaScript null can exist as a property and is different than zig + /// `null` (property does not exist). + /// + /// `property` must be either `[]const u8`. A comptime slice may defer to + /// calling `fastGet`, which use a more optimal code path. This function is + /// marked `inline` to allow Zig to determine if `fastGet` should be used + /// per invocation. + pub inline fn get(target: *JSObject, global: *JSGlobalObject, property: anytype) bun.JSError!?JSValue { + const property_slice: []const u8 = property; // must be a slice! + + // This call requires `get` to be `inline` + if (bun.isComptimeKnown(property_slice)) { + if (comptime JSValue.BuiltinName.get(property_slice)) |builtin_name| { + return target.fastGetWithError(global, builtin_name); + } + } + + return switch (JSC__JSObject__getIfPropertyExistsImpl(target, global, property_slice.ptr, @intCast(property_slice.len))) { + .zero => error.JSError, + .property_does_not_exist_on_object => null, + + // TODO: see bug described in ObjectBindings.cpp + // since there are false positives, the better path is to make them + // negatives, as the number of places that desire throwing on + // existing undefined is extremely small, but non-zero. + .undefined => null, + else => |val| val, + }; + } + extern fn JSC__JSObject__getIfPropertyExistsImpl(object: *JSObject, global: *JSGlobalObject, ptr: [*]const u8, len: u32) JSValue; + + + pub fn fastGetWithError(this: *JSObject, global: *JSGlobalObject, builtin_name: JSValue.BuiltinName) bun.JSError!?JSValue { + return switch (JSC__JSObject__fastGet(this, global, @intFromEnum(builtin_name))) { + .zero => error.JSError, + .undefined => null, + .property_does_not_exist_on_object => null, + else => |val| val, + }; + } + extern fn JSC__JSObject__fastGet(object: *JSObject, global: *JSGlobalObject, builtin_name: u32) JSValue; + extern fn JSC__createStructure(*JSC.JSGlobalObject, *JSC.JSCell, u32, names: [*]ExternColumnIdentifier) JSC.JSValue; pub const ExternColumnIdentifier = extern struct { @@ -106,6 +162,7 @@ pub const JSObject = extern struct { } } }; + pub fn createStructure(global: *JSGlobalObject, owner: JSC.JSValue, length: u32, names: [*]ExternColumnIdentifier) JSValue { JSC.markBinding(@src()); return JSC__createStructure(global, owner.asCell(), length, names); diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index ae1298109f..40b77bccc8 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -1535,10 +1535,17 @@ pub const JSValue = enum(i64) { }; } + /// Non-objects will be converted to an object via `toObjectSlowCase()` pub fn toObject(this: JSValue, globalThis: *JSGlobalObject) *JSObject { return cppFn("toObject", .{ this, globalThis }); } + /// Cast `this` to a JSObject, or return null if it is not an object. + /// Use `toObject` to convert non-objects. + pub fn asObject(this: JSValue, globalThis: *JSGlobalObject) ?*JSObject { + return if (this.isObject()) this.toObject(globalThis) else null; + } + pub fn getPrototype(this: JSValue, globalObject: *JSGlobalObject) JSValue { return cppFn("getPrototype", .{ this, globalObject }); } @@ -1701,28 +1708,17 @@ pub const JSValue = enum(i64) { /// calling `fastGet`, which use a more optimal code path. This function is /// marked `inline` to allow Zig to determine if `fastGet` should be used /// per invocation. + /// + /// ## Deprecated + /// Cast `target` to a JSObject then use `.get()` instead. + /// ```zig + /// if (value.asObject(global)) |obj| { + /// const prop = obj.get(global, "foo"); + /// } + /// ``` pub inline fn get(target: JSValue, global: *JSGlobalObject, property: anytype) JSError!?JSValue { if (bun.Environment.isDebug) bun.assert(target.isObject()); - const property_slice: []const u8 = property; // must be a slice! - - // This call requires `get` to be `inline` - if (bun.isComptimeKnown(property_slice)) { - if (comptime BuiltinName.get(property_slice)) |builtin_name| { - return target.fastGetWithError(global, builtin_name); - } - } - - return switch (JSC__JSValue__getIfPropertyExistsImpl(target, global, property_slice.ptr, @intCast(property_slice.len))) { - .zero => error.JSError, - .property_does_not_exist_on_object => null, - - // TODO: see bug described in ObjectBindings.cpp - // since there are false positives, the better path is to make them - // negatives, as the number of places that desire throwing on - // existing undefined is extremely small, but non-zero. - .undefined => null, - else => |val| val, - }; + return target.toObject(global).get(global, property); } extern fn JSC__JSValue__getOwn(value: JSValue, globalObject: *JSGlobalObject, propertyName: *const bun.String) JSValue; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index ef76e711f1..d2426d0915 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -140,6 +140,8 @@ #endif #endif +static inline const JSC::Identifier builtinNameMap(JSC::VM& vm, unsigned char name); + static WTF::StringView StringView_slice(WTF::StringView sv, unsigned start, unsigned end) { return sv.substring(start, end - start); @@ -2416,6 +2418,35 @@ void JSC__JSValue__putRecord(JSC__JSValue objectValue, JSC__JSGlobalObject* glob object->putDirect(global->vm(), ident, descriptor.value()); scope.release(); } +// Returns empty for exception, returns deleted if not found. +// Be careful when handling the return value. +JSC__JSValue JSC__JSObject__getIfPropertyExistsImpl(JSC::JSObject* object, + JSC__JSGlobalObject* globalObject, + const unsigned char* arg1, uint32_t arg2) +{ + ASSERT_NO_PENDING_EXCEPTION(globalObject); + + auto& vm = JSC::getVM(globalObject); + ASSERT(object != nullptr); + + // Since Identifier might not ref the string, we need to ensure it doesn't get deref'd until this function returns + const auto propertyString = String(StringImpl::createWithoutCopying({ arg1, arg2 })); + const auto identifier = JSC::Identifier::fromString(vm, propertyString); + const auto property = JSC::PropertyName(identifier); + + return JSC::JSValue::encode(Bun::getIfPropertyExistsPrototypePollutionMitigationUnsafe(vm, globalObject, object, property)); +} + +// Returns empty for exception, returns deleted if not found. +// Be careful when handling the return value. +JSC__JSValue JSC__JSObject__fastGet(JSC::JSObject* object, JSC__JSGlobalObject* globalObject, unsigned char arg2) +{ + ASSERT(object); + auto& vm = JSC::getVM(globalObject); + const auto property = JSC::PropertyName(builtinNameMap(vm, arg2)); + + return JSC::JSValue::encode(Bun::getIfPropertyExistsPrototypePollutionMitigationUnsafe(vm, globalObject, object, property)); +} JSC__JSInternalPromise* JSC__JSValue__asInternalPromise(JSC__JSValue JSValue0) { @@ -3977,18 +4008,13 @@ JSC__JSValue JSC__JSValue__getIfPropertyExistsImpl(JSC__JSValue JSValue0, JSValue value = JSC::JSValue::decode(JSValue0); ASSERT_WITH_MESSAGE(!value.isEmpty(), "get() must not be called on empty value"); - auto& vm = JSC::getVM(globalObject); + // auto& vm = JSC::getVM(globalObject); JSC::JSObject* object = value.getObject(); if (UNLIKELY(!object)) { return JSValue::encode(JSValue::decode(JSC::JSValue::ValueDeleted)); } - // Since Identifier might not ref the string, we need to ensure it doesn't get deref'd until this function returns - const auto propertyString = String(StringImpl::createWithoutCopying({ arg1, arg2 })); - const auto identifier = JSC::Identifier::fromString(vm, propertyString); - const auto property = JSC::PropertyName(identifier); - - return JSC::JSValue::encode(Bun::getIfPropertyExistsPrototypePollutionMitigationUnsafe(vm, globalObject, object, property)); + return JSC__JSObject__getIfPropertyExistsImpl(object, globalObject, arg1, arg2); } extern "C" JSC__JSValue JSC__JSValue__getOwn(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, BunString* propertyName) @@ -4238,7 +4264,11 @@ JSC__JSValue JSC__JSValue__jsUndefined() { return JSC::JSValue::encode(JSC::jsUn JSC__JSObject* JSC__JSValue__toObject(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1) { JSC::JSValue value = JSC::JSValue::decode(JSValue0); - return value.toObject(arg1); + auto* obj = value.toObject(arg1); + #if BUN_DEBUG + ASSERT(obj); + #endif + return obj; } JSC__JSString* JSC__JSValue__toString(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1) @@ -5477,10 +5507,7 @@ JSC__JSValue JSC__JSValue__fastGet(JSC__JSValue JSValue0, JSC__JSGlobalObject* g JSC::JSObject* object = value.getObject(); ASSERT_WITH_MESSAGE(object, "fastGet() called on non-object. Check that the JSValue is an object before calling fastGet()."); - auto& vm = JSC::getVM(globalObject); - const auto property = JSC::PropertyName(builtinNameMap(vm, arg2)); - - return JSC::JSValue::encode(Bun::getIfPropertyExistsPrototypePollutionMitigationUnsafe(vm, globalObject, object, property)); + return JSC__JSObject__fastGet(object, globalObject, arg2); } extern "C" JSC__JSValue JSC__JSValue__fastGetOwn(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, unsigned char arg2) diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index a0dd61a47d..6f166344ad 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -145,6 +145,8 @@ typedef void* JSClassRef; CPP_DECL JSC__JSValue JSC__JSObject__create(JSC__JSGlobalObject* arg0, size_t arg1, void* arg2, void(* ArgFn3)(void* arg0, JSC__JSObject* arg1, JSC__JSGlobalObject* arg2)); CPP_DECL size_t JSC__JSObject__getArrayLength(JSC__JSObject* arg0); +CPP_DECL JSC__JSValue JSC__JSObject__fastGet(JSC__JSObject* object, JSC__JSGlobalObject* arg1, unsigned char arg2); +CPP_DECL JSC__JSValue JSC__JSObject__getIfPropertyExistsImpl(JSC__JSObject* object, JSC__JSGlobalObject* arg1, const unsigned char* arg2, uint32_t arg3); CPP_DECL JSC__JSValue JSC__JSObject__getDirect(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, const ZigString* arg2); CPP_DECL JSC__JSValue JSC__JSObject__getIndex(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, uint32_t arg2); CPP_DECL void JSC__JSObject__putRecord(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4);