Give querystring.parse results a null prototype (#1263)

This commit is contained in:
Chris Tate
2026-10-18 01:48:07 -05:00
committed by GitHub
6 changed files with 64 additions and 0 deletions
@@ -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})`,
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5842-qs-parse-null-prototype.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5843-qs-parse-null-prototype-js.mjs"
],
"diags": []
}
@@ -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);
@@ -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);
+4
View File
@@ -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,