diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5633-zlib-typed-options.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5633-zlib-typed-options.ts.json new file mode 100644 index 000000000..f4d9673d5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5633-zlib-typed-options.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5633-zlib-typed-options.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_zlib.c b/packages/runtime/src/scr_zlib.c index b61cdc1cb..ff09e7ea1 100644 --- a/packages/runtime/src/scr_zlib.c +++ b/packages/runtime/src/scr_zlib.c @@ -420,7 +420,22 @@ void scr_zlib_options_drop(ScrZlibOptions *o) { o->dict = NULL; } +static bool scr_zlib_options_parse_object(int mode, const ScrDyn *opts, ScrZlibOptions *out); + +/* A typed options object (a record that crossed into a checked-dynamic + * slot by reference) is read through its materialized view, as a plain + * object is. */ bool scr_zlib_options_parse(int mode, const ScrDyn *opts, ScrZlibOptions *out) { + if (opts == NULL || opts->kind != SCR_DYN_TYPED_REF) return scr_zlib_options_parse_object(mode, opts, out); + ScrDyn *view = scr_dyn_typed_ref_materialize(opts); + bool ok = false; + if (scr_exc_pending()) memset(out, 0, sizeof *out); + else ok = scr_zlib_options_parse_object(mode, view, out); + scr_dyn_release(view); + return ok; +} + +static bool scr_zlib_options_parse_object(int mode, const ScrDyn *opts, ScrZlibOptions *out) { memset(out, 0, sizeof *out); out->window_bits = 15; out->level = Z_DEFAULT_COMPRESSION; diff --git a/packages/runtime/src/scr_zlib_stream.c b/packages/runtime/src/scr_zlib_stream.c index 14d8747e8..4ed717d53 100644 --- a/packages/runtime/src/scr_zlib_stream.c +++ b/packages/runtime/src/scr_zlib_stream.c @@ -431,6 +431,13 @@ static const char *scr_zs_class_name(int mode) { } ScrStream *scr_zlib_stream_new(double mode_d, ScrDyn *opts) { + /* A typed options object is read through its materialized view. */ + if (opts != NULL && opts->kind == SCR_DYN_TYPED_REF) { + ScrDyn *view = scr_dyn_typed_ref_materialize(opts); + ScrStream *stream = scr_exc_pending() ? NULL : scr_zlib_stream_new(mode_d, view); + scr_dyn_release(view); + return stream; + } int mode = (int)mode_d; const ScrDyn *o = opts != NULL && scr_dyn_truthy(opts) ? opts : NULL; ScrZlibOptions zo; diff --git a/tests/corpus/5633-zlib-typed-options.ts b/tests/corpus/5633-zlib-typed-options.ts new file mode 100644 index 000000000..da657ca14 --- /dev/null +++ b/tests/corpus/5633-zlib-typed-options.ts @@ -0,0 +1,58 @@ +// zlib options held in a typed object (a cast object literal that crosses +// into the options slot by reference) are read like a plain options +// object: levels, windowBits and strategies apply, and out-of-range values +// throw Node's ERR_OUT_OF_RANGE from the sync, callback, promisified and +// stream forms. +import * as zlib from "node:zlib"; +import { promisify } from "node:util"; + +const data = Buffer.from("hello hello hello hello hello"); +const stored = { level: 0 } as zlib.ZlibOptions; +const fast = { level: 1 } as zlib.ZlibOptions; +const huffman = { strategy: zlib.constants.Z_HUFFMAN_ONLY } as zlib.ZlibOptions; +const smallWindow = { windowBits: 9, memLevel: 1 } as zlib.ZlibOptions; +const badLevel = { level: 99 } as zlib.ZlibOptions; +const badWindow = { windowBits: 3 } as zlib.ZlibOptions; + +function attempt(label: string, run: () => void): void { + try { + run(); + console.log(label, "ok"); + } catch (e) { + const err = e as NodeJS.ErrnoException; + console.log(label, err.name, err.code, err.message); + } +} + +console.log("sync stored", zlib.deflateSync(data, stored).toString("hex")); +console.log("sync fast", zlib.deflateSync(data, fast).toString("hex")); +console.log("sync huffman", zlib.deflateSync(data, huffman).toString("hex")); +console.log("raw small window", zlib.deflateRawSync(data, smallWindow).toString("hex")); +console.log("gzip stored", zlib.gzipSync(data, stored).subarray(10).toString("hex")); +attempt("sync bad level", () => zlib.deflateSync(data, badLevel)); +attempt("sync bad window", () => zlib.inflateSync(zlib.deflateSync(data), badWindow)); +attempt("callback bad level", () => zlib.gzip(data, badLevel, () => console.log("not called"))); +attempt("stream bad level", () => zlib.createDeflate(badLevel)); + +const deflateAsync = promisify(zlib.deflate); + +async function main(): Promise { + const viaCallback = await new Promise((resolve, reject) => + zlib.deflate(data, stored, (err, out) => (err ? reject(err) : resolve(out))), + ); + console.log("callback stored", viaCallback.toString("hex")); + console.log("promise stored", (await deflateAsync(data, stored)).toString("hex")); + try { + await deflateAsync(data, badLevel); + console.log("promise bad level fulfilled"); + } catch (e) { + console.log("promise bad level", (e as NodeJS.ErrnoException).code); + } + const stream = zlib.createDeflate(fast); + const parts: Buffer[] = []; + stream.on("data", (chunk: Buffer) => parts.push(chunk)); + stream.on("end", () => console.log("stream fast", Buffer.concat(parts).toString("hex"))); + stream.end(data); +} + +main().catch((e) => console.log("unexpected", String(e))); diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index 810e6a798..e211a263b 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -1976,6 +1976,7 @@ "5630-promisify-zlib.ts": 2503, "5631-promisify-crypto.ts": 2200, "5632-promisify-dns.ts": 1700, + "5633-zlib-typed-options.ts": 1800, "5644-net-socket-addresses.ts": 2220, "5645-net-socket-callbacks.ts": 2442, "5646-net-server-connections.ts": 2389, @@ -5012,6 +5013,7 @@ "5630-promisify-zlib.ts": 3392, "5631-promisify-crypto.ts": 2600, "5632-promisify-dns.ts": 2000, + "5633-zlib-typed-options.ts": 2200, "5644-net-socket-addresses.ts": 2393, "5645-net-socket-callbacks.ts": 2060, "5646-net-server-connections.ts": 2755,