mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-05 08:13:06 +08:00
Keep one source label per node in the shape analyzer
This commit is contained in:
@@ -317,6 +317,14 @@ export function analyzeShapes(program: ts.Program, checker: ts.TypeChecker): Sha
|
||||
}
|
||||
|
||||
class Analyzer {
|
||||
/** locLabel per node: sink reasons and atom labels name the same sites
|
||||
* over and over, and each label reads the file's text for its line. */
|
||||
private readonly locLabels = new Map<Node, string>();
|
||||
private loc(node: Node): string {
|
||||
let label = this.locLabels.get(node);
|
||||
if (label === undefined) this.locLabels.set(node, (label = locLabel(node)));
|
||||
return label;
|
||||
}
|
||||
readonly atoms = new AtomTable();
|
||||
readonly nodes: ValueNode[] = [];
|
||||
readonly functions = new Map<string, FunctionInfo>();
|
||||
@@ -830,7 +838,7 @@ class Analyzer {
|
||||
if (this.traceTop)
|
||||
this.nodeLabel.set(
|
||||
id,
|
||||
`binding ${(decl as { name?: { text?: string } }).name?.text ?? "?"}@${locLabel(decl)}${this.ctx !== "" ? ` [${this.ctx}]` : ""}`,
|
||||
`binding ${(decl as { name?: { text?: string } }).name?.text ?? "?"}@${this.loc(decl)}${this.ctx !== "" ? ` [${this.ctx}]` : ""}`,
|
||||
);
|
||||
}
|
||||
return id;
|
||||
@@ -908,7 +916,7 @@ class Analyzer {
|
||||
const nameNode = (fn as { name?: Node }).name;
|
||||
const name = nameNode !== undefined ? (propertyNameText(nameNode) ?? "<computed>") : null;
|
||||
const owner = ownerClass !== null ? this.className(ownerClass) : null;
|
||||
const label = `${owner !== null ? `${owner}.` : ""}${name ?? (ts.isConstructorDeclaration(node) ? "constructor" : "<fn>")}@${locLabel(node)}`;
|
||||
const label = `${owner !== null ? `${owner}.` : ""}${name ?? (ts.isConstructorDeclaration(node) ? "constructor" : "<fn>")}@${this.loc(node)}`;
|
||||
const info: FunctionInfo = {
|
||||
node,
|
||||
ctx,
|
||||
@@ -952,7 +960,7 @@ class Analyzer {
|
||||
if (p.dotDotDotToken !== undefined) {
|
||||
const arr = this.atoms.intern(`rest:${this.nodes.length}`, () => ({
|
||||
kind: "array",
|
||||
label: `rest@${locLabel(p)}`,
|
||||
label: `rest@${this.loc(p)}`,
|
||||
node: p,
|
||||
}));
|
||||
this.add(this.binding(p), arr);
|
||||
@@ -998,7 +1006,7 @@ class Analyzer {
|
||||
const p = decl.parent;
|
||||
if (p !== undefined && ts.isVariableDeclaration(p) && ts.isIdentifier(p.name))
|
||||
return p.name.text;
|
||||
return `<class@${locLabel(decl)}>`;
|
||||
return `<class@${this.loc(decl)}>`;
|
||||
}
|
||||
|
||||
classAtoms(decl: Node): { cls: Atom; inst: Atom } {
|
||||
@@ -1039,7 +1047,7 @@ class Analyzer {
|
||||
ctx: "",
|
||||
root: null,
|
||||
walked: true,
|
||||
label: `${label}.${suffix}@${locLabel(decl)}`,
|
||||
label: `${label}.${suffix}@${this.loc(decl)}`,
|
||||
params: [],
|
||||
rest: -1,
|
||||
thisNode: this.newNode(),
|
||||
@@ -1187,7 +1195,7 @@ class Analyzer {
|
||||
if (this.isFactory(info.node)) {
|
||||
const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({
|
||||
kind: "dictionary",
|
||||
label: `Object.create@${locLabel(site)}`,
|
||||
label: `Object.create@${this.loc(site)}`,
|
||||
node: site,
|
||||
}));
|
||||
this.add(dst, d);
|
||||
@@ -1253,7 +1261,7 @@ class Analyzer {
|
||||
syntheticArray(site: Node, tag: string): Atom {
|
||||
return this.atoms.intern(`arr:${tag}:${this.nodeKey(site)}`, () => ({
|
||||
kind: "array",
|
||||
label: `${tag}[]@${locLabel(site)}`,
|
||||
label: `${tag}[]@${this.loc(site)}`,
|
||||
node: site,
|
||||
}));
|
||||
}
|
||||
@@ -1361,7 +1369,7 @@ class Analyzer {
|
||||
return;
|
||||
}
|
||||
if (ts.isThrowStatement(s)) {
|
||||
this.sink(this.expr(s.expression), `thrown at ${locLabel(s)}`);
|
||||
this.sink(this.expr(s.expression), `thrown at ${this.loc(s)}`);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
@@ -1437,7 +1445,7 @@ class Analyzer {
|
||||
Symbol.iterator member have a computed key; they are G sites. */
|
||||
} else {
|
||||
this.add(dst, this.top);
|
||||
if (a.kind !== "top") this.escape(a, `iterated at ${locLabel(site)}`);
|
||||
if (a.kind !== "top") this.escape(a, `iterated at ${this.loc(site)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1453,7 +1461,7 @@ class Analyzer {
|
||||
if (this.traceTop && !this.nodeLabel.has(id))
|
||||
this.nodeLabel.set(
|
||||
id,
|
||||
`expr ${e.getText().slice(0, 40).replace(/\s+/g, " ")}@${locLabel(e)}`,
|
||||
`expr ${e.getText().slice(0, 40).replace(/\s+/g, " ")}@${this.loc(e)}`,
|
||||
);
|
||||
return id;
|
||||
}
|
||||
@@ -1586,7 +1594,7 @@ class Analyzer {
|
||||
if (ts.isTaggedTemplateExpression(e)) return this.taggedTemplate(e);
|
||||
if (ts.isYieldExpression(e)) {
|
||||
if (e.expression !== undefined)
|
||||
this.sink(this.expr(e.expression), `yielded at ${locLabel(e)}`);
|
||||
this.sink(this.expr(e.expression), `yielded at ${this.loc(e)}`);
|
||||
return this.constNode(this.top);
|
||||
}
|
||||
if (ts.isMetaProperty(e)) return this.constNode(this.top);
|
||||
@@ -1841,7 +1849,7 @@ class Analyzer {
|
||||
const spreads: number[] = [];
|
||||
const inPackage = isPackageJsFile(e.getSourceFile());
|
||||
const label = (): string =>
|
||||
`{${keys.slice(0, 6).join(",")}${keys.length > 6 ? ",…" : ""}${keysKnown ? "" : ",…"}}@${locLabel(e)}`;
|
||||
`{${keys.slice(0, 6).join(",")}${keys.length > 6 ? ",…" : ""}${keysKnown ? "" : ",…"}}@${this.loc(e)}`;
|
||||
// keys first, so the label is complete
|
||||
for (const p of list(e.properties)) {
|
||||
if (ts.isSpreadAssignment(p)) {
|
||||
@@ -2053,7 +2061,7 @@ class Analyzer {
|
||||
arrayLiteral(e: ts.ArrayLiteralExpression): number {
|
||||
const atom = this.atoms.intern(`arr:${this.nodeKey(e)}`, () => ({
|
||||
kind: "array",
|
||||
label: `[]@${locLabel(e)}`,
|
||||
label: `[]@${this.loc(e)}`,
|
||||
node: e,
|
||||
}));
|
||||
const n = this.newNode();
|
||||
@@ -2175,7 +2183,7 @@ class Analyzer {
|
||||
if (ts.isIdentifier(t)) {
|
||||
const decl = this.resolve(t);
|
||||
if (decl === undefined || decl.getSourceFile().isDeclarationFile) {
|
||||
this.sink(value, `global write at ${locLabel(t)}`);
|
||||
this.sink(value, `global write at ${this.loc(t)}`);
|
||||
return;
|
||||
}
|
||||
this.edge(value, this.binding(decl));
|
||||
@@ -2421,7 +2429,7 @@ class Analyzer {
|
||||
switch (a.kind) {
|
||||
case "top":
|
||||
this.noteTopWrite(name, site);
|
||||
this.sink(value, `stored into ⊤.${name} at ${locLabel(site)}`);
|
||||
this.sink(value, `stored into ⊤.${name} at ${this.loc(site)}`);
|
||||
return;
|
||||
case "instance": {
|
||||
const member = this.lookupMember(a.node!, name, false);
|
||||
@@ -2448,7 +2456,7 @@ class Analyzer {
|
||||
case "prim":
|
||||
return;
|
||||
default:
|
||||
this.sink(value, `stored into ${a.label} at ${locLabel(site)}`);
|
||||
this.sink(value, `stored into ${a.label} at ${this.loc(site)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2497,7 +2505,7 @@ class Analyzer {
|
||||
switch (a.kind) {
|
||||
case "top":
|
||||
this.noteTopWrite(null, site);
|
||||
this.sink(value, `stored into ⊤[k] at ${locLabel(site)}`);
|
||||
this.sink(value, `stored into ⊤[k] at ${this.loc(site)}`);
|
||||
return;
|
||||
case "array":
|
||||
// An index, or a named property under an unknown key.
|
||||
@@ -2515,7 +2523,7 @@ class Analyzer {
|
||||
return;
|
||||
default:
|
||||
// As a store under a name: a builtin, promise or collection keeps it where it isn't tracked.
|
||||
this.sink(value, `stored into ${a.label}[k] at ${locLabel(site)}`);
|
||||
this.sink(value, `stored into ${a.label}[k] at ${this.loc(site)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2591,7 +2599,7 @@ class Analyzer {
|
||||
if (inst.kind === "instance") this.callConstructorChain(base, inst, args, spread, e);
|
||||
});
|
||||
} else if (base === undefined)
|
||||
for (const a of args) this.sink(a, `super(...) at ${locLabel(e)}`);
|
||||
for (const a of args) this.sink(a, `super(...) at ${this.loc(e)}`);
|
||||
return dst;
|
||||
}
|
||||
// CommonJS `require("<spec>")` of an analyzed module: its exports (the
|
||||
@@ -2686,9 +2694,9 @@ class Analyzer {
|
||||
case "prim":
|
||||
return;
|
||||
default:
|
||||
for (const x of args) this.sink(x, `argument of ⊤ call at ${locLabel(site)}`);
|
||||
for (const x of args) this.sink(x, `argument of ⊤ call at ${this.loc(site)}`);
|
||||
if (recv !== null && a.kind === "top")
|
||||
this.sink(recv, `receiver of ⊤ call at ${locLabel(site)}`);
|
||||
this.sink(recv, `receiver of ⊤ call at ${this.loc(site)}`);
|
||||
this.add(dst, this.top);
|
||||
}
|
||||
});
|
||||
@@ -2731,7 +2739,7 @@ class Analyzer {
|
||||
this.constructBuiltin(a.path ?? "", args, dst, e);
|
||||
return;
|
||||
default:
|
||||
for (const x of args) this.sink(x, `argument of ⊤ constructor at ${locLabel(e)}`);
|
||||
for (const x of args) this.sink(x, `argument of ⊤ constructor at ${this.loc(e)}`);
|
||||
this.add(dst, this.top);
|
||||
}
|
||||
});
|
||||
@@ -2746,7 +2754,7 @@ class Analyzer {
|
||||
case "WeakSet": {
|
||||
const c = this.atoms.intern(`coll:${this.nodeKey(site)}`, () => ({
|
||||
kind: "collection",
|
||||
label: `${path}@${locLabel(site)}`,
|
||||
label: `${path}@${this.loc(site)}`,
|
||||
node: site,
|
||||
}));
|
||||
this.add(dst, c);
|
||||
@@ -2760,7 +2768,7 @@ class Analyzer {
|
||||
this.add(dst, this.builtin("Error.instance"));
|
||||
return;
|
||||
case "Promise": {
|
||||
const p = this.promiseAtom(site, locLabel(site));
|
||||
const p = this.promiseAtom(site, this.loc(site));
|
||||
this.add(this.field(p, "value"), this.top);
|
||||
this.add(dst, p);
|
||||
if (args[0] !== undefined) {
|
||||
@@ -2793,11 +2801,11 @@ class Analyzer {
|
||||
// The handler's traps run on every operation with the target.
|
||||
if (args[0] !== undefined)
|
||||
this.ops.push({ kind: "proxy", recv: args[0], name: null, node: site });
|
||||
for (const x of args) this.sink(x, `argument of new Proxy at ${locLabel(site)}`);
|
||||
for (const x of args) this.sink(x, `argument of new Proxy at ${this.loc(site)}`);
|
||||
this.add(dst, this.top);
|
||||
return;
|
||||
default:
|
||||
for (const x of args) this.sink(x, `argument of new ${path} at ${locLabel(site)}`);
|
||||
for (const x of args) this.sink(x, `argument of new ${path} at ${this.loc(site)}`);
|
||||
this.add(dst, this.top);
|
||||
}
|
||||
}
|
||||
@@ -2824,13 +2832,13 @@ class Analyzer {
|
||||
this.callFunction(this.infoOfAtom(f)!, null, cbArgs, false, ret ?? this.newNode(), site);
|
||||
this.forceLoop = saved;
|
||||
} else if (f.kind === "top" || f.kind === "builtin") {
|
||||
for (const x of cbArgs) this.sink(x, `argument of ⊤ callback at ${locLabel(site)}`);
|
||||
for (const x of cbArgs) this.sink(x, `argument of ⊤ callback at ${this.loc(site)}`);
|
||||
if (ret !== null) this.add(ret, this.top);
|
||||
}
|
||||
});
|
||||
};
|
||||
const escapeAll = (): void => {
|
||||
for (const x of args) this.sink(x, `argument of ${path} at ${locLabel(site)}`);
|
||||
for (const x of args) this.sink(x, `argument of ${path} at ${this.loc(site)}`);
|
||||
this.add(dst, this.top);
|
||||
};
|
||||
// Methods of builtin receivers.
|
||||
@@ -2948,7 +2956,7 @@ class Analyzer {
|
||||
return;
|
||||
}
|
||||
escapeAll();
|
||||
this.escape(recv, `array.${method} at ${locLabel(site)}`);
|
||||
this.escape(recv, `array.${method} at ${this.loc(site)}`);
|
||||
return;
|
||||
}
|
||||
if (path.startsWith("string.") || path.startsWith("number.") || path.startsWith("boolean.")) {
|
||||
@@ -2977,7 +2985,7 @@ class Analyzer {
|
||||
return;
|
||||
}
|
||||
if (path.startsWith("promise.")) {
|
||||
const out = this.promiseAtom(site, locLabel(site));
|
||||
const out = this.promiseAtom(site, this.loc(site));
|
||||
const value = this.field(recv, "value");
|
||||
const ret = this.newNode();
|
||||
if (method === "then") {
|
||||
@@ -3047,7 +3055,7 @@ class Analyzer {
|
||||
if (info !== undefined && args.length > 1) {
|
||||
this.activate(info);
|
||||
for (const p of info.params) this.add(p, this.top);
|
||||
for (const x of args.slice(1)) this.sink(x, `bound argument at ${locLabel(site)}`);
|
||||
for (const x of args.slice(1)) this.sink(x, `bound argument at ${this.loc(site)}`);
|
||||
}
|
||||
this.add(dst, recv);
|
||||
return;
|
||||
@@ -3145,7 +3153,7 @@ class Analyzer {
|
||||
for (const s of args.slice(1)) this.copyFields(s, t, site);
|
||||
if (t.kind === "top")
|
||||
for (const s of args.slice(1))
|
||||
this.sink(s, `Object.assign into ⊤ at ${locLabel(site)}`);
|
||||
this.sink(s, `Object.assign into ⊤ at ${this.loc(site)}`);
|
||||
});
|
||||
this.edge(arg(0), dst);
|
||||
return;
|
||||
@@ -3154,20 +3162,20 @@ class Analyzer {
|
||||
case "Object.defineProperties":
|
||||
case "Reflect.defineProperty":
|
||||
this.ops.push({ kind: "defineProperty", recv: arg(0), name: null, node: site });
|
||||
this.sink(arg(2), `property descriptor at ${locLabel(site)}`);
|
||||
this.sink(arg(1), `property descriptor at ${locLabel(site)}`);
|
||||
this.sink(arg(2), `property descriptor at ${this.loc(site)}`);
|
||||
this.sink(arg(1), `property descriptor at ${this.loc(site)}`);
|
||||
this.edge(arg(0), dst);
|
||||
return;
|
||||
case "Object.setPrototypeOf":
|
||||
case "Reflect.setPrototypeOf":
|
||||
this.ops.push({ kind: "setPrototypeOf", recv: arg(0), name: null, node: site });
|
||||
this.sink(arg(1), `prototype at ${locLabel(site)}`);
|
||||
this.sink(arg(1), `prototype at ${this.loc(site)}`);
|
||||
this.edge(arg(0), dst);
|
||||
return;
|
||||
case "Object.create": {
|
||||
const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({
|
||||
kind: "dictionary",
|
||||
label: `Object.create@${locLabel(site)}`,
|
||||
label: `Object.create@${this.loc(site)}`,
|
||||
node: site,
|
||||
}));
|
||||
this.add(dst, d);
|
||||
@@ -3176,7 +3184,7 @@ class Analyzer {
|
||||
case "Object.fromEntries": {
|
||||
const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({
|
||||
kind: "dictionary",
|
||||
label: `fromEntries@${locLabel(site)}`,
|
||||
label: `fromEntries@${this.loc(site)}`,
|
||||
node: site,
|
||||
}));
|
||||
const pairs = this.newNode();
|
||||
@@ -3225,7 +3233,7 @@ class Analyzer {
|
||||
site,
|
||||
);
|
||||
else if (f.kind === "top")
|
||||
this.sink(revived, `JSON.parse reviver at ${locLabel(site)}`);
|
||||
this.sink(revived, `JSON.parse reviver at ${this.loc(site)}`);
|
||||
});
|
||||
this.edge(revived, dst);
|
||||
return;
|
||||
@@ -3252,7 +3260,7 @@ class Analyzer {
|
||||
site,
|
||||
);
|
||||
else if (f.kind === "top")
|
||||
this.sink(serialized, `JSON.stringify replacer at ${locLabel(site)}`);
|
||||
this.sink(serialized, `JSON.stringify replacer at ${this.loc(site)}`);
|
||||
});
|
||||
prim("string");
|
||||
prim("undefined");
|
||||
@@ -3281,7 +3289,7 @@ class Analyzer {
|
||||
if (c.kind === "builtin" && c.path === "Proxy")
|
||||
this.ops.push({ kind: "proxy", recv: listed, name: null, node: site });
|
||||
});
|
||||
this.sink(listed, `argument of ${path} at ${locLabel(site)}`);
|
||||
this.sink(listed, `argument of ${path} at ${this.loc(site)}`);
|
||||
escapeAll();
|
||||
return;
|
||||
}
|
||||
@@ -3310,23 +3318,23 @@ class Analyzer {
|
||||
prim("bigint");
|
||||
return;
|
||||
case "Promise.resolve": {
|
||||
const p = this.promiseAtom(site, locLabel(site));
|
||||
const p = this.promiseAtom(site, this.loc(site));
|
||||
if (args[0] !== undefined) this.awaitInto(args[0], this.field(p, "value"));
|
||||
else this.add(this.field(p, "value"), this.prim("undefined"));
|
||||
this.add(dst, p);
|
||||
return;
|
||||
}
|
||||
case "Promise.reject": {
|
||||
const p = this.promiseAtom(site, locLabel(site));
|
||||
const p = this.promiseAtom(site, this.loc(site));
|
||||
this.add(dst, p);
|
||||
if (args[0] !== undefined) this.sink(args[0], `rejection at ${locLabel(site)}`);
|
||||
if (args[0] !== undefined) this.sink(args[0], `rejection at ${this.loc(site)}`);
|
||||
return;
|
||||
}
|
||||
case "Promise.all":
|
||||
case "Promise.allSettled":
|
||||
case "Promise.race":
|
||||
case "Promise.any": {
|
||||
const p = this.promiseAtom(site, locLabel(site));
|
||||
const p = this.promiseAtom(site, this.loc(site));
|
||||
const items = this.newNode();
|
||||
if (args[0] !== undefined) this.iterateInto(args[0], items, site);
|
||||
if (path === "Promise.all") {
|
||||
@@ -3340,7 +3348,7 @@ class Analyzer {
|
||||
return;
|
||||
}
|
||||
case "promise.resolver":
|
||||
for (const x of args) this.sink(x, `promise resolution at ${locLabel(site)}`);
|
||||
for (const x of args) this.sink(x, `promise resolution at ${this.loc(site)}`);
|
||||
return;
|
||||
}
|
||||
if (path.startsWith("console.")) {
|
||||
|
||||
Reference in New Issue
Block a user