diff --git a/src/jsc/bindings/JSMockFunction.cpp b/src/jsc/bindings/JSMockFunction.cpp index 1c1c7fe70f6..bad75e96f0a 100644 --- a/src/jsc/bindings/JSMockFunction.cpp +++ b/src/jsc/bindings/JSMockFunction.cpp @@ -1561,8 +1561,7 @@ BUN_DEFINE_HOST_FUNCTION(JSMock__jsSpyOn, (JSC::JSGlobalObject * lexicalGlobalOb moduleNamespaceObject->overrideExportValue(globalObject, propertyKey, mock); mock->spyAttributes |= JSMockFunction::SpyAttributeESModuleNamespace; } else if (auto index = parseIndex(propertyKey)) { - // For indexed properties, set the mock directly instead of wrapping in GetterSetter - object->putDirectIndex(globalObject, *index, mock, attributes, PutDirectIndexLikePutDirect); + object->putDirectIndex(globalObject, *index, JSC::GetterSetter::create(vm, globalObject, mock, mock), attributes, PutDirectIndexLikePutDirect); } else { object->putDirectAccessor(globalObject, propertyKey, JSC::GetterSetter::create(vm, globalObject, mock, mock), attributes); } diff --git a/test/js/bun/test/mock-fn.test.js b/test/js/bun/test/mock-fn.test.js index 7f6a244d980..0ea1de91c19 100644 --- a/test/js/bun/test/mock-fn.test.js +++ b/test/js/bun/test/mock-fn.test.js @@ -1011,6 +1011,21 @@ describe("spyOn", () => { expect(arr[14]()).toBe(456); expect(fn).not.toHaveBeenCalled(); }); + + test("spyOn on a missing indexed property does not crash on read/write", () => { + const obj = {}; + const fn = spyOn(obj, 1002); + + // Reading the spied index must not crash (it is installed as an accessor). + expect(obj[1002]).toBeUndefined(); + expect(fn).toHaveBeenCalledTimes(1); + + // Writing the spied index must not crash either. + obj[1002] = 42; + Reflect.set(obj, 1002, 43); + + fn.mockRestore(); + }); } // spyOn does not work with getters/setters yet.