diff --git a/packages/compiler/src/backend/llvm/lib-filesystem.ts b/packages/compiler/src/backend/llvm/lib-filesystem.ts index 1083c69d5..b1d8869fd 100644 --- a/packages/compiler/src/backend/llvm/lib-filesystem.ts +++ b/packages/compiler/src/backend/llvm/lib-filesystem.ts @@ -746,6 +746,8 @@ export function emitPathUrlLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll const dict = B.tmp(); B.line(`${dict} = call ptr @${mangleRecordNew(e.type.shapeId)}()`); const out = host.own({ name: dict, type: e.type }); + // Node builds the result with a null prototype. + B.line(`store i8 1, ptr ${host.recordMapFlagsPtr(dict, e.type.shapeId)}`); const ovf = host.recordOvfPtr(dict, e.type.shapeId); B.line( `call void @scr_qs_parse_into(ptr ${ovf}, ptr ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${args[2]!.name}, double ${args[3]!.name}, i32 ${strTag}, i32 ${arrTag})`, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5842-qs-parse-null-prototype.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5842-qs-parse-null-prototype.ts.json new file mode 100644 index 000000000..a5b4a3e21 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5842-qs-parse-null-prototype.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5842-qs-parse-null-prototype.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5843-qs-parse-null-prototype-js.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5843-qs-parse-null-prototype-js.mjs.json new file mode 100644 index 000000000..228f0a4c0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5843-qs-parse-null-prototype-js.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5843-qs-parse-null-prototype-js.mjs" + ], + "diags": [] +} diff --git a/tests/corpus/5842-qs-parse-null-prototype.ts b/tests/corpus/5842-qs-parse-null-prototype.ts new file mode 100644 index 000000000..02d5cc134 --- /dev/null +++ b/tests/corpus/5842-qs-parse-null-prototype.ts @@ -0,0 +1,34 @@ +// querystring.parse and querystring.decode return a null-prototype +// object: inspection prints the "[Object: null prototype]" base, +// getPrototypeOf answers null, and ToPrimitive and Object.prototype +// methods throw TypeErrors. Spread copies keep Object.prototype. +import * as qs from "node:querystring"; +import { inspect } from "node:util"; + +const parsed = qs.parse("a=1&b=2&b=3&c"); +console.log(parsed); +console.log(inspect(parsed), inspect({ nested: parsed }), inspect([qs.parse("")])); +console.log(qs.decode("x=1;y=2", ";")); +console.log(Object.getPrototypeOf(parsed) === null, Object.keys(parsed), JSON.stringify(parsed)); + +const attempt = (label: string, f: () => unknown): void => { + try { + console.log(label, f()); + } catch (e) { + console.log(label, (e as Error).name, (e as Error).message); + } +}; +attempt("String", () => String(parsed)); +attempt("template", () => `${parsed}`); +attempt("concat", () => "q=" + parsed); +attempt("hasOwnProperty", () => parsed.hasOwnProperty("a")); +attempt("toString", () => parsed.toString()); +console.log(Object.prototype.toString.call(parsed), Object.hasOwn(parsed, "b")); +console.log(Object.prototype.hasOwnProperty.call(parsed, "c")); + +const copy = { ...parsed }; +console.log(copy, Object.getPrototypeOf(copy) === Object.prototype, String(copy)); + +parsed.added = "yes"; +delete parsed.a; +console.log(parsed, parsed.b, parsed.added); diff --git a/tests/corpus/5843-qs-parse-null-prototype-js.mjs b/tests/corpus/5843-qs-parse-null-prototype-js.mjs new file mode 100644 index 000000000..676d188c4 --- /dev/null +++ b/tests/corpus/5843-qs-parse-null-prototype-js.mjs @@ -0,0 +1,12 @@ +// querystring.parse results keep their null prototype when they cross into +// checked-dynamic values in a JavaScript module: inspection, getPrototypeOf, +// ToPrimitive and the `in` operator. +import qs from "node:querystring"; +const parsed = qs.parse("a=1&b=2&b=3"); +const holder = { parsed, list: [parsed] }; +console.log(holder); +const dyn = JSON.parse("{}"); +dyn.p = parsed; +console.log(dyn, Object.getPrototypeOf(dyn.p) === null); +try { console.log(String(dyn.p)); } catch (e) { console.log(e.name, e.message); } +console.log("toString" in parsed, "a" in parsed); diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index e2645a7a8..c09013aba 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -1849,6 +1849,8 @@ "5836-url-search-params-tuple-pairs.ts": 2118, "5837-util-format-json-circular.ts": 3117, "5838-qs-stringify-bigint.ts": 2207, + "5842-qs-parse-null-prototype.ts": 1774, + "5843-qs-parse-null-prototype-js.mjs": 1559, "5858-disposable-stack.ts": 3050, "5859-disposable-stack-js.js": 3100, "5881-inspect-weak-collections.ts": 2615, @@ -4509,6 +4511,8 @@ "5836-url-search-params-tuple-pairs.ts": 3193, "5837-util-format-json-circular.ts": 3408, "5838-qs-stringify-bigint.ts": 3519, + "5842-qs-parse-null-prototype.ts": 7077, + "5843-qs-parse-null-prototype-js.mjs": 5395, "5858-disposable-stack.ts": 3750, "5859-disposable-stack-js.js": 3600, "5881-inspect-weak-collections.ts": 2915,