diff --git a/src/bun.js/bindings/JSWrappingFunction.cpp b/src/bun.js/bindings/JSWrappingFunction.cpp index 5ae814ab01..b7f5c4374b 100644 --- a/src/bun.js/bindings/JSWrappingFunction.cpp +++ b/src/bun.js/bindings/JSWrappingFunction.cpp @@ -24,7 +24,7 @@ JS_EXPORT_PRIVATE JSWrappingFunction* JSWrappingFunction::create( Zig::NativeFunctionPtr functionPointer, JSC::JSValue wrappedFnValue) { - JSC::JSFunction* wrappedFn = jsCast(wrappedFnValue.asCell()); + JSC::JSObject* wrappedFn = wrappedFnValue.getObject(); ASSERT(wrappedFn != nullptr); auto nameStr = symbolName->tag == BunStringTag::Empty ? WTF::emptyString() : symbolName->toWTFString(); @@ -75,9 +75,9 @@ extern "C" JSC::EncodedJSValue Bun__JSWrappingFunction__getWrappedFunction( Zig::GlobalObject* globalObject) { JSC::JSValue thisValue = JSC::JSValue::decode(thisValueEncoded); - JSWrappingFunction* thisObject = jsCast(thisValue.asCell()); + JSWrappingFunction* thisObject = jsDynamicCast(thisValue.asCell()); if (thisObject != nullptr) { - JSC::JSFunction* wrappedFn = thisObject->m_wrappedFn.get(); + JSC::JSObject* wrappedFn = thisObject->m_wrappedFn.get(); return JSC::JSValue::encode(wrappedFn); } return {}; diff --git a/src/bun.js/bindings/JSWrappingFunction.h b/src/bun.js/bindings/JSWrappingFunction.h index df86f8334c..5b2aec591b 100644 --- a/src/bun.js/bindings/JSWrappingFunction.h +++ b/src/bun.js/bindings/JSWrappingFunction.h @@ -59,7 +59,7 @@ public: } private: - JSWrappingFunction(JSC::VM& vm, JSC::NativeExecutable* native, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSFunction* wrappedFn) + JSWrappingFunction(JSC::VM& vm, JSC::NativeExecutable* native, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* wrappedFn) : Base(vm, native, globalObject, structure) , m_wrappedFn(wrappedFn, JSC::WriteBarrierEarlyInit) { @@ -69,7 +69,7 @@ private: DECLARE_VISIT_CHILDREN; - JSC::WriteBarrier m_wrappedFn; + JSC::WriteBarrier m_wrappedFn; }; } diff --git a/test/regression/issue/fuzzer-ENG-22942.test.ts b/test/regression/issue/fuzzer-ENG-22942.test.ts new file mode 100644 index 0000000000..867b345de8 --- /dev/null +++ b/test/regression/issue/fuzzer-ENG-22942.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from "bun:test"; + +// Regression test for ENG-22942: Crash when calling expect.extend with non-function values +// The crash occurred because JSWrappingFunction assumed all callable objects are JSFunction, +// but class constructors like Expect are callable but not JSFunction instances. + +test("expect.extend with jest object should throw TypeError, not crash", () => { + const jest = Bun.jest(import.meta.path); + + expect(() => { + jest.expect.extend(jest); + }).toThrow(TypeError); +}); + +test("expect.extend with object containing non-function values should throw", () => { + const jest = Bun.jest(import.meta.path); + + expect(() => { + jest.expect.extend({ + notAFunction: "string value", + }); + }).toThrow("expect.extend: `notAFunction` is not a valid matcher"); +}); + +test("expect.extend with valid matchers still works", () => { + const jest = Bun.jest(import.meta.path); + + jest.expect.extend({ + toBeEven(received: number) { + const pass = received % 2 === 0; + return { + message: () => `expected ${received} ${pass ? "not " : ""}to be even`, + pass, + }; + }, + }); + + jest.expect(4).toBeEven(); + jest.expect(3).not.toBeEven(); +});