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.
This commit is contained in:
Chris Tate
2026-10-21 20:21:31 -05:00
parent 982ef6dbef
commit 6f0d047e8e
2 changed files with 51 additions and 27 deletions
@@ -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<IrExpr>();
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"
);
}
@@ -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);
});
});
});
}