monkeypatchlist 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +61 -0
- package/README.md +183 -2
- package/catalog/catalog.json +552211 -0
- package/catalog/catalog.schema.json +787 -0
- package/catalog/changelog.json +3 -0
- package/dist/bin/monkeypatchlist.d.ts +2 -0
- package/dist/bin/monkeypatchlist.js +3 -0
- package/dist/catalog.d.ts +561 -0
- package/dist/catalog.js +58 -0
- package/dist/cli.d.ts +84 -0
- package/dist/cli.js +470 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lockfile-input.d.ts +9 -0
- package/dist/lockfile-input.js +91 -0
- package/dist/packages/analysis/binding.d.ts +2 -0
- package/dist/packages/analysis/binding.js +5 -0
- package/dist/packages/analysis/index.d.ts +10 -0
- package/dist/packages/analysis/index.js +13 -0
- package/dist/packages/analysis/job.d.ts +33 -0
- package/dist/packages/analysis/job.js +85 -0
- package/dist/packages/analysis/lib/absence-tags.d.ts +34 -0
- package/dist/packages/analysis/lib/absence-tags.js +340 -0
- package/dist/packages/analysis/lib/ast.d.ts +28 -0
- package/dist/packages/analysis/lib/ast.js +64 -0
- package/dist/packages/analysis/lib/binding.d.ts +36 -0
- package/dist/packages/analysis/lib/binding.js +66 -0
- package/dist/packages/analysis/lib/budget.d.ts +80 -0
- package/dist/packages/analysis/lib/budget.js +31 -0
- package/dist/packages/analysis/lib/catch-arms.d.ts +19 -0
- package/dist/packages/analysis/lib/catch-arms.js +351 -0
- package/dist/packages/analysis/lib/chunk-loads.d.ts +160 -0
- package/dist/packages/analysis/lib/chunk-loads.js +882 -0
- package/dist/packages/analysis/lib/chunk-registry.d.ts +45 -0
- package/dist/packages/analysis/lib/chunk-registry.js +334 -0
- package/dist/packages/analysis/lib/code-routes.d.ts +52 -0
- package/dist/packages/analysis/lib/code-routes.js +921 -0
- package/dist/packages/analysis/lib/collisions.d.ts +22 -0
- package/dist/packages/analysis/lib/collisions.js +103 -0
- package/dist/packages/analysis/lib/definitions.d.ts +76 -0
- package/dist/packages/analysis/lib/definitions.js +782 -0
- package/dist/packages/analysis/lib/early-exits.d.ts +39 -0
- package/dist/packages/analysis/lib/early-exits.js +161 -0
- package/dist/packages/analysis/lib/effect.d.ts +138 -0
- package/dist/packages/analysis/lib/effect.js +800 -0
- package/dist/packages/analysis/lib/global-object.d.ts +581 -0
- package/dist/packages/analysis/lib/global-object.js +8240 -0
- package/dist/packages/analysis/lib/keys.d.ts +33 -0
- package/dist/packages/analysis/lib/keys.js +82 -0
- package/dist/packages/analysis/lib/line-starts.d.ts +41 -0
- package/dist/packages/analysis/lib/line-starts.js +79 -0
- package/dist/packages/analysis/lib/link-group.d.ts +51 -0
- package/dist/packages/analysis/lib/link-group.js +487 -0
- package/dist/packages/analysis/lib/module-links.d.ts +117 -0
- package/dist/packages/analysis/lib/module-links.js +1060 -0
- package/dist/packages/analysis/lib/nested-code.d.ts +38 -0
- package/dist/packages/analysis/lib/nested-code.js +200 -0
- package/dist/packages/analysis/lib/numeric.d.ts +16 -0
- package/dist/packages/analysis/lib/numeric.js +216 -0
- package/dist/packages/analysis/lib/once-flags.d.ts +12 -0
- package/dist/packages/analysis/lib/once-flags.js +190 -0
- package/dist/packages/analysis/lib/parameter-arguments.d.ts +12 -0
- package/dist/packages/analysis/lib/parameter-arguments.js +34 -0
- package/dist/packages/analysis/lib/parse.d.ts +10 -0
- package/dist/packages/analysis/lib/parse.js +39 -0
- package/dist/packages/analysis/lib/partial.d.ts +60 -0
- package/dist/packages/analysis/lib/partial.js +518 -0
- package/dist/packages/analysis/lib/print.d.ts +3 -0
- package/dist/packages/analysis/lib/print.js +150 -0
- package/dist/packages/analysis/lib/reachability.d.ts +137 -0
- package/dist/packages/analysis/lib/reachability.js +1295 -0
- package/dist/packages/analysis/lib/resolver.d.ts +13 -0
- package/dist/packages/analysis/lib/resolver.js +2886 -0
- package/dist/packages/analysis/lib/restores.d.ts +42 -0
- package/dist/packages/analysis/lib/restores.js +482 -0
- package/dist/packages/analysis/lib/rewritten-deletes.d.ts +6 -0
- package/dist/packages/analysis/lib/rewritten-deletes.js +142 -0
- package/dist/packages/analysis/lib/scope.d.ts +110 -0
- package/dist/packages/analysis/lib/scope.js +443 -0
- package/dist/packages/analysis/lib/script-loads.d.ts +56 -0
- package/dist/packages/analysis/lib/script-loads.js +359 -0
- package/dist/packages/analysis/lib/summaries.d.ts +177 -0
- package/dist/packages/analysis/lib/summaries.js +498 -0
- package/dist/packages/analysis/lib/types.d.ts +632 -0
- package/dist/packages/analysis/lib/types.js +128 -0
- package/dist/packages/analysis/lib/wrap.d.ts +34 -0
- package/dist/packages/analysis/lib/wrap.js +407 -0
- package/dist/packages/analysis/lib/writes.d.ts +358 -0
- package/dist/packages/analysis/lib/writes.js +1612 -0
- package/dist/packages/analysis/visit-order.d.ts +3 -0
- package/dist/packages/analysis/visit-order.js +23 -0
- package/dist/packages/attribution/index.d.ts +233 -0
- package/dist/packages/attribution/index.js +444 -0
- package/dist/packages/attribution/lib/containing-package.d.ts +76 -0
- package/dist/packages/attribution/lib/containing-package.js +304 -0
- package/dist/packages/attribution/lib/first-party.d.ts +37 -0
- package/dist/packages/attribution/lib/first-party.js +153 -0
- package/dist/packages/attribution/lib/lockfile-graph.d.ts +99 -0
- package/dist/packages/attribution/lib/lockfile-graph.js +158 -0
- package/dist/packages/attribution/lib/lockfile.d.ts +33 -0
- package/dist/packages/attribution/lib/lockfile.js +330 -0
- package/dist/packages/attribution/lib/path-rules.d.ts +29 -0
- package/dist/packages/attribution/lib/path-rules.js +84 -0
- package/dist/packages/attribution/lib/pnpm.d.ts +4 -0
- package/dist/packages/attribution/lib/pnpm.js +133 -0
- package/dist/packages/attribution/lib/remote-url.d.ts +7 -0
- package/dist/packages/attribution/lib/remote-url.js +24 -0
- package/dist/packages/attribution/lib/source-map.d.ts +51 -0
- package/dist/packages/attribution/lib/source-map.js +125 -0
- package/dist/packages/attribution/lib/yaml.d.ts +9 -0
- package/dist/packages/attribution/lib/yaml.js +277 -0
- package/dist/packages/attribution/lib/yarn.d.ts +7 -0
- package/dist/packages/attribution/lib/yarn.js +227 -0
- package/dist/packages/baseline/index.d.ts +45 -0
- package/dist/packages/baseline/index.js +41 -0
- package/dist/packages/baseline/lib/error.d.ts +15 -0
- package/dist/packages/baseline/lib/error.js +25 -0
- package/dist/packages/baseline/lib/format.d.ts +49 -0
- package/dist/packages/baseline/lib/format.js +22 -0
- package/dist/packages/baseline/lib/migrations.d.ts +19 -0
- package/dist/packages/baseline/lib/migrations.js +7 -0
- package/dist/packages/baseline/lib/previous.d.ts +23 -0
- package/dist/packages/baseline/lib/previous.js +64 -0
- package/dist/packages/baseline/lib/read.d.ts +39 -0
- package/dist/packages/baseline/lib/read.js +219 -0
- package/dist/packages/baseline/lib/report.d.ts +25 -0
- package/dist/packages/baseline/lib/report.js +104 -0
- package/dist/packages/baseline/lib/skew.d.ts +51 -0
- package/dist/packages/baseline/lib/skew.js +75 -0
- package/dist/packages/compare/index.d.ts +120 -0
- package/dist/packages/compare/index.js +274 -0
- package/dist/packages/compare/lib/catalog-notes.d.ts +73 -0
- package/dist/packages/compare/lib/catalog-notes.js +224 -0
- package/dist/packages/completeness/index.d.ts +246 -0
- package/dist/packages/completeness/index.js +249 -0
- package/dist/packages/confidence/index.d.ts +54 -0
- package/dist/packages/confidence/index.js +71 -0
- package/dist/packages/config/index.d.ts +107 -0
- package/dist/packages/config/index.js +95 -0
- package/dist/packages/config/lib/command-line.d.ts +23 -0
- package/dist/packages/config/lib/command-line.js +239 -0
- package/dist/packages/config/lib/config-file.d.ts +11 -0
- package/dist/packages/config/lib/config-file.js +119 -0
- package/dist/packages/config/lib/scan-config-id.d.ts +28 -0
- package/dist/packages/config/lib/scan-config-id.js +54 -0
- package/dist/packages/config/lib/schema.d.ts +2 -0
- package/dist/packages/config/lib/schema.js +31 -0
- package/dist/packages/config/lib/types.d.ts +64 -0
- package/dist/packages/config/lib/types.js +6 -0
- package/dist/packages/config/lib/urls.d.ts +6 -0
- package/dist/packages/config/lib/urls.js +9 -0
- package/dist/packages/discovery/index.d.ts +277 -0
- package/dist/packages/discovery/index.js +1950 -0
- package/dist/packages/discovery/lib/import-map.d.ts +68 -0
- package/dist/packages/discovery/lib/import-map.js +180 -0
- package/dist/packages/discovery/lib/requests.d.ts +58 -0
- package/dist/packages/discovery/lib/requests.js +591 -0
- package/dist/packages/discovery/lib/scopes.d.ts +6 -0
- package/dist/packages/discovery/lib/scopes.js +286 -0
- package/dist/packages/discovery/lib/source-maps.d.ts +56 -0
- package/dist/packages/discovery/lib/source-maps.js +209 -0
- package/dist/packages/discovery/lib/swept-code.d.ts +27 -0
- package/dist/packages/discovery/lib/swept-code.js +107 -0
- package/dist/packages/discovery/lib/syntax.d.ts +17 -0
- package/dist/packages/discovery/lib/syntax.js +39 -0
- package/dist/packages/findings/index.d.ts +106 -0
- package/dist/packages/findings/index.js +254 -0
- package/dist/packages/findings/lib/scope.d.ts +38 -0
- package/dist/packages/findings/lib/scope.js +110 -0
- package/dist/packages/parallel/index.d.ts +2 -0
- package/dist/packages/parallel/index.js +5 -0
- package/dist/packages/parallel/lib/protocol.d.ts +15 -0
- package/dist/packages/parallel/lib/protocol.js +2 -0
- package/dist/packages/parallel/lib/runner.d.ts +36 -0
- package/dist/packages/parallel/lib/runner.js +96 -0
- package/dist/packages/parallel/lib/schedule.d.ts +16 -0
- package/dist/packages/parallel/lib/schedule.js +52 -0
- package/dist/packages/parallel/lib/worker.d.ts +1 -0
- package/dist/packages/parallel/lib/worker.js +18 -0
- package/dist/packages/policy/index.d.ts +36 -0
- package/dist/packages/policy/index.js +22 -0
- package/dist/packages/report/explain.d.ts +26 -0
- package/dist/packages/report/explain.js +99 -0
- package/dist/packages/report/index.d.ts +210 -0
- package/dist/packages/report/index.js +388 -0
- package/dist/packages/report/terminal.d.ts +11 -0
- package/dist/packages/report/terminal.js +331 -0
- package/dist/packages/source-store/fixture.d.ts +22 -0
- package/dist/packages/source-store/fixture.js +58 -0
- package/dist/packages/source-store/index.d.ts +278 -0
- package/dist/packages/source-store/index.js +322 -0
- package/dist/packages/source-store/lib/addresses.d.ts +6 -0
- package/dist/packages/source-store/lib/addresses.js +91 -0
- package/dist/packages/source-store/lib/connection.d.ts +15 -0
- package/dist/packages/source-store/lib/connection.js +135 -0
- package/dist/packages/source-store/lib/content-types.d.ts +16 -0
- package/dist/packages/source-store/lib/content-types.js +75 -0
- package/dist/packages/source-store/lib/data-urls.d.ts +10 -0
- package/dist/packages/source-store/lib/data-urls.js +85 -0
- package/dist/packages/source-store/lib/deadline.d.ts +18 -0
- package/dist/packages/source-store/lib/deadline.js +44 -0
- package/dist/packages/source-store/lib/exchange.d.ts +29 -0
- package/dist/packages/source-store/lib/exchange.js +112 -0
- package/dist/packages/source-store/lib/integrity.d.ts +19 -0
- package/dist/packages/source-store/lib/integrity.js +86 -0
- package/dist/packages/source-store/lib/outcomes.d.ts +12 -0
- package/dist/packages/source-store/lib/outcomes.js +22 -0
- package/dist/packages/source-store/lib/proxy.d.ts +11 -0
- package/dist/packages/source-store/lib/proxy.js +53 -0
- package/dist/packages/source-store/lib/session.d.ts +12 -0
- package/dist/packages/source-store/lib/session.js +139 -0
- package/dist/packages/source-store/lib/urls.d.ts +6 -0
- package/dist/packages/source-store/lib/urls.js +15 -0
- package/dist/packages/source-store/resolve.d.ts +59 -0
- package/dist/packages/source-store/resolve.js +45 -0
- package/dist/scan.d.ts +65 -0
- package/dist/scan.js +592 -0
- package/package.json +95 -4
- package/schema/config.schema.json +133 -0
- package/schema/report.schema.json +1905 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
// Function summaries (§5 Resolver capabilities 3): a function is instantiated per call site with the values of the
|
|
2
|
+
// arguments there. A **frame** is one instantiation: the function, the call site (or, when a cap merges or widens
|
|
3
|
+
// calls, the sites) its parameters take their arguments from, and for each site the frame the arguments are evaluated
|
|
4
|
+
// in. So a write in `c` of the chain `a → b → c` is evaluated in the frame of `c` at its call in `b`, whose arguments
|
|
5
|
+
// are evaluated in the frame of `b` at its call in `a`, and so on out to a call that no function around it needs.
|
|
6
|
+
//
|
|
7
|
+
// Which function a call calls is read from the syntax here: a function or arrow expression called directly (an IIFE),
|
|
8
|
+
// a name whose declaration or definitions are functions, either of those through `.call` or `.apply`, a `new` of a
|
|
9
|
+
// class, which runs its constructor, and the callback an iteration native calls once per element of a constant
|
|
10
|
+
// collection (§5 Resolver capabilities 4): `names.forEach(n => fill(proto, n, w))`. A call whose callee the syntax
|
|
11
|
+
// doesn't name (a method, a callback, a closure, a bound function, `Reflect.apply`) is resolved from its callee's values
|
|
12
|
+
// (`global-object.ts`). A frame's `outer` is the frame of the code a function closes over, and its calls' receivers are
|
|
13
|
+
// its `this`.
|
|
14
|
+
//
|
|
15
|
+
// Loops (§5 Resolver capabilities 4): a loop statement is a context too. An **iteration** is a frame of the loop, one per
|
|
16
|
+
// element or counter value it unrolls, that runs within a frame of the code around the loop (`outer`). So is the frame
|
|
17
|
+
// of an iteration native's callback, which runs within the frame of the code that calls the native.
|
|
18
|
+
//
|
|
19
|
+
// Caps (§18 Context policy): a chain deeper than the `depth` cap's frames, or one that reaches a function already on
|
|
20
|
+
// it (recursion), widens: the call's function runs in its widened frame, whose parameters take the arguments of every
|
|
21
|
+
// call of it, evaluated with no frame around them. More than the `contexts` cap's frames of one function merge: the
|
|
22
|
+
// calls that carry no native are merged first. A widened or merged call that carries the global object, a native
|
|
23
|
+
// holder or a native member raises `analysis-budget` (Principle 5). Every cut is counted.
|
|
24
|
+
import { createHash } from 'node:crypto';
|
|
25
|
+
import { calleeExpression, walkTree } from "./ast.js";
|
|
26
|
+
const functionTypes = new Set(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression']);
|
|
27
|
+
export const isFunction = (node) => functionTypes.has(node.type);
|
|
28
|
+
const loopTypes = new Set([
|
|
29
|
+
'ForStatement',
|
|
30
|
+
'ForInStatement',
|
|
31
|
+
'ForOfStatement',
|
|
32
|
+
'WhileStatement',
|
|
33
|
+
'DoWhileStatement',
|
|
34
|
+
]);
|
|
35
|
+
/** A loop statement, which may be unrolled (§5 Resolver capabilities 4). */
|
|
36
|
+
export const isLoop = (node) => loopTypes.has(node.type);
|
|
37
|
+
/**
|
|
38
|
+
* The natives that call a callback once per element of an array (ECMA-262), by method name, with the index of the
|
|
39
|
+
* callback's parameter that takes the element. `from` is `Array.from(items, mapfn)`.
|
|
40
|
+
*/
|
|
41
|
+
export const iterationNatives = new Map([
|
|
42
|
+
['forEach', { element: 0 }],
|
|
43
|
+
['map', { element: 0 }],
|
|
44
|
+
['filter', { element: 0 }],
|
|
45
|
+
['some', { element: 0 }],
|
|
46
|
+
['every', { element: 0 }],
|
|
47
|
+
['find', { element: 0 }],
|
|
48
|
+
['findIndex', { element: 0 }],
|
|
49
|
+
['findLast', { element: 0 }],
|
|
50
|
+
['findLastIndex', { element: 0 }],
|
|
51
|
+
['flatMap', { element: 0 }],
|
|
52
|
+
['reduce', { element: 1 }],
|
|
53
|
+
['reduceRight', { element: 1 }],
|
|
54
|
+
['from', { element: 0 }],
|
|
55
|
+
]);
|
|
56
|
+
/**
|
|
57
|
+
* The calls in a program, and the functions they call. `guardsAt(parent, child)` gives the `typeof` guards a branch
|
|
58
|
+
* adds, as the resolver's walk takes them.
|
|
59
|
+
*/
|
|
60
|
+
export function callGraph(program, scope, definitions, guardsAt) {
|
|
61
|
+
let index;
|
|
62
|
+
/**
|
|
63
|
+
* The functions a callee can be, and whether it can be something else too. A class is its constructor (`constructorOf`),
|
|
64
|
+
* and only a `new` (`construct`) calls it: calling a class throws.
|
|
65
|
+
*/
|
|
66
|
+
function functionsOf(callee, construct = false) {
|
|
67
|
+
const expression = calleeExpression(callee);
|
|
68
|
+
if (isFunction(expression))
|
|
69
|
+
return { fns: [expression], open: false };
|
|
70
|
+
if (isClass(expression))
|
|
71
|
+
return construct ? { fns: [constructorOf(expression)], open: false } : { fns: [], open: true };
|
|
72
|
+
if (expression.type !== 'Identifier')
|
|
73
|
+
return { fns: [], open: true };
|
|
74
|
+
const key = definitions.keyOf(expression);
|
|
75
|
+
if (key === undefined)
|
|
76
|
+
return { fns: [], open: true };
|
|
77
|
+
// An import binding of a link group names the variable it is by that variable's own name (§6).
|
|
78
|
+
const name = key.name;
|
|
79
|
+
const fns = [];
|
|
80
|
+
let open = false;
|
|
81
|
+
if (key.scope === 'global') {
|
|
82
|
+
const binding = scope.resolve(expression);
|
|
83
|
+
const declaration = binding === 'local' ? undefined : binding.declaration;
|
|
84
|
+
if (declaration !== undefined && declaration.kind !== 'var' && isFunction(declaration.node))
|
|
85
|
+
fns.push(declaration.node);
|
|
86
|
+
else
|
|
87
|
+
open = true;
|
|
88
|
+
}
|
|
89
|
+
else if (isFunction(key.scope)) {
|
|
90
|
+
const id = key.scope['id'];
|
|
91
|
+
// The function's own name, or a parameter or `arguments`, which the call binds.
|
|
92
|
+
if (id?.['name'] === name && key.scope.type !== 'ArrowFunctionExpression')
|
|
93
|
+
fns.push(key.scope);
|
|
94
|
+
else
|
|
95
|
+
open = true;
|
|
96
|
+
}
|
|
97
|
+
else if (isClass(key.scope)) {
|
|
98
|
+
const id = key.scope['id'];
|
|
99
|
+
if (id?.['name'] === name && construct)
|
|
100
|
+
fns.push(constructorOf(key.scope));
|
|
101
|
+
else
|
|
102
|
+
open = true;
|
|
103
|
+
}
|
|
104
|
+
for (const definition of definitions.of(key)) {
|
|
105
|
+
const source = definition.source;
|
|
106
|
+
if (source !== undefined && definition.pattern.type === 'Identifier' && isFunction(source))
|
|
107
|
+
fns.push(source);
|
|
108
|
+
else if (source !== undefined && definition.pattern.type === 'Identifier' && isClass(source) && construct) {
|
|
109
|
+
fns.push(constructorOf(source));
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
open = true;
|
|
113
|
+
}
|
|
114
|
+
// A `var` or `let` that nothing defines, or a declaration with no function, is no known function.
|
|
115
|
+
if (fns.length === 0)
|
|
116
|
+
open = true;
|
|
117
|
+
return { fns: [...new Set(fns)], open };
|
|
118
|
+
}
|
|
119
|
+
function built() {
|
|
120
|
+
if (index !== undefined)
|
|
121
|
+
return index;
|
|
122
|
+
const sites = new Map();
|
|
123
|
+
const open = new Set();
|
|
124
|
+
const calls = new Map();
|
|
125
|
+
const functions = [];
|
|
126
|
+
const info = new Map();
|
|
127
|
+
const all = [];
|
|
128
|
+
// The functions around the walk, and the code whose `this` is open, and the guards, as the resolver keeps them.
|
|
129
|
+
const around = [];
|
|
130
|
+
// The loops and functions around the walk, innermost last.
|
|
131
|
+
const contexts = [];
|
|
132
|
+
let boundaries = 0;
|
|
133
|
+
const frames = [];
|
|
134
|
+
const guards = new Map();
|
|
135
|
+
walkTree(program, {
|
|
136
|
+
enter(node, parent) {
|
|
137
|
+
if (parent !== null) {
|
|
138
|
+
const guarded = guardsAt(parent, node);
|
|
139
|
+
const boundary = node.type === 'FunctionDeclaration' ||
|
|
140
|
+
node.type === 'FunctionExpression' ||
|
|
141
|
+
node.type === 'StaticBlock' ||
|
|
142
|
+
((parent.type === 'PropertyDefinition' || parent.type === 'AccessorProperty') && parent['value'] === node);
|
|
143
|
+
if (guarded.size > 0 || boundary) {
|
|
144
|
+
frames.push({ node, guards: guarded, boundary });
|
|
145
|
+
for (const [name, test] of guarded) {
|
|
146
|
+
const tests = guards.get(name);
|
|
147
|
+
if (tests === undefined)
|
|
148
|
+
guards.set(name, [test]);
|
|
149
|
+
else
|
|
150
|
+
tests.push(test);
|
|
151
|
+
}
|
|
152
|
+
if (boundary)
|
|
153
|
+
boundaries++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (isFunction(node)) {
|
|
157
|
+
functions.push(node);
|
|
158
|
+
around.push(node);
|
|
159
|
+
}
|
|
160
|
+
if (isFunction(node) || isLoop(node))
|
|
161
|
+
contexts.push(node);
|
|
162
|
+
if (node.type !== 'CallExpression' && node.type !== 'NewExpression')
|
|
163
|
+
return;
|
|
164
|
+
const within = around.at(-1);
|
|
165
|
+
const own = contextsOf(contexts, within);
|
|
166
|
+
info.set(node, { topLevel: boundaries === 0, guards: currentGuards(), within, contexts: own });
|
|
167
|
+
all.push(node);
|
|
168
|
+
for (const target of iterationsIn(node)) {
|
|
169
|
+
const site = {
|
|
170
|
+
call: node,
|
|
171
|
+
fn: target.fn,
|
|
172
|
+
args: [],
|
|
173
|
+
unlisted: 0,
|
|
174
|
+
topLevel: boundaries === 0,
|
|
175
|
+
guards: currentGuards(),
|
|
176
|
+
within,
|
|
177
|
+
contexts: own,
|
|
178
|
+
iterates: target.iterates,
|
|
179
|
+
...(target.receiver === undefined ? {} : { receiver: target.receiver }),
|
|
180
|
+
};
|
|
181
|
+
const of = calls.get(target.fn);
|
|
182
|
+
if (of === undefined)
|
|
183
|
+
calls.set(target.fn, [site]);
|
|
184
|
+
else
|
|
185
|
+
of.push(site);
|
|
186
|
+
}
|
|
187
|
+
const found = callsIn(node);
|
|
188
|
+
if (found.open)
|
|
189
|
+
open.add(node);
|
|
190
|
+
if (found.targets.length === 0)
|
|
191
|
+
return;
|
|
192
|
+
const current = currentGuards();
|
|
193
|
+
const list = [];
|
|
194
|
+
for (const target of found.targets) {
|
|
195
|
+
const site = {
|
|
196
|
+
call: node,
|
|
197
|
+
fn: target.fn,
|
|
198
|
+
args: target.args,
|
|
199
|
+
...(target.unlisted === undefined ? {} : { unlisted: target.unlisted }),
|
|
200
|
+
topLevel: boundaries === 0,
|
|
201
|
+
guards: current,
|
|
202
|
+
within,
|
|
203
|
+
contexts: own,
|
|
204
|
+
...(target.receiver === undefined ? {} : { receiver: target.receiver }),
|
|
205
|
+
};
|
|
206
|
+
list.push(site);
|
|
207
|
+
const of = calls.get(target.fn);
|
|
208
|
+
if (of === undefined)
|
|
209
|
+
calls.set(target.fn, [site]);
|
|
210
|
+
else
|
|
211
|
+
of.push(site);
|
|
212
|
+
}
|
|
213
|
+
sites.set(node, list);
|
|
214
|
+
},
|
|
215
|
+
leave(node) {
|
|
216
|
+
if (around.at(-1) === node)
|
|
217
|
+
around.pop();
|
|
218
|
+
if (contexts.at(-1) === node)
|
|
219
|
+
contexts.pop();
|
|
220
|
+
if (frames.at(-1)?.node !== node)
|
|
221
|
+
return;
|
|
222
|
+
const frame = frames.pop();
|
|
223
|
+
for (const name of frame.guards.keys())
|
|
224
|
+
guards.get(name).pop();
|
|
225
|
+
if (frame.boundary)
|
|
226
|
+
boundaries--;
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
functions.sort((a, b) => a.start - b.start);
|
|
230
|
+
return (index = { sites, open, calls, functions, info, all });
|
|
231
|
+
function currentGuards() {
|
|
232
|
+
if (guards.size === 0)
|
|
233
|
+
return new Map();
|
|
234
|
+
return new Map([...guards].flatMap(([name, tests]) => (tests.length > 0 ? [[name, tests.at(-1)]] : [])));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The callbacks a call of an iteration native calls once per element (§5 Resolver capabilities 4): `c.forEach(cb)`
|
|
239
|
+
* and the other array methods, and `Array.from(c, cb)`. Whether the collection is constant is judged when the call's
|
|
240
|
+
* frames are made.
|
|
241
|
+
*/
|
|
242
|
+
function iterationsIn(call) {
|
|
243
|
+
if (call.type !== 'CallExpression')
|
|
244
|
+
return [];
|
|
245
|
+
const callee = calleeExpression(call['callee']);
|
|
246
|
+
if (callee.type !== 'MemberExpression' || callee['computed'] === true)
|
|
247
|
+
return [];
|
|
248
|
+
const native = callee['property']['name'];
|
|
249
|
+
if (!iterationNatives.has(native))
|
|
250
|
+
return [];
|
|
251
|
+
const args = call['arguments'];
|
|
252
|
+
const object = callee['object'];
|
|
253
|
+
const from = native === 'from';
|
|
254
|
+
if (from && !(object.type === 'Identifier' && object['name'] === 'Array'))
|
|
255
|
+
return [];
|
|
256
|
+
const [collection, callback] = from ? [args[0], args[1]] : [object, args[0]];
|
|
257
|
+
if (collection === undefined || callback === undefined)
|
|
258
|
+
return [];
|
|
259
|
+
if (collection.type === 'SpreadElement' || callback.type === 'SpreadElement' || object.type === 'Super')
|
|
260
|
+
return [];
|
|
261
|
+
const expression = calleeExpression(callback);
|
|
262
|
+
const fns = isFunction(expression) ? [expression] : expression.type === 'Identifier' ? functionsOf(expression).fns : [];
|
|
263
|
+
// The callback's `this` is the native's `thisArg`, which `reduce` and `reduceRight` don't take (ECMA-262).
|
|
264
|
+
const thisArg = native === 'reduce' || native === 'reduceRight' ? undefined : args[from ? 2 : 1];
|
|
265
|
+
const receiver = thisArg === undefined ? 'none' : thisArg.type === 'SpreadElement' ? undefined : thisArg;
|
|
266
|
+
return fns.map((fn) => ({ fn, iterates: { native, collection }, ...(receiver === undefined ? {} : { receiver }) }));
|
|
267
|
+
}
|
|
268
|
+
/** The functions a call calls, with the arguments bound to their parameters. */
|
|
269
|
+
function callsIn(call) {
|
|
270
|
+
const callee = call['callee'];
|
|
271
|
+
const args = call['arguments'];
|
|
272
|
+
const spread = args.findIndex((arg) => arg.type === 'SpreadElement');
|
|
273
|
+
const listed = (list, from) => ({ args: list, ...(from < 0 ? {} : { unlisted: from }) });
|
|
274
|
+
// `f.call(t, …args)` and `f.apply(t, [ …args ])` call f with those arguments.
|
|
275
|
+
const expression = calleeExpression(callee);
|
|
276
|
+
if (call.type === 'CallExpression' &&
|
|
277
|
+
expression.type === 'MemberExpression' &&
|
|
278
|
+
expression['computed'] !== true &&
|
|
279
|
+
(expression['property']['name'] === 'call' || expression['property']['name'] === 'apply')) {
|
|
280
|
+
const object = expression['object'];
|
|
281
|
+
if (object.type !== 'Identifier' && !isFunction(calleeExpression(object)))
|
|
282
|
+
return { targets: [], open: true };
|
|
283
|
+
const { fns, open } = functionsOf(object);
|
|
284
|
+
let bound;
|
|
285
|
+
if (expression['property']['name'] === 'call') {
|
|
286
|
+
bound = listed(args.slice(1), spread < 0 ? -1 : Math.max(0, spread - 1));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
const array = args[1];
|
|
290
|
+
if (spread >= 0 && spread <= 1)
|
|
291
|
+
bound = { args: [], unlisted: 0 };
|
|
292
|
+
else if (array === undefined)
|
|
293
|
+
bound = { args: [] };
|
|
294
|
+
else if (array.type !== 'ArrayExpression')
|
|
295
|
+
bound = { args: [], unlisted: 0 };
|
|
296
|
+
else {
|
|
297
|
+
const elements = array['elements'];
|
|
298
|
+
const hole = elements.findIndex((element) => element === null || element.type === 'SpreadElement');
|
|
299
|
+
bound = listed(elements.slice(0, hole < 0 ? elements.length : hole), hole < 0 ? -1 : elements[hole] === null ? -1 : hole);
|
|
300
|
+
// A hole is `undefined`, as a missing argument is, only when nothing follows it: otherwise unlisted.
|
|
301
|
+
if (hole >= 0 && elements[hole] === null && hole < elements.length - 1)
|
|
302
|
+
bound = { args: bound.args, unlisted: hole };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// A `thisArg` written at the call is the function's `this`, and none passes no `this`.
|
|
306
|
+
const thisArg = args[0];
|
|
307
|
+
const receiver = thisArg === undefined ? { receiver: 'none' } : thisArg.type === 'SpreadElement' ? {} : { receiver: thisArg };
|
|
308
|
+
return { targets: fns.map((fn) => ({ fn, ...bound, ...receiver })), open };
|
|
309
|
+
}
|
|
310
|
+
const construct = call.type === 'NewExpression';
|
|
311
|
+
const { fns, open } = functionsOf(callee, construct);
|
|
312
|
+
const receiver = construct ? { receiver: 'new' } : { receiver: 'none' };
|
|
313
|
+
return { targets: fns.map((fn) => ({ fn, ...listed(args, spread), ...receiver })), open };
|
|
314
|
+
}
|
|
315
|
+
/** The function whose own code holds each node asked about, or null for none. */
|
|
316
|
+
const owners = new WeakMap();
|
|
317
|
+
return {
|
|
318
|
+
sitesOf: (call) => built().sites.get(call) ?? [],
|
|
319
|
+
open: (call) => built().open.has(call) || !built().sites.has(call),
|
|
320
|
+
callsOf: (fn) => built().calls.get(fn) ?? [],
|
|
321
|
+
infoOf: (call) => built().info.get(call),
|
|
322
|
+
allCalls: () => built().all,
|
|
323
|
+
functionOf(node) {
|
|
324
|
+
const known = owners.get(node);
|
|
325
|
+
if (known !== undefined)
|
|
326
|
+
return known ?? undefined;
|
|
327
|
+
const { functions } = built();
|
|
328
|
+
let found;
|
|
329
|
+
// The innermost function that holds the node, other than the node itself.
|
|
330
|
+
for (const fn of functions) {
|
|
331
|
+
if (fn.start > node.start)
|
|
332
|
+
break;
|
|
333
|
+
if (fn !== node && fn.start <= node.start && node.end <= fn.end)
|
|
334
|
+
found = fn;
|
|
335
|
+
}
|
|
336
|
+
owners.set(node, found ?? null);
|
|
337
|
+
return found;
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const classTypes = new Set(['ClassDeclaration', 'ClassExpression']);
|
|
342
|
+
export const isClass = (node) => classTypes.has(node.type);
|
|
343
|
+
/**
|
|
344
|
+
* The code a `new` of a class runs as its constructor: its `constructor` method's function, or the class itself when it
|
|
345
|
+
* has none. Its frames are the class's constructions, whose `this` is the instance made.
|
|
346
|
+
*/
|
|
347
|
+
export function constructorOf(cls) {
|
|
348
|
+
const members = cls['body']['body'];
|
|
349
|
+
const method = members.find((member) => member.type === 'MethodDefinition' && member['kind'] === 'constructor');
|
|
350
|
+
return method === undefined ? cls : method['value'];
|
|
351
|
+
}
|
|
352
|
+
/** The loops around a node, innermost first, out to the function whose own code holds it, and that function. */
|
|
353
|
+
function contextsOf(contexts, within) {
|
|
354
|
+
const out = [];
|
|
355
|
+
for (let at = contexts.length - 1; at >= 0; at--) {
|
|
356
|
+
out.push(contexts[at]);
|
|
357
|
+
if (contexts[at] === within)
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
return out;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* The longest frame id kept as it is, in characters: a longer one stands for itself as a digest. An id names the calls
|
|
364
|
+
* out along its frame's chain, so ids are long (up to a few hundred KB on the corpus's New Relic builds at the shipped
|
|
365
|
+
* caps), and the order the resolver works through frames in breaks ties by them (`framesInDocumentOrder`), so none the
|
|
366
|
+
* shipped caps make may change. This is past those, and far under the string length a join can't exceed (about 5 × 10^8).
|
|
367
|
+
*/
|
|
368
|
+
const maxFrameIdLength = 1 << 20;
|
|
369
|
+
/**
|
|
370
|
+
* A frame id that stays bounded. A merged frame's id names every frame it merges, and a call's names its caller and
|
|
371
|
+
* closure, so under raised caps ids grew geometrically with the depth of the chain, until a join exceeded the maximum
|
|
372
|
+
* string length and threw `RangeError: Invalid string length`. Past `maxFrameIdLength` an id is replaced
|
|
373
|
+
* by its leading characters and a digest of the whole, which is as deterministic as the id, and tells frames apart as
|
|
374
|
+
* well (128 bits).
|
|
375
|
+
*/
|
|
376
|
+
export function frameId(id) {
|
|
377
|
+
if (id.length <= maxFrameIdLength)
|
|
378
|
+
return id;
|
|
379
|
+
return `${id.slice(0, 32)}~${createHash('sha256').update(id).digest('base64url').slice(0, 22)}`;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* A frame for one call of `site.fn`, with the frame its arguments are evaluated in. `closure` is the frame the function
|
|
383
|
+
* was created in, which it reads its free variables through (`outer`), and `receiverKey` tells apart the frames of one
|
|
384
|
+
* call whose receivers differ: a method is instantiated once per receiver object (§5 Resolver capabilities 5).
|
|
385
|
+
*/
|
|
386
|
+
export function callFrame(site, caller, closure, receiverKey) {
|
|
387
|
+
const binding = {
|
|
388
|
+
site,
|
|
389
|
+
caller,
|
|
390
|
+
...(closure === undefined ? {} : { closure }),
|
|
391
|
+
...(receiverKey === undefined ? {} : { receiverKey }),
|
|
392
|
+
};
|
|
393
|
+
const id = frameId(`${site.call.start}:${site.fn.start}` +
|
|
394
|
+
(receiverKey === undefined ? '' : `/${receiverKey}`) +
|
|
395
|
+
(closure === undefined ? '' : `^${closure.id}`) +
|
|
396
|
+
(caller === undefined ? '' : `<${caller.id}`));
|
|
397
|
+
return {
|
|
398
|
+
fn: site.fn,
|
|
399
|
+
bindings: [binding],
|
|
400
|
+
id,
|
|
401
|
+
depth: Math.max(caller?.depth ?? 0, closure?.depth ?? 0) + 1,
|
|
402
|
+
how: 'call',
|
|
403
|
+
...(closure === undefined ? {} : { outer: closure }),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Frames in document order: by where their first call is, then the function called, then their ids, which name the
|
|
408
|
+
* positions of the calls out along their chains. Whatever order the resolver found them in, the order it works through
|
|
409
|
+
* them in is this one, so results are independent of visit order (§18 Context policy).
|
|
410
|
+
*/
|
|
411
|
+
export function framesInDocumentOrder(frames) {
|
|
412
|
+
const key = (frame) => frame.bindings[0]?.site.call.start ?? -1;
|
|
413
|
+
return [...frames].sort((a, b) => key(a) - key(b) || a.fn.start - b.fn.start || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
414
|
+
}
|
|
415
|
+
/** Call sites in document order: by where the call is, then the function called. */
|
|
416
|
+
export const sitesInDocumentOrder = (sites) => [...sites].sort((a, b) => a.call.start - b.call.start || a.fn.start - b.fn.start);
|
|
417
|
+
/** The widened frame of a function: every call of it, with the arguments evaluated in no frame (above). */
|
|
418
|
+
export function widenedFrame(fn, calls) {
|
|
419
|
+
const bindings = sitesInDocumentOrder(calls).map((site) => ({ site, caller: undefined }));
|
|
420
|
+
return { fn, bindings, id: `w${fn.start}`, depth: 1, how: 'widened' };
|
|
421
|
+
}
|
|
422
|
+
/** Frames of one function merged into one, whose parameters take the arguments of every one of their calls. */
|
|
423
|
+
export function mergedFrame(fn, unordered) {
|
|
424
|
+
const frames = framesInDocumentOrder(unordered);
|
|
425
|
+
const bindings = frames.flatMap((frame) => frame.bindings);
|
|
426
|
+
// The frames share a closure only when every one was created in the same frame.
|
|
427
|
+
const [first, ...rest] = frames.map((frame) => frame.outer);
|
|
428
|
+
const outer = rest.every((each) => each === first) ? first : undefined;
|
|
429
|
+
return {
|
|
430
|
+
fn,
|
|
431
|
+
bindings,
|
|
432
|
+
id: mergedId(fn, frames),
|
|
433
|
+
depth: Math.max(...frames.map((frame) => frame.depth)),
|
|
434
|
+
how: 'merged',
|
|
435
|
+
...(outer === undefined ? {} : { outer }),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/** The id of the frame merging `frames`: theirs in order, or a digest of them when they are too long to join (`frameId`). */
|
|
439
|
+
function mergedId(fn, frames) {
|
|
440
|
+
const head = `m${fn.start}(`;
|
|
441
|
+
let length = head.length + 1;
|
|
442
|
+
for (const frame of frames)
|
|
443
|
+
length += frame.id.length + 1;
|
|
444
|
+
if (length <= maxFrameIdLength)
|
|
445
|
+
return `${head}${frames.map((frame) => frame.id).join(',')})`;
|
|
446
|
+
const digest = createHash('sha256').update(head);
|
|
447
|
+
for (const frame of frames)
|
|
448
|
+
digest.update(frame.id).update(',');
|
|
449
|
+
return `${head.slice(0, 32)}~${digest.digest('base64url').slice(0, 22)}`;
|
|
450
|
+
}
|
|
451
|
+
/** Whether `fn` is instantiated in `frame` or a frame out from it. */
|
|
452
|
+
export function onChain(frame, fn) {
|
|
453
|
+
const seen = new Set();
|
|
454
|
+
const pending = frame === undefined ? [] : [frame];
|
|
455
|
+
while (pending.length > 0) {
|
|
456
|
+
const next = pending.pop();
|
|
457
|
+
if (seen.has(next))
|
|
458
|
+
continue;
|
|
459
|
+
seen.add(next);
|
|
460
|
+
if (next.fn === fn)
|
|
461
|
+
return true;
|
|
462
|
+
for (const { caller } of next.bindings)
|
|
463
|
+
if (caller !== undefined)
|
|
464
|
+
pending.push(caller);
|
|
465
|
+
if (next.outer !== undefined)
|
|
466
|
+
pending.push(next.outer);
|
|
467
|
+
}
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* The frame `frame` runs `code` in: `frame` itself or one it runs within (`outer`), out through loops and iteration
|
|
472
|
+
* natives' callbacks. Undefined when none of them instantiates `code`.
|
|
473
|
+
*/
|
|
474
|
+
export function frameOf(frame, code) {
|
|
475
|
+
for (let at = frame; at !== undefined; at = at.outer)
|
|
476
|
+
if (at.fn === code)
|
|
477
|
+
return at;
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
/** The loop or call whose iterations the iteration cap cut, when `frame` is one of the iterations done, or runs in one. */
|
|
481
|
+
export function cutIn(frame) {
|
|
482
|
+
const seen = new Set();
|
|
483
|
+
const pending = frame === undefined ? [] : [frame];
|
|
484
|
+
while (pending.length > 0) {
|
|
485
|
+
const next = pending.pop();
|
|
486
|
+
if (seen.has(next))
|
|
487
|
+
continue;
|
|
488
|
+
seen.add(next);
|
|
489
|
+
if (next.cut !== undefined)
|
|
490
|
+
return next.cut;
|
|
491
|
+
for (const { caller } of next.bindings)
|
|
492
|
+
if (caller !== undefined)
|
|
493
|
+
pending.push(caller);
|
|
494
|
+
if (next.outer !== undefined)
|
|
495
|
+
pending.push(next.outer);
|
|
496
|
+
}
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|