-
Notifications
You must be signed in to change notification settings - Fork 5k
Fix crash when constructing a mock function that returns a primitive #31094
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟣 Pre-existing nit (not blocking):
fn->instancesis still never populated, sofn.mock.instanceswill remain[]afternew fn()even though Jest records the constructedthisthere. Since this PR now synthesizes the correctthisValuefor the construct path, it would be a natural place to also push it ontofn->instances(mirroring howcalls/contexts/resultsare pushed) — but that can also land separately.Extended reasoning...
What's missing
JSMockFunctiondeclaresmutable JSC::WriteBarrier<JSC::JSArray> instances;, clears it inclear(), GC-visits it, lazily initializes an empty array ingetInstances(), and exposes it on the mock object at offset 2 as theinstancesproperty. However, nothing injsMockFunctionCallOrConstruct(or anywhere else) ever pushes to it. The function pushes tofn->calls,fn->contexts,fn->invocationCallOrder, andfn->returnValueson every invocation, butfn->instancesis dead storage that always reads back as[].Why it's relevant to this PR
In Jest,
mockFn.mock.instancesis documented to record thethisthat was bound for each invocation — for a constructor call, that's the freshly-allocated instance. Before this PR,new fn()on a mock that returned a primitive would assert in debug builds and return a primitive in release builds, so the fact thatmock.instancesstayed empty was largely unobservable (you couldn't really use mocks as constructors anyway). After this PR,new fn()works correctly and the construct path explicitly allocates the rightthisValueviaconstructEmptyObject(globalObject, prototype)— so users who can now construct mocks may reasonably reach forfn.mock.instancesand find it empty.Step-by-step
const fn = jest.fn(function () { this.x = 1; });const inst = new fn();→ entersjsMockFunctionConstruct→jsMockFunctionCallOrConstruct(..., true).isConstructCallis true, sothisValue = constructEmptyObject(globalObject, prototype)— this is the instance.argumentsArraytofn->calls,thisValuetofn->contexts, the invocation id tofn->invocationCallOrder, and the result record tofn->returnValues.fn->instancesis never touched.encodeReturn(returnValue)returnsthisValue(the impl returnedundefined, a non-object), soinstis the constructed object — correct.fn.mock.instances→getInstances()→ lazy-creates and returns an empty array. Expected (Jest):[inst]. Actual:[].Why nothing prevents it
There's simply no write site. Grepping the file shows
instancesonly at the declaration,clear(),getInstances(), the visitor, and the mock-object structure setup. The existing test suite only assertsfn.mock.instancesis empty aftermockClear/mockReset, never that it's populated, so no test catches this.Impact
Low — it's a Jest-compat gap, not a crash or correctness issue in the PR's stated scope (a Fuzzilli-found assertion fix). Users porting Jest tests that assert on
mock.instances(e.g.expect(fn.mock.instances[0]).toBe(inst)) will see failures.Suggested fix (optional, can be a follow-up)
In
jsMockFunctionCallOrConstruct, alongside thecontextspush, also pushthisValuetofn->instances(Jest pushesthisfor every call, not just constructs — non-construct calls record whateverthiswas, and plainfn()recordsundefined):Severity
Pre-existing / nit. This gap predates the PR entirely —
instanceswas never populated for any kind of call. The PR is a targeted crash fix and shouldn't be blocked on this; it's flagged only because the PR is restructuring exactly the construct path that now has the right value to record.