From 6f0d047e8ea39b8cf9e89715917145e82ee90517 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Wed, 21 Oct 2026 20:21:31 -0500 Subject: [PATCH] Pass a reassigned zlib callback result on to typed functions On the error path, a callback's result reads from a checked-dynamic slot. Passing it on into a Buffer parameter still handed over the adapter's original value, so after `if (!out) out = Buffer.from(...)` a typed callee received the absent Buffer instead of the fallback and crashed. The forward now tests the slot: while it holds undefined the callee gets the absent Buffer, and otherwise the Buffer the slot holds. --- .../src/frontend/lowering/lower-functions.ts | 65 ++++++++++++------- .../corpus/6920-zlib-callback-error-result.ts | 13 +++- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index 8f2c575a4..cd8dc31d1 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -751,31 +751,35 @@ function lowerUndefinedResultBody( lowerer.suppressStats = true; lowerer.errorPathCopies++; try { - // Passed on into a Buffer slot, the result stays what the adapter - // passed: the Buffer, or the absent Buffer, which a callee with this - // entry test reads as undefined. (The copy that serves both paths - // reaches this with a Buffer.) - const body = transformStmtList(withKnownError(knownError, lowerBody), { - expr: (e) => - e.kind === "dynCheck" && - e.value.kind === "varRef" && - e.value.localId === slot.id && - e.type.kind === "bytes" && - e.type.elem === "u8" - ? { - kind: "ternary", - cond: absent(), - then: { kind: "libCall", fn: "bytes.absent", args: [], type: e.type, loc: e.loc }, - else_: varRef(param.id, param.type, e.loc), - type: e.type, - loc: e.loc, - } - : e, + const lowered = withKnownError(knownError, lowerBody); + if (lowerer.diags.length > diags || !readsSlotDynamically(lowered, slot.id)) + throw new PoisonError(); + // Passed on into a Buffer slot, the result is the Buffer it holds + // (the adapter's, or one the body assigned), or, while it holds + // undefined, the absent Buffer, which a callee with this entry test + // reads as undefined. + const guarded = new Set(); + return transformStmtList(lowered, { + expr: (e) => { + if (!isBufferCheckOf(e, slot.id) || guarded.has(e)) return e; + guarded.add(e); + return { + kind: "ternary", + cond: { + kind: "dynTest", + test: "undefined", + value: varRef(slot.id, DYN, e.loc), + type: BOOL, + loc: e.loc, + }, + then: { kind: "libCall", fn: "bytes.absent", args: [], type: e.type, loc: e.loc }, + else_: e, + type: e.type, + loc: e.loc, + }; + }, stmt: (stmt) => stmt, }); - if (lowerer.diags.length > diags || !readsSlotDynamically(body, slot.id)) - throw new PoisonError(); - return body; } catch (error) { if (error instanceof InternalCompilerError) throw error; lowerer.diags.length = diags; @@ -831,15 +835,26 @@ function lowerUndefinedResultBody( /** Whether the error-path copy reads the checked-dynamic result slot only as * a checked-dynamic value, never converted into a static slot (where Node - * would hold undefined). */ + * would hold undefined) other than a Buffer one. */ function readsSlotDynamically(body: IrStmt[], slotId: string): boolean { const isSlot = (e: IrExpr): boolean => e.kind === "varRef" && e.localId === slotId; return everyStmtList(body, { expr: (e) => { if (isSlot(e)) return e.type.kind === "dyn"; - if (e.kind === "dynCheck" && isSlot(e.value)) return false; + if (e.kind === "dynCheck" && isSlot(e.value)) return isBufferCheckOf(e, slotId); return true; }, stmt: () => true, }); } + +/** Whether `e` converts the result slot into a Buffer slot. */ +function isBufferCheckOf(e: IrExpr, slotId: string): boolean { + return ( + e.kind === "dynCheck" && + e.value.kind === "varRef" && + e.value.localId === slotId && + e.type.kind === "bytes" && + e.type.elem === "u8" + ); +} diff --git a/tests/corpus/6920-zlib-callback-error-result.ts b/tests/corpus/6920-zlib-callback-error-result.ts index 5016fa841..44803333b 100644 --- a/tests/corpus/6920-zlib-callback-error-result.ts +++ b/tests/corpus/6920-zlib-callback-error-result.ts @@ -176,15 +176,24 @@ function namedRetry(): void { }); } +function shown(label: string, out: Buffer): void { + console.log(label, typeof out, out.toString()); +} function fallbackResult(): void { - // The body assigns a fallback Buffer to its result. + // The body assigns a fallback Buffer to its result, then passes the + // result on to typed functions. zlib.inflate(bad, (err, out) => { if (!out) out = Buffer.from("fallback"); console.log("fallback", err?.code, typeof out, out.toString()); + shown("fallback passed", out); zlib.inflate(good, (err2, out2) => { if (err2) out2 = Buffer.alloc(0); console.log("fallback nested", err2, out2.length); - step(); + shown("fallback nested passed", out2); + zlib.inflate(bad, (err3, out3) => { + if (err3) out3 = Buffer.from("inner fallback"); + sink(null, out3); + }); }); }); }