Perry version: 0.5.1161 (native host target; also reproduced on iOS device)
Summary
A specific function — a function declaration that iterates a module-level array of objects, reads a string property, and calls a string method — segfaults (SIGSEGV) when it is invoked from inside a closure. Rewriting the identical body as a const function-expression fixes it.
Minimal repro (native host)
interface SP { id: string }
let products: SP[] = [{ id: "GSC_AGENCY_MONTHLY" }, { id: "GSC_PRO_MONTHLY" }, { id: "GSC_PRO_ANNUAL" }]
function productsMatching(needle: string): SP[] {
const out: SP[] = []
for (let i = 0; i < products.length; i++) {
const pid: string = products[i].id
if (pid.indexOf(needle) >= 0) out.push(products[i])
}
return out
}
console.log("START")
const a = productsMatching("PRO") // called at MODULE scope
console.log("module pp.len=" + a.length) // prints 2 ✅
const onPress = function (): void {
const b = productsMatching("PRO") // SAME call, from inside a closure
console.log("closure pp.len=" + b.length) // never prints — SIGSEGV here
}
onPress()
console.log("END") // never prints
perry run repro.ts → prints START, module pp.len=2, then crashes (exit 1, faulting frame is the closure). EXC_BAD_ACCESS at a small/near-null address (0xfffffffffffffff9), consistent with a mis-resolved/mis-boxed function reference.
The fix that works
Change only the declaration form — function productsMatching(...) → const productsMatching = function (...). Then the closure call prints closure pp.len=2 and reaches END. Nothing else changes.
Characterization
It is not "any function declaration called from a closure" — simpler ones work from closures:
function noMod(x: number) { return x + 1 }
function withModArray(x: number) { let s=0; for (let i=0;i<arr.length;i++) s+=arr[i]; return s+x } // module array of NUMBERS
const cb = function () { noMod(5); withModArray(5) } // both work fine from the closure
The crash needs the combination in productsMatching (module array of objects + property read + indexOf + push) AND the hoisted function-declaration form AND being called from a closure. The const function-expression form is immune.
(Possibly related to the inline obj.prop.method() miscompile in #5195 — same products[i].id.indexOf(...) shape appears here — but here it's a hard crash gated on the declaration form + closure-call site, which is distinct.)
Impact
Silent SIGSEGV in a common shape (a helper that filters a module-level list of objects, called from a button handler/closure). Worked around app-side by using const fn = function(){} instead of function fn(){}.
Perry version: 0.5.1161 (native host target; also reproduced on iOS device)
Summary
A specific function — a
functiondeclaration that iterates a module-level array of objects, reads a string property, and calls a string method — segfaults (SIGSEGV) when it is invoked from inside a closure. Rewriting the identical body as aconstfunction-expression fixes it.Minimal repro (native host)
perry run repro.ts→ printsSTART,module pp.len=2, then crashes (exit 1, faulting frame is the closure). EXC_BAD_ACCESS at a small/near-null address (0xfffffffffffffff9), consistent with a mis-resolved/mis-boxed function reference.The fix that works
Change only the declaration form —
function productsMatching(...)→const productsMatching = function (...). Then the closure call printsclosure pp.len=2and reachesEND. Nothing else changes.Characterization
It is not "any function declaration called from a closure" — simpler ones work from closures:
The crash needs the combination in
productsMatching(module array of objects + property read +indexOf+push) AND the hoistedfunction-declaration form AND being called from a closure. Theconstfunction-expression form is immune.(Possibly related to the inline
obj.prop.method()miscompile in #5195 — sameproducts[i].id.indexOf(...)shape appears here — but here it's a hard crash gated on the declaration form + closure-call site, which is distinct.)Impact
Silent SIGSEGV in a common shape (a helper that filters a module-level list of objects, called from a button handler/closure). Worked around app-side by using
const fn = function(){}instead offunction fn(){}.