diff --git a/src/bun.js/bindings/JSMockFunction.cpp b/src/bun.js/bindings/JSMockFunction.cpp index 1c760357186..648f9fa277e 100644 --- a/src/bun.js/bindings/JSMockFunction.cpp +++ b/src/bun.js/bindings/JSMockFunction.cpp @@ -1529,12 +1529,12 @@ BUN_DEFINE_HOST_FUNCTION(JSMock__jsSpyOn, (JSC::JSGlobalObject * lexicalGlobalOb auto* mock = JSMockFunction::create(vm, globalObject, globalObject->mockModule.mockFunctionStructure.getInitializedOnMainThread(globalObject), CallbackKind::GetterSetter); mock->spyTarget = JSC::Weak(object, &weakValueHandleOwner(), nullptr); mock->spyIdentifier = propertyKey.isSymbol() ? Identifier::fromUid(vm, propertyKey.uid()) : Identifier::fromString(vm, propertyKey.publicName()); - mock->spyAttributes = hasValue ? slot.attributes() : 0; + mock->spyAttributes = hasValue ? attributesForStructure(slot.attributes()) : 0; unsigned attributes = 0; if (hasValue && ((slot.attributes() & PropertyAttribute::Function) != 0 || (value.isCell() && value.isCallable()))) { if (hasValue) - attributes = slot.attributes(); + attributes = attributesForStructure(slot.attributes()); mock->copyNameAndLength(vm, globalObject, value); @@ -1553,7 +1553,7 @@ BUN_DEFINE_HOST_FUNCTION(JSMock__jsSpyOn, (JSC::JSGlobalObject * lexicalGlobalOb pushImpl(mock, globalObject, JSMockImplementation::Kind::Call, value); } else { if (hasValue) - attributes = slot.attributes(); + attributes = attributesForStructure(slot.attributes()); attributes |= PropertyAttribute::Accessor; diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index df81b0daecc..545a63d8cf6 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -906,7 +906,7 @@ bool NodeVMSpecialSandbox::getOwnPropertySlot(JSObject* cell, JSGlobalObject* gl if (propertyName.uid()->utf8() == "globalThis") [[unlikely]] { slot.disableCaching(); slot.setThisValue(thisObject); - slot.setValue(thisObject, slot.attributes(), thisObject); + slot.setValue(thisObject, 0, thisObject); return true; } @@ -932,7 +932,7 @@ bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* glob if (notContextified && propertyName.uid()->utf8() == "globalThis") [[unlikely]] { slot.disableCaching(); slot.setThisValue(thisObject); - slot.setValue(thisObject, slot.attributes(), thisObject->specialSandbox()); + slot.setValue(thisObject, 0, thisObject->specialSandbox()); return true; } diff --git a/test/js/bun/test/spyon-static-property.test.ts b/test/js/bun/test/spyon-static-property.test.ts new file mode 100644 index 00000000000..55db6a52890 --- /dev/null +++ b/test/js/bun/test/spyon-static-property.test.ts @@ -0,0 +1,18 @@ +import { expect, spyOn, test } from "bun:test"; + +test("spyOn works on static hash table function properties", () => { + const spy = spyOn(Bun, "gc"); + try { + Bun.gc(true); + expect(spy).toHaveBeenCalledTimes(1); + } finally { + spy.mockRestore(); + } +}); + +test("spyOn preserves correct attributes after mockRestore", () => { + const spy = spyOn(Bun, "peek"); + spy.mockRestore(); + const p = Promise.resolve(42); + expect(Bun.peek(p)).toBe(42); +});