fix(runtime): in operator on a closure value no longer SIGSEGVs (#1758) - #1811
Merged
Conversation
) Functions are objects in JS, so `key in fn` is valid. `js_object_has_property` treated a closure pointer as an `ObjectHeader` and read `(*obj_ptr).keys_array` at the closure's capture-slot offset — a NaN-boxed value, not a real `*ArrayHeader` — then crashed in `js_array_length` → `is_registered_set` dereferencing the tagged value. This is the last blocker on `import * as S from "effect/Schema"` MODULE INIT (after #1804/#1809/#1810): effect's `dual`-wrapped helpers do `<key> in someClosure` deep in the fiber runtime. Localized via PERRY_DEBUG_SYMBOLS=1 + lldb `-k` crash hooks + a conditional breakpoint on `js_array_length` with a tagged arg: the receiver had GC type 0x04 (GC_TYPE_CLOSURE) and its first field was a code pointer (a fiberRuntime closure body). Fix: `js_object_has_property` detects a `GC_TYPE_CLOSURE` receiver (sibling to the existing GC_TYPE_ARRAY fast path) and mirrors the closure read path (`js_object_get_field_by_name`: `length` → arity, others → CLOSURE_DYNAMIC_PROPS) — present-and-not-undefined ⇒ true. Matches node for absent keys (false, no crash), `length`, and dynamically-assigned props. With this, `import * as S from "effect/Schema"` initializes byte-identical to node (`ok object`). `effect/Effect` deep import stays green (42). New gap test `test_gap_in_operator_closure.ts`. (`'name'`/`'prototype' in fn` remain a separate pre-existing closure-property gap — `fn.name` also reads undefined — intentionally not asserted.) NOTE: actually USING Schema (`S.decodeUnknownSync(S.Number)(42)`) and the `effect` barrel hit further distinct blockers (ParseResult `_tag`-undefined; `value is not a function`). Reported separately. Refs #1758, #1785, #1772, #1791.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
key in fn(theinoperator on a closure/function value) SIGSEGV'd.Functions are objects in JS, so
key in fnis valid — butjs_object_has_propertytreated the closure pointer as anObjectHeaderandread
(*obj_ptr).keys_arrayat the closure's capture-slot offset (a NaN-boxedvalue, not a real
*ArrayHeader), then crashed injs_array_length→is_registered_setdereferencing the tagged value.This is the last blocker on
import * as S from "effect/Schema"MODULE INIT(after #1804 / #1809 / #1810): effect's
dual-wrapped helpers do<key> in someClosuredeep in the fiber runtime. With this fix, the Schemaimport initializes byte-identical to node (
ok object).Localization
PERRY_DEBUG_SYMBOLS=1+ lldb-kcrash hooks pinned the fault tois_registered_setderef'ing0x7ffd…(a NaN-boxed value). A conditionalbreakpoint on
js_array_lengthwith a tagged arg captured the receiver: GCtype
0x04(GC_TYPE_CLOSURE), first field a code pointer (afiberRuntimeclosure body) — i.e. a function, not an object.
Fix
js_object_has_propertydetects aGC_TYPE_CLOSUREreceiver (sibling to theexisting
GC_TYPE_ARRAYfast path) and mirrors the closure read path(
js_object_get_field_by_name:length→ arity, others →CLOSURE_DYNAMIC_PROPS): present-and-not-undefined ⇒ true. Regularobject/array
inis untouched.Validation
test_gap_in_operator_closure.ts— byte-for-byte with node:absent key (false, no crash),
length, dynamically-assigned props.import * as S from "effect/Schema"→ok object(exit 0).effect/Effectdeep import stays green (42).
(
PERRY_NO_AUTO_OPTIMIZE=1): 0 regressions — every failing test ispre-existing and none uses the
inoperator (the fix's only blastradius). (
'name'/'prototype' in fnremain a separate pre-existingclosure-property gap —
fn.namealso reads undefined — intentionally notasserted.)
Remaining (reported, not chained)
Schema init works now, but actually USING Schema and the barrel hit further
distinct blockers:
S.decodeUnknownSync(S.Number)(42)→_tag-undefined inParseResult.ts__69(the parse machinery).import { Effect } from "effect"barrel →value is not a function.Refs #1758, #1785, #1772, #1791.