Skip to content

BytecodeGenerator: emit expression info before [Symbol.iterator] get and spread#255

Open
robobun wants to merge 1 commit into
mainfrom
bun/fix-iterator-error-location-21134
Open

BytecodeGenerator: emit expression info before [Symbol.iterator] get and spread#255
robobun wants to merge 1 commit into
mainfrom
bun/fix-iterator-error-location-21134

Conversation

@robobun

@robobun robobun commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

What

When the subject of for-of, for-await-of, array destructuring, yield*, or array spread is a constant or other node that does not itself emit expression info (e.g. a null / undefined literal, including one produced by Bun's runtime-transpiler const inlining), the get_by_id for [Symbol.iterator] (and the OpSpread) inherit the previous statement's expression range. The resulting TypeError is attributed to the wrong line and the evaluating '...' text quotes the previous statement.

console.log("here");
for (const b of null) {}
TypeError: null is not an object (evaluating 'console.log("here")')
      at test.js:1:9

Fix

Emit expression info from the enclosing ThrowableExpressionData before each of these ops, matching what emitIteratorOpen / emitIteratorNext already do:

  • emitEnumeration: before the [Symbol.iterator] emitGetById
  • emitGetGenericIterator / emitGetAsyncIterator: before the [Symbol.iterator] / [Symbol.asyncIterator] emitGetById
  • ArrayPatternNode::bindValue: before the [Symbol.iterator] emitGetById
  • emitNewArrayWithSpread / emitCall spread / emitConstruct spread: before OpSpread::emit

After:

TypeError: null is not an object (evaluating 'b of null')
      at test.js:2:16

Verification

Built Bun against this branch on linux x64 debug+ASAN; oven-sh/bun regression test (test/regression/issue/21134.test.ts) covering all five constructs fails on the currently pinned commit and passes with this change. Normal for-of / destructuring / spread / yield* execution unaffected.

Fixes oven-sh/bun#21134.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ef0740ba-f9f9-49ef-a3d8-45e75db51b52

📥 Commits

Reviewing files that changed from the base of the PR and between cd821fe and e08efa3.

📒 Files selected for processing (2)
  • Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
  • Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

Walkthrough

emitExpressionInfo calls are added to spread-array fast paths (emitNewArrayWithSpread, emitCall, emitConstructImpl) and iterator-setup helpers (emitEnumeration, emitGetGenericIterator, emitGetAsyncIterator) in BytecodeGenerator.cpp, plus the array destructuring iterator path in ArrayPatternNode::bindValue in NodesCodegen.cpp.

Source-position instrumentation in spread and iterator paths

Layer / File(s) Summary
emitExpressionInfo in spread fast paths
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
In emitNewArrayWithSpread, the spread node's inner expression is extracted and emitExpressionInfo is called with the spread node's divot range before OpSpread. In emitCall's varargs fast path, the spread expression is extracted and expression info is recorded. In emitConstructImpl's varargs fast path, expression info is emitted conditionally, skipped when isDefaultDerivedConstructorCall is true.
emitExpressionInfo in iterator setup paths
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp, Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
emitExpressionInfo is inserted at the start of emitEnumeration's iteration-intrinsics branch, at the entry of emitGetGenericIterator, at the entry of emitGetAsyncIterator, and at the beginning of the iterator setup block in ArrayPatternNode::bindValue before iteratorSymbol is fetched.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: emitting expression info before Symbol.iterator gets and spread operations in the BytecodeGenerator.
Description check ✅ Passed The description provides a clear 'What' section explaining the problem, a 'Fix' section detailing the solution, and a 'Verification' section documenting testing, aligning well with typical pull request documentation standards.
Linked Issues check ✅ Passed The PR successfully addresses issue #21134 by emitting expression info before Symbol.iterator accesses and spread operations in five constructs (for-of, for-await-of, destructuring, yield*, and spread), fixing incorrect line numbers in stack traces.
Out of Scope Changes check ✅ Passed All changes are focused on emitting expression info in the BytecodeGenerator to fix stack trace attribution; no unrelated refactoring or out-of-scope modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any issues — the added emitExpressionInfo calls mirror the existing pattern in emitIteratorOpen/emitIteratorNext and only affect error-message source attribution — but since this touches the JSC bytecode generator it's worth a quick human sign-off.

Extended reasoning...

Overview

This PR adds emitExpressionInfo(...) calls immediately before seven sites in the JSC bytecode generator that emit a get_by_id for [Symbol.iterator]/[Symbol.asyncIterator] or an OpSpread: emitNewArrayWithSpread, emitCall (single-spread fast path), emitConstructImpl (single-spread fast path), emitEnumeration, emitGetGenericIterator, emitGetAsyncIterator, and ArrayPatternNode::bindValue. In the spread cases it also hoists the SpreadExpressionNode* cast into a local to read its ThrowableExpressionData divots. No other behavior is changed.

Security risks

None. emitExpressionInfo only records source-position metadata into the unlinked code block's expression-info table; it does not alter the emitted opcode stream, register allocation, or control flow. SpreadExpressionNode already inherits ThrowableExpressionData (Nodes.h:930), so the divot accessors are valid. The node parameter in the iterator helpers is already dereferenced by surrounding code, so no new null-deref surface is introduced.

Level of scrutiny

The diff itself is mechanical and exactly mirrors the established pattern two functions away in emitIteratorOpen / emitIteratorNext (BytecodeGenerator.cpp:5399, 5413). Functionally it only changes which line/column and "evaluating '...'" snippet appear in a thrown TypeError. That said, this is the JavaScriptCore bytecode compiler — a critical code path — so per policy I'm deferring rather than auto-approving.

Other factors

The PR description reports a Bun regression test (oven-sh/bun#21134) covering all five constructs that fails before and passes after, verified on a debug+ASAN build. The bug-hunting system found no issues. There are no prior human reviews or outstanding comments to address.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
e08efa3c autobuild-preview-pr-255-e08efa3c 2026-06-17 07:19:38 UTC
38cbbb3e autobuild-preview-pr-255-38cbbb3e 2026-06-17 00:20:27 UTC

…and spread

When the subject of for-of / for-await-of / array destructuring /
yield* / array spread is a constant or other node that does not itself
emit expression info (e.g. a null or undefined literal, including one
produced by Bun's const inlining), the get_by_id for [Symbol.iterator]
(or the OpSpread) inherited the previous statement's expression range.
The resulting TypeError was attributed to the wrong line and the
"evaluating '...'" text quoted the previous statement.

Emit expression info from the enclosing ThrowableExpressionData before
each of these ops so the throw is attributed to the iterating
construct itself.

Fixes oven-sh/bun#21134.
@robobun robobun force-pushed the bun/fix-iterator-error-location-21134 branch from 38cbbb3 to e08efa3 Compare June 17, 2026 06:28
robobun added a commit to oven-sh/bun that referenced this pull request Jun 17, 2026
…ndefined

When the subject of for-of, for-await-of, array destructuring, yield*
or array spread is a constant that emits no expression info of its own
(a null/undefined literal, including one Bun produces by inlining
`const a = undefined`), JSC's bytecode generator emitted the
[Symbol.iterator] get_by_id (and OpSpread) without calling
emitExpressionInfo. The TypeError inherited the previous statement's
divot and was attributed to the wrong line, with the "evaluating '...'"
text quoting that previous statement.

Fixed in oven-sh/WebKit#255 by emitting expression info from the
enclosing ThrowableExpressionData before each of these ops. This PR
pins a preview build containing that change and adds a regression test
covering all five constructs.

Fixes #21134
@robobun

robobun commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto cd821fe (#254) to match oven-sh/bun main's current WEBKIT_VERSION. New preview tag will be autobuild-preview-pr-255-e08efa3c.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — purely additive expression-info emission following the existing emitIteratorOpen pattern; only affects error-message source attribution.

Extended reasoning...

Overview

This PR adds emitExpressionInfo(...) calls in seven locations in the bytecode compiler immediately before ops that can throw when the iterable is non-iterable: the [Symbol.iterator] / [Symbol.asyncIterator] get_by_id in emitEnumeration, emitGetGenericIterator, emitGetAsyncIterator, and ArrayPatternNode::bindValue, and the OpSpread emission in emitNewArrayWithSpread, emitCall, and emitConstructImpl. Three of the spread sites also hoist the SpreadExpressionNode* into a local so its divot data is reachable — a no-op refactor.

Security risks

None. emitExpressionInfo only appends source-position metadata to the unlinked code block's expression-info table; it does not change emitted instructions, register allocation, control flow, or any runtime semantics. The only observable effect is which line/column and "evaluating '…'" snippet appear in a thrown TypeError.

Level of scrutiny

While BytecodeGenerator.cpp is critical-path, this change is mechanical and follows the exact established pattern already used a few lines away in emitIteratorOpen / emitIteratorNext (emitExpressionInfo(node->divot(), node->divotStart(), node->divotEnd()) before the throwable op). Every node touched (SpreadExpressionNode, ArrayPatternNode, and the ThrowableExpressionData* node parameters) already inherits ThrowableExpressionData and was already being dereferenced for divot data in adjacent code (e.g. passed to emitCallIterator / emitIteratorOpen), so there's no new null-deref or uninitialized-divot risk. The debug ASSERTs in emitExpressionInfo would have caught bad divot data in the author's debug+ASAN run.

Other factors

The bug-hunting system found no issues. The PR includes a downstream regression test (oven-sh/bun test/regression/issue/21134.test.ts) covering all five constructs that fails before and passes after. The diff is small (~15 lines net), self-contained, and the worst-case failure mode is a slightly-off error location — not a correctness or stability regression.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect line numbers in stack trace on TypeError: undefined is not an object inside a for loop

1 participant