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.
Files changed (221) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +61 -0
  3. package/README.md +183 -2
  4. package/catalog/catalog.json +552211 -0
  5. package/catalog/catalog.schema.json +787 -0
  6. package/catalog/changelog.json +3 -0
  7. package/dist/bin/monkeypatchlist.d.ts +2 -0
  8. package/dist/bin/monkeypatchlist.js +3 -0
  9. package/dist/catalog.d.ts +561 -0
  10. package/dist/catalog.js +58 -0
  11. package/dist/cli.d.ts +84 -0
  12. package/dist/cli.js +470 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +1 -0
  15. package/dist/lockfile-input.d.ts +9 -0
  16. package/dist/lockfile-input.js +91 -0
  17. package/dist/packages/analysis/binding.d.ts +2 -0
  18. package/dist/packages/analysis/binding.js +5 -0
  19. package/dist/packages/analysis/index.d.ts +10 -0
  20. package/dist/packages/analysis/index.js +13 -0
  21. package/dist/packages/analysis/job.d.ts +33 -0
  22. package/dist/packages/analysis/job.js +85 -0
  23. package/dist/packages/analysis/lib/absence-tags.d.ts +34 -0
  24. package/dist/packages/analysis/lib/absence-tags.js +340 -0
  25. package/dist/packages/analysis/lib/ast.d.ts +28 -0
  26. package/dist/packages/analysis/lib/ast.js +64 -0
  27. package/dist/packages/analysis/lib/binding.d.ts +36 -0
  28. package/dist/packages/analysis/lib/binding.js +66 -0
  29. package/dist/packages/analysis/lib/budget.d.ts +80 -0
  30. package/dist/packages/analysis/lib/budget.js +31 -0
  31. package/dist/packages/analysis/lib/catch-arms.d.ts +19 -0
  32. package/dist/packages/analysis/lib/catch-arms.js +351 -0
  33. package/dist/packages/analysis/lib/chunk-loads.d.ts +160 -0
  34. package/dist/packages/analysis/lib/chunk-loads.js +882 -0
  35. package/dist/packages/analysis/lib/chunk-registry.d.ts +45 -0
  36. package/dist/packages/analysis/lib/chunk-registry.js +334 -0
  37. package/dist/packages/analysis/lib/code-routes.d.ts +52 -0
  38. package/dist/packages/analysis/lib/code-routes.js +921 -0
  39. package/dist/packages/analysis/lib/collisions.d.ts +22 -0
  40. package/dist/packages/analysis/lib/collisions.js +103 -0
  41. package/dist/packages/analysis/lib/definitions.d.ts +76 -0
  42. package/dist/packages/analysis/lib/definitions.js +782 -0
  43. package/dist/packages/analysis/lib/early-exits.d.ts +39 -0
  44. package/dist/packages/analysis/lib/early-exits.js +161 -0
  45. package/dist/packages/analysis/lib/effect.d.ts +138 -0
  46. package/dist/packages/analysis/lib/effect.js +800 -0
  47. package/dist/packages/analysis/lib/global-object.d.ts +581 -0
  48. package/dist/packages/analysis/lib/global-object.js +8240 -0
  49. package/dist/packages/analysis/lib/keys.d.ts +33 -0
  50. package/dist/packages/analysis/lib/keys.js +82 -0
  51. package/dist/packages/analysis/lib/line-starts.d.ts +41 -0
  52. package/dist/packages/analysis/lib/line-starts.js +79 -0
  53. package/dist/packages/analysis/lib/link-group.d.ts +51 -0
  54. package/dist/packages/analysis/lib/link-group.js +487 -0
  55. package/dist/packages/analysis/lib/module-links.d.ts +117 -0
  56. package/dist/packages/analysis/lib/module-links.js +1060 -0
  57. package/dist/packages/analysis/lib/nested-code.d.ts +38 -0
  58. package/dist/packages/analysis/lib/nested-code.js +200 -0
  59. package/dist/packages/analysis/lib/numeric.d.ts +16 -0
  60. package/dist/packages/analysis/lib/numeric.js +216 -0
  61. package/dist/packages/analysis/lib/once-flags.d.ts +12 -0
  62. package/dist/packages/analysis/lib/once-flags.js +190 -0
  63. package/dist/packages/analysis/lib/parameter-arguments.d.ts +12 -0
  64. package/dist/packages/analysis/lib/parameter-arguments.js +34 -0
  65. package/dist/packages/analysis/lib/parse.d.ts +10 -0
  66. package/dist/packages/analysis/lib/parse.js +39 -0
  67. package/dist/packages/analysis/lib/partial.d.ts +60 -0
  68. package/dist/packages/analysis/lib/partial.js +518 -0
  69. package/dist/packages/analysis/lib/print.d.ts +3 -0
  70. package/dist/packages/analysis/lib/print.js +150 -0
  71. package/dist/packages/analysis/lib/reachability.d.ts +137 -0
  72. package/dist/packages/analysis/lib/reachability.js +1295 -0
  73. package/dist/packages/analysis/lib/resolver.d.ts +13 -0
  74. package/dist/packages/analysis/lib/resolver.js +2886 -0
  75. package/dist/packages/analysis/lib/restores.d.ts +42 -0
  76. package/dist/packages/analysis/lib/restores.js +482 -0
  77. package/dist/packages/analysis/lib/rewritten-deletes.d.ts +6 -0
  78. package/dist/packages/analysis/lib/rewritten-deletes.js +142 -0
  79. package/dist/packages/analysis/lib/scope.d.ts +110 -0
  80. package/dist/packages/analysis/lib/scope.js +443 -0
  81. package/dist/packages/analysis/lib/script-loads.d.ts +56 -0
  82. package/dist/packages/analysis/lib/script-loads.js +359 -0
  83. package/dist/packages/analysis/lib/summaries.d.ts +177 -0
  84. package/dist/packages/analysis/lib/summaries.js +498 -0
  85. package/dist/packages/analysis/lib/types.d.ts +632 -0
  86. package/dist/packages/analysis/lib/types.js +128 -0
  87. package/dist/packages/analysis/lib/wrap.d.ts +34 -0
  88. package/dist/packages/analysis/lib/wrap.js +407 -0
  89. package/dist/packages/analysis/lib/writes.d.ts +358 -0
  90. package/dist/packages/analysis/lib/writes.js +1612 -0
  91. package/dist/packages/analysis/visit-order.d.ts +3 -0
  92. package/dist/packages/analysis/visit-order.js +23 -0
  93. package/dist/packages/attribution/index.d.ts +233 -0
  94. package/dist/packages/attribution/index.js +444 -0
  95. package/dist/packages/attribution/lib/containing-package.d.ts +76 -0
  96. package/dist/packages/attribution/lib/containing-package.js +304 -0
  97. package/dist/packages/attribution/lib/first-party.d.ts +37 -0
  98. package/dist/packages/attribution/lib/first-party.js +153 -0
  99. package/dist/packages/attribution/lib/lockfile-graph.d.ts +99 -0
  100. package/dist/packages/attribution/lib/lockfile-graph.js +158 -0
  101. package/dist/packages/attribution/lib/lockfile.d.ts +33 -0
  102. package/dist/packages/attribution/lib/lockfile.js +330 -0
  103. package/dist/packages/attribution/lib/path-rules.d.ts +29 -0
  104. package/dist/packages/attribution/lib/path-rules.js +84 -0
  105. package/dist/packages/attribution/lib/pnpm.d.ts +4 -0
  106. package/dist/packages/attribution/lib/pnpm.js +133 -0
  107. package/dist/packages/attribution/lib/remote-url.d.ts +7 -0
  108. package/dist/packages/attribution/lib/remote-url.js +24 -0
  109. package/dist/packages/attribution/lib/source-map.d.ts +51 -0
  110. package/dist/packages/attribution/lib/source-map.js +125 -0
  111. package/dist/packages/attribution/lib/yaml.d.ts +9 -0
  112. package/dist/packages/attribution/lib/yaml.js +277 -0
  113. package/dist/packages/attribution/lib/yarn.d.ts +7 -0
  114. package/dist/packages/attribution/lib/yarn.js +227 -0
  115. package/dist/packages/baseline/index.d.ts +45 -0
  116. package/dist/packages/baseline/index.js +41 -0
  117. package/dist/packages/baseline/lib/error.d.ts +15 -0
  118. package/dist/packages/baseline/lib/error.js +25 -0
  119. package/dist/packages/baseline/lib/format.d.ts +49 -0
  120. package/dist/packages/baseline/lib/format.js +22 -0
  121. package/dist/packages/baseline/lib/migrations.d.ts +19 -0
  122. package/dist/packages/baseline/lib/migrations.js +7 -0
  123. package/dist/packages/baseline/lib/previous.d.ts +23 -0
  124. package/dist/packages/baseline/lib/previous.js +64 -0
  125. package/dist/packages/baseline/lib/read.d.ts +39 -0
  126. package/dist/packages/baseline/lib/read.js +219 -0
  127. package/dist/packages/baseline/lib/report.d.ts +25 -0
  128. package/dist/packages/baseline/lib/report.js +104 -0
  129. package/dist/packages/baseline/lib/skew.d.ts +51 -0
  130. package/dist/packages/baseline/lib/skew.js +75 -0
  131. package/dist/packages/compare/index.d.ts +120 -0
  132. package/dist/packages/compare/index.js +274 -0
  133. package/dist/packages/compare/lib/catalog-notes.d.ts +73 -0
  134. package/dist/packages/compare/lib/catalog-notes.js +224 -0
  135. package/dist/packages/completeness/index.d.ts +246 -0
  136. package/dist/packages/completeness/index.js +249 -0
  137. package/dist/packages/confidence/index.d.ts +54 -0
  138. package/dist/packages/confidence/index.js +71 -0
  139. package/dist/packages/config/index.d.ts +107 -0
  140. package/dist/packages/config/index.js +95 -0
  141. package/dist/packages/config/lib/command-line.d.ts +23 -0
  142. package/dist/packages/config/lib/command-line.js +239 -0
  143. package/dist/packages/config/lib/config-file.d.ts +11 -0
  144. package/dist/packages/config/lib/config-file.js +119 -0
  145. package/dist/packages/config/lib/scan-config-id.d.ts +28 -0
  146. package/dist/packages/config/lib/scan-config-id.js +54 -0
  147. package/dist/packages/config/lib/schema.d.ts +2 -0
  148. package/dist/packages/config/lib/schema.js +31 -0
  149. package/dist/packages/config/lib/types.d.ts +64 -0
  150. package/dist/packages/config/lib/types.js +6 -0
  151. package/dist/packages/config/lib/urls.d.ts +6 -0
  152. package/dist/packages/config/lib/urls.js +9 -0
  153. package/dist/packages/discovery/index.d.ts +277 -0
  154. package/dist/packages/discovery/index.js +1950 -0
  155. package/dist/packages/discovery/lib/import-map.d.ts +68 -0
  156. package/dist/packages/discovery/lib/import-map.js +180 -0
  157. package/dist/packages/discovery/lib/requests.d.ts +58 -0
  158. package/dist/packages/discovery/lib/requests.js +591 -0
  159. package/dist/packages/discovery/lib/scopes.d.ts +6 -0
  160. package/dist/packages/discovery/lib/scopes.js +286 -0
  161. package/dist/packages/discovery/lib/source-maps.d.ts +56 -0
  162. package/dist/packages/discovery/lib/source-maps.js +209 -0
  163. package/dist/packages/discovery/lib/swept-code.d.ts +27 -0
  164. package/dist/packages/discovery/lib/swept-code.js +107 -0
  165. package/dist/packages/discovery/lib/syntax.d.ts +17 -0
  166. package/dist/packages/discovery/lib/syntax.js +39 -0
  167. package/dist/packages/findings/index.d.ts +106 -0
  168. package/dist/packages/findings/index.js +254 -0
  169. package/dist/packages/findings/lib/scope.d.ts +38 -0
  170. package/dist/packages/findings/lib/scope.js +110 -0
  171. package/dist/packages/parallel/index.d.ts +2 -0
  172. package/dist/packages/parallel/index.js +5 -0
  173. package/dist/packages/parallel/lib/protocol.d.ts +15 -0
  174. package/dist/packages/parallel/lib/protocol.js +2 -0
  175. package/dist/packages/parallel/lib/runner.d.ts +36 -0
  176. package/dist/packages/parallel/lib/runner.js +96 -0
  177. package/dist/packages/parallel/lib/schedule.d.ts +16 -0
  178. package/dist/packages/parallel/lib/schedule.js +52 -0
  179. package/dist/packages/parallel/lib/worker.d.ts +1 -0
  180. package/dist/packages/parallel/lib/worker.js +18 -0
  181. package/dist/packages/policy/index.d.ts +36 -0
  182. package/dist/packages/policy/index.js +22 -0
  183. package/dist/packages/report/explain.d.ts +26 -0
  184. package/dist/packages/report/explain.js +99 -0
  185. package/dist/packages/report/index.d.ts +210 -0
  186. package/dist/packages/report/index.js +388 -0
  187. package/dist/packages/report/terminal.d.ts +11 -0
  188. package/dist/packages/report/terminal.js +331 -0
  189. package/dist/packages/source-store/fixture.d.ts +22 -0
  190. package/dist/packages/source-store/fixture.js +58 -0
  191. package/dist/packages/source-store/index.d.ts +278 -0
  192. package/dist/packages/source-store/index.js +322 -0
  193. package/dist/packages/source-store/lib/addresses.d.ts +6 -0
  194. package/dist/packages/source-store/lib/addresses.js +91 -0
  195. package/dist/packages/source-store/lib/connection.d.ts +15 -0
  196. package/dist/packages/source-store/lib/connection.js +135 -0
  197. package/dist/packages/source-store/lib/content-types.d.ts +16 -0
  198. package/dist/packages/source-store/lib/content-types.js +75 -0
  199. package/dist/packages/source-store/lib/data-urls.d.ts +10 -0
  200. package/dist/packages/source-store/lib/data-urls.js +85 -0
  201. package/dist/packages/source-store/lib/deadline.d.ts +18 -0
  202. package/dist/packages/source-store/lib/deadline.js +44 -0
  203. package/dist/packages/source-store/lib/exchange.d.ts +29 -0
  204. package/dist/packages/source-store/lib/exchange.js +112 -0
  205. package/dist/packages/source-store/lib/integrity.d.ts +19 -0
  206. package/dist/packages/source-store/lib/integrity.js +86 -0
  207. package/dist/packages/source-store/lib/outcomes.d.ts +12 -0
  208. package/dist/packages/source-store/lib/outcomes.js +22 -0
  209. package/dist/packages/source-store/lib/proxy.d.ts +11 -0
  210. package/dist/packages/source-store/lib/proxy.js +53 -0
  211. package/dist/packages/source-store/lib/session.d.ts +12 -0
  212. package/dist/packages/source-store/lib/session.js +139 -0
  213. package/dist/packages/source-store/lib/urls.d.ts +6 -0
  214. package/dist/packages/source-store/lib/urls.js +15 -0
  215. package/dist/packages/source-store/resolve.d.ts +59 -0
  216. package/dist/packages/source-store/resolve.js +45 -0
  217. package/dist/scan.d.ts +65 -0
  218. package/dist/scan.js +592 -0
  219. package/package.json +95 -4
  220. package/schema/config.schema.json +133 -0
  221. package/schema/report.schema.json +1905 -0
@@ -0,0 +1,42 @@
1
+ import type { Realm, SerializedCatalog } from '../../../catalog.ts';
2
+ import type { AstNode } from './ast.ts';
3
+ import type { Definitions } from './definitions.ts';
4
+ import { type GlobalResolution } from './global-object.ts';
5
+ import type { Frame } from './summaries.ts';
6
+ import type { Write } from './writes.ts';
7
+ export interface RestoreContext {
8
+ resolution: GlobalResolution;
9
+ definitions: Definitions;
10
+ catalog: SerializedCatalog;
11
+ realm: Realm;
12
+ /** The realm's global holder, e.g. `Window`. */
13
+ global: string;
14
+ /** The source text, to compare a guard's operand with the slot the restore writes. */
15
+ text: string;
16
+ }
17
+ /**
18
+ * A write that puts back the native of its slot on some path. `proven` says it does on every path. `reads` are the
19
+ * native reads its value came from, which locate its chain. `guard` and `leavesOwnShadow` are its `restore` step's.
20
+ */
21
+ export interface Restore {
22
+ proven: boolean;
23
+ reads: AstNode[];
24
+ guard: 'identity' | 'none';
25
+ leavesOwnShadow: boolean;
26
+ }
27
+ /** The slot a restore writes: the holder written, its target, and the inherited member a shadowing write shadows. */
28
+ export interface RestoredSlot {
29
+ name: string;
30
+ shadows?: string | undefined;
31
+ }
32
+ /**
33
+ * Judges restores over one program: for a write of a value and the slot it writes, the restore it is, or undefined when
34
+ * no alternative of its value is the slot's native. `ancestors` are the nodes the write is in, outermost first.
35
+ */
36
+ export declare function restoreJudge(context: RestoreContext): (write: Write, frame: Frame | undefined, slot: RestoredSlot, ancestors: readonly AstNode[]) => Restore | undefined;
37
+ /**
38
+ * Whether two code positions are in one instantiated call chain (§3 Restores): their functions (undefined at the top
39
+ * level) are the same, or one is reached from the other along resolved calls, or, for a function no resolved call
40
+ * reaches, from where it was created.
41
+ */
42
+ export declare function sameChain(a: AstNode | undefined, b: AstNode | undefined, outward: (fn: AstNode) => (AstNode | undefined)[]): boolean;
@@ -0,0 +1,482 @@
1
+ // Restores (§3 Restores): a write that puts back the unmutated native of the same written object and member. It is
2
+ // proven when, on every path, the value written is that native, however it travelled: locals, parameters, closures or
3
+ // heap fields. A define's descriptor is the native when it is `getOwnPropertyDescriptor` of the slot, or of the holder
4
+ // the catalog resolves it from, or a fresh literal carrying the saved native as `value`. A union containing
5
+ // any non-native value is not proven, except that a guard on the restore that tests the saved value narrows it:
6
+ // `if (orig)`, `if (orig != null)` and `typeof orig === 'function'` remove the nullish or non-function alternatives.
7
+ // Guards never make a proof fail.
8
+ //
9
+ // §3's dead-fallback rule is for a `||`/`??` after a `getOwnPropertyDescriptor` read of a member the catalog defines
10
+ // directly on the holder read. A fallback after a value read is a live alternative: `window.fetch = window.fetch || p`
11
+ // stays the polyfill §7 judges.
12
+ //
13
+ // Only this window's slots are restored: the same member of another window expression may be another window's by the
14
+ // time the restore runs (§3 Undecidable outcomes).
15
+ //
16
+ // A proven restore is never a finding or an occurrence: it leaves a `restore` step on the install occurrence in its
17
+ // chain, or nothing when there is none. An unproven restore whose chain has an install occurrence of the same target
18
+ // joins that install's finding, taking its effect. An install sets a value (`replace`, `wrap`,
19
+ // `descriptor-change`); a `delete` never is one. The chain is the instantiated call chain the
20
+ // native was read in: an install is in it when its write and the native read are in the same function, or one's
21
+ // function is reached from the other's along resolved calls, or, for a function no resolved call reaches, from where it
22
+ // was created (§7).
23
+ import { isHeap, sameWindow, windowOf, } from "./global-object.js";
24
+ import { parameterArguments } from "./parameter-arguments.js";
25
+ import { memberName, toPropertyKey } from "./keys.js";
26
+ /** The native functions that read a property descriptor. */
27
+ const descriptorReaders = new Set(['Object.getOwnPropertyDescriptor', 'Reflect.getOwnPropertyDescriptor']);
28
+ /** The attributes of a data descriptor. */
29
+ const attributes = new Set(['writable', 'enumerable', 'configurable']);
30
+ const functionTypes = new Set([
31
+ 'FunctionDeclaration',
32
+ 'FunctionExpression',
33
+ 'ArrowFunctionExpression',
34
+ 'ClassDeclaration',
35
+ 'ClassExpression',
36
+ ]);
37
+ /**
38
+ * Judges restores over one program: for a write of a value and the slot it writes, the restore it is, or undefined when
39
+ * no alternative of its value is the slot's native. `ancestors` are the nodes the write is in, outermost first.
40
+ */
41
+ export function restoreJudge(context) {
42
+ const { resolution, definitions } = context;
43
+ const holders = context.catalog.holders[context.realm];
44
+ const targets = context.catalog.targets[context.realm];
45
+ const target = (name) => (Object.hasOwn(targets, name) ? targets[name] : undefined);
46
+ const chainOf = (start) => {
47
+ const chain = [];
48
+ for (let at = start; at !== null; at = Object.hasOwn(holders, at) ? holders[at].proto : null)
49
+ chain.push(at);
50
+ return chain;
51
+ };
52
+ /** The catalog target a native function value's name reads: the first on the chain of the holder it is read from. */
53
+ const nativeTarget = (name) => {
54
+ const dot = name.lastIndexOf('.');
55
+ const holder = dot < 0 ? context.global : name.slice(0, dot);
56
+ const key = dot < 0 ? name : name.slice(dot + 1);
57
+ return chainOf(holder)
58
+ .map((at) => memberName(at, key))
59
+ .find((each) => target(each) !== undefined);
60
+ };
61
+ const nowhere = { guard: () => undefined, topLevel: false };
62
+ const inFrame = (frame) => ({ guard: () => undefined, topLevel: false, frame });
63
+ /** The position code owned by `owner` is evaluated at: in the frame of `at`, or one around it, that instantiates it. */
64
+ const framed = (owner, at) => {
65
+ for (let frame = at.frame; frame !== undefined; frame = frame.outer)
66
+ if (frame.fn === owner)
67
+ return inFrame(frame);
68
+ return nowhere;
69
+ };
70
+ return (write, frame, slot, ancestors) => {
71
+ const { change } = write;
72
+ const source = change.kind === 'set'
73
+ ? change.same || change.sources?.length !== 1
74
+ ? undefined
75
+ : change.sources[0]
76
+ : change.kind === 'define'
77
+ ? change.source
78
+ : undefined;
79
+ if (source === undefined)
80
+ return undefined;
81
+ // A define of the slot's own value, read where it is written, writes back what is there (§3): no restore.
82
+ if (change.kind === 'define' && change.descriptor.value?.same === true && change.descriptor.value.throughHeap !== true) {
83
+ return undefined;
84
+ }
85
+ const original = slot.shadows ?? slot.name;
86
+ const window = windowOf(write.leaves);
87
+ if (window === undefined || target(original) === undefined)
88
+ return undefined;
89
+ /**
90
+ * The reads that give a value when it is the slot's native, read through the same window, or undefined. A holder
91
+ * value (`window.XMLHttpRequest`) doesn't say which slot it was read from, and a value the catalog knows is the same
92
+ * isn't a read of the same slot (§3: `window.URL = window.URL || window.webkitURL`), so it is never the native.
93
+ */
94
+ const nativeReads = (value) => {
95
+ // The write itself has effects, so only this window is shown the same around it.
96
+ switch (value.kind) {
97
+ case 'member':
98
+ return value.target === original && same(value.leaves) ? value.sites : undefined;
99
+ case 'native':
100
+ return nativeTarget(value.name) === original && same(value.leaves) ? (value.sites ?? []) : undefined;
101
+ default:
102
+ return undefined;
103
+ }
104
+ };
105
+ /** The definitions that reach a read of a variable, each where it is evaluated, when every one has a source. */
106
+ const definitionSources = (id, at) => {
107
+ const key = definitions.keyOf(id);
108
+ if (key === undefined)
109
+ return undefined;
110
+ const reaching = definitions.reaching(id, key);
111
+ const found = reaching.definitions.flatMap((definition) => definition.pattern.type === 'Identifier' && definition.source !== undefined && !definition.logical
112
+ ? [{ node: definition.source, at: framed(definition.owner, at) }]
113
+ : []);
114
+ return found.length > 0 && found.length === reaching.definitions.length ? found : undefined;
115
+ };
116
+ /** The alternatives of an expression's value. `followed` ends a cycle of definitions. */
117
+ const alternatives = (node, at, followed) => {
118
+ switch (node.type) {
119
+ case 'ParenthesizedExpression':
120
+ case 'ChainExpression':
121
+ case 'TSAsExpression':
122
+ case 'TSNonNullExpression':
123
+ return alternatives(node['expression'], at, followed);
124
+ case 'ConditionalExpression':
125
+ return [
126
+ ...alternatives(node['consequent'], at, followed),
127
+ ...alternatives(node['alternate'], at, followed),
128
+ ];
129
+ case 'LogicalExpression':
130
+ return [
131
+ ...alternatives(node['left'], at, followed),
132
+ ...alternatives(node['right'], at, followed),
133
+ ];
134
+ case 'SequenceExpression':
135
+ return alternatives(node['expressions'].at(-1), at, followed);
136
+ case 'AssignmentExpression':
137
+ if (node['operator'] === '=')
138
+ return alternatives(node['right'], at, followed);
139
+ break;
140
+ case 'Identifier': {
141
+ const found = definitionSources(node, at);
142
+ if (found !== undefined) {
143
+ const fresh = found.filter((each) => !followed.has(each.node));
144
+ for (const each of fresh)
145
+ followed.add(each.node);
146
+ return fresh.flatMap((each) => alternatives(each.node, each.at, followed));
147
+ }
148
+ // A helper's parameter holds what its calls pass. Its values are read first; the arguments are read when they
149
+ // show a native the values can't: a holder value doesn't say the slot it was read from (`wa(t, e, n, s)` after
150
+ // `n = t[e]`), and the read in the caller does.
151
+ const own = evaluated(node, at);
152
+ if (own.every((each) => each.native))
153
+ return own;
154
+ const passed = parameterArguments(definitions, node, at);
155
+ if (passed === undefined)
156
+ return own;
157
+ const fresh = passed.filter((each) => !followed.has(each.node));
158
+ for (const each of fresh)
159
+ followed.add(each.node);
160
+ const viaArguments = fresh.flatMap((each) => alternatives(each.node, each.at, followed));
161
+ return viaArguments.some((each) => each.native) ? viaArguments : own;
162
+ }
163
+ }
164
+ return evaluated(node, at);
165
+ };
166
+ const evaluated = (node, at) => {
167
+ const values = resolution.evaluate(node, at);
168
+ if (values.length === 0)
169
+ return [{ native: false, value: { kind: 'unknown' } }];
170
+ return values.map((value) => {
171
+ const reads = value.kind === 'holder' ? slotReads(node, at, value) : nativeReads(value);
172
+ return reads === undefined ? { native: false, value } : { native: true, reads };
173
+ });
174
+ };
175
+ /**
176
+ * The read that gives a constructor or other holder value when it reads the slot itself, through the same window: a
177
+ * bare global name, or a member read whose object and key the resolution decides (`t[e]` with `t` the global object
178
+ * and `e` "WebSocket", `n.prototype.constructor`). A holder value doesn't say which slot it was read from, so the
179
+ * read's own syntax must: one the catalog knows holds the same object is another slot (§3, `window.webkitURL`).
180
+ */
181
+ const slotReads = (node, at, value) => {
182
+ if (target(original)?.holds !== value.holder || !same(value.leaves))
183
+ return undefined;
184
+ if (node.type === 'Identifier') {
185
+ const named = value.leaves.length === 1 && value.leaves[0].node === node && value.leaves[0].form === 'global-property';
186
+ return named && nativeTarget(node['name']) === original ? [node] : undefined;
187
+ }
188
+ if (node.type !== 'MemberExpression')
189
+ return undefined;
190
+ const property = node['property'];
191
+ const keys = node['computed'] === true
192
+ ? resolution.evaluate(property, at)
193
+ : property.type === 'Identifier'
194
+ ? [{ kind: 'constant', value: property['name'] }]
195
+ : [];
196
+ // Only a holder the object may be gives a holder value: an unknown alternative of it gives values of its own.
197
+ const objects = resolution.evaluate(node['object'], at).filter((object) => object.kind === 'holder');
198
+ if (keys.length === 0 || objects.length === 0)
199
+ return undefined;
200
+ const reads = objects.every((object) => keys.every((key) => key.kind === 'constant' &&
201
+ typeof key.value !== 'object' &&
202
+ chainOf(object.holder)
203
+ .map((each) => memberName(each, toPropertyKey(key.value)))
204
+ .find((each) => target(each) !== undefined) === original));
205
+ return reads ? [node] : undefined;
206
+ };
207
+ // The same window as the write's, with nothing in the write that could change which window that is.
208
+ const same = (leaves) => sameWindow(leaves, write.leaves, [write.site]);
209
+ const unknownValue = { native: false, value: { kind: 'unknown' } };
210
+ /**
211
+ * The alternatives of a descriptor (§3 Restores): `getOwnPropertyDescriptor` of the slot, or of the holder the
212
+ * unmutated catalog resolves the member from along the written object's chain, with no field written to it; or a
213
+ * fresh literal whose `value` is the slot's native, whose decided attributes equal the unmutated catalog's, or any
214
+ * attributes on an own shadow. They are followed through locals, parameters, closures and heap fields.
215
+ */
216
+ const descriptors = (node, at, followed) => {
217
+ switch (node.type) {
218
+ case 'ParenthesizedExpression':
219
+ case 'TSAsExpression':
220
+ case 'TSNonNullExpression':
221
+ return descriptors(node['expression'], at, followed);
222
+ case 'ConditionalExpression':
223
+ return [
224
+ ...descriptors(node['consequent'], at, followed),
225
+ ...descriptors(node['alternate'], at, followed),
226
+ ];
227
+ case 'LogicalExpression': {
228
+ const left = descriptors(node['left'], at, followed);
229
+ // A fallback after a descriptor read the catalog always defines is dead code.
230
+ const dead = node['operator'] !== '&&' && left.length > 0 && left.every((each) => each.defined === true);
231
+ return dead ? left : [...left, ...descriptors(node['right'], at, followed)];
232
+ }
233
+ case 'SequenceExpression':
234
+ return descriptors(node['expressions'].at(-1), at, followed);
235
+ case 'AssignmentExpression':
236
+ if (node['operator'] === '=')
237
+ return descriptors(node['right'], at, followed);
238
+ break;
239
+ case 'Identifier': {
240
+ const found = definitionSources(node, at);
241
+ if (found !== undefined)
242
+ return followedTo(found, followed, descriptors);
243
+ break;
244
+ }
245
+ case 'MemberExpression': {
246
+ const found = resolution.fieldSources(node, at);
247
+ if (found !== undefined && found.length > 0) {
248
+ return followedTo(found.map((each) => ({ node: each.node, at: each.at })), followed, descriptors);
249
+ }
250
+ break;
251
+ }
252
+ }
253
+ const values = resolution.evaluate(node, at);
254
+ if (values.length === 0)
255
+ return [unknownValue];
256
+ return values.map((value) => {
257
+ if (!isHeap(value))
258
+ return { native: false, value };
259
+ if (value.heap.kind === 'returned')
260
+ return described(value.heap) ?? { native: false, value };
261
+ if (value.heap.kind !== 'literal')
262
+ return { native: false, value };
263
+ const reads = literalReads(value.heap);
264
+ return reads === undefined ? { native: false, value } : { native: true, reads };
265
+ });
266
+ };
267
+ /** Follows each expression a definition or field write gives, once. */
268
+ const followedTo = (found, followed, next) => {
269
+ const fresh = found.filter((each) => !followed.has(each.node));
270
+ for (const each of fresh)
271
+ followed.add(each.node);
272
+ return fresh.flatMap((each) => next(each.node, each.at, followed));
273
+ };
274
+ /** A `getOwnPropertyDescriptor` result as an alternative, or undefined when it is no such call. */
275
+ const described = (heap) => {
276
+ const call = heap.node;
277
+ if (call.type !== 'CallExpression')
278
+ return undefined;
279
+ const callees = resolution.evaluate(call['callee'], heap.at);
280
+ if (callees.length === 0 || !callees.every((each) => each.kind === 'native' && descriptorReaders.has(each.name))) {
281
+ return undefined;
282
+ }
283
+ const [object, key] = call['arguments'];
284
+ if (object === undefined || key === undefined || object.type === 'SpreadElement' || key.type === 'SpreadElement')
285
+ return undefined;
286
+ const holders = resolution.evaluate(object, heap.at);
287
+ const keys = resolution.evaluate(key, heap.at);
288
+ const [holder] = holders;
289
+ const [only] = keys;
290
+ if (holders.length !== 1 || holder?.kind !== 'holder' || keys.length !== 1 || only?.kind !== 'constant')
291
+ return undefined;
292
+ const name = memberName(holder.holder, toPropertyKey(only.value));
293
+ const defined = target(name) !== undefined ? { defined: true } : {};
294
+ // A field written to the result makes it another descriptor than the native's.
295
+ const unmodified = resolution.objectParts(call, heap.at)?.every(({ parts }) => parts.every((part) => part.kind === 'unseen'));
296
+ const native = name === original && same(holder.leaves) && unmodified === true;
297
+ return native ? { native: true, reads: [call], ...defined } : { native: false, value: { kind: 'object', heap }, ...defined };
298
+ };
299
+ /** The fields of a fresh literal, the last of each key, when every part of it is seen; undefined otherwise. */
300
+ const fieldsOf = (heap) => {
301
+ const objects = resolution.objectParts(heap.node, heap.at);
302
+ if (objects?.length !== 1)
303
+ return undefined;
304
+ const fields = new Map();
305
+ for (const part of objects[0].parts) {
306
+ if (part.kind !== 'field' || part.maybe !== undefined)
307
+ return undefined;
308
+ if (typeof part.key === 'string')
309
+ fields.set(part.key, part);
310
+ }
311
+ return fields;
312
+ };
313
+ /**
314
+ * The reads of the slot's native that a fresh literal carries as `value`, when its attributes are ones a restore may
315
+ * give; undefined when it is no such literal.
316
+ */
317
+ const literalReads = (heap) => {
318
+ const fields = fieldsOf(heap);
319
+ const value = fields?.get('value');
320
+ if (fields === undefined || value === undefined || value.accessor !== undefined || value.sources.length === 0)
321
+ return undefined;
322
+ if (fields.has('get') || fields.has('set'))
323
+ return undefined;
324
+ const natives = value.sources.flatMap((each) => alternatives(each.node, each.at, new Set()));
325
+ if (natives.length === 0 || natives.some((each) => !each.native))
326
+ return undefined;
327
+ const reads = natives.flatMap((each) => (each.native ? each.reads : []));
328
+ // On an own shadow the install created, any attributes; otherwise none the analyzer decides may differ.
329
+ if (slot.shadows !== undefined)
330
+ return reads;
331
+ const catalogDescriptor = target(original).descriptor;
332
+ if (catalogDescriptor.type !== 'data')
333
+ return undefined;
334
+ const kept = [...fields].every(([key, part]) => {
335
+ if (!attributes.has(key))
336
+ return true;
337
+ if (part.accessor !== undefined || part.values.length === 0)
338
+ return true;
339
+ const truthy = part.values.map((each) => (each.kind === 'constant' && each.tested !== true ? Boolean(each.value) : undefined));
340
+ if (truthy.some((each) => each === undefined) || new Set(truthy).size !== 1)
341
+ return true;
342
+ return truthy[0] === catalogDescriptor[key];
343
+ });
344
+ return kept ? reads : undefined;
345
+ };
346
+ const found = change.kind === 'define' ? descriptors(source.node, source.at, new Set()) : alternatives(source.node, source.at, new Set());
347
+ const natives = found.flatMap((each) => (each.native ? [each.reads] : []));
348
+ if (natives.length === 0)
349
+ return undefined;
350
+ // The guards on the restore, between it and the start of its function.
351
+ const tests = [];
352
+ for (let index = ancestors.length - 1; index > 0; index--) {
353
+ const child = ancestors[index];
354
+ const parent = ancestors[index - 1];
355
+ if (functionTypes.has(child.type))
356
+ break;
357
+ if (parent.type === 'IfStatement' && parent['consequent'] === child)
358
+ tests.push(parent['test']);
359
+ else if (parent.type === 'ConditionalExpression' && parent['consequent'] === child)
360
+ tests.push(parent['test']);
361
+ else if (parent.type === 'LogicalExpression' && parent['operator'] === '&&' && parent['right'] === child) {
362
+ tests.push(parent['left']);
363
+ }
364
+ }
365
+ const conjuncts = tests.flatMap(conjunctsOf);
366
+ const value = unwrapped(source.node);
367
+ const narrowings = value.type === 'Identifier' ? conjuncts.flatMap((test) => narrowingOf(test, value, definitions)) : [];
368
+ const others = found.filter((each) => !each.native && !narrowings.some((narrowing) => removes(narrowing, each.value)));
369
+ // An identity guard compares what the slot holds now with a value that isn't its native.
370
+ const written = write.site.type === 'AssignmentExpression' ? write.site['left'] : undefined;
371
+ const atWrite = { guard: () => undefined, topLevel: false, frame };
372
+ const slotRead = (side) => (written !== undefined && squeezed(context.text, side) === squeezed(context.text, written)) ||
373
+ (resolution.evaluate(side, atWrite).length > 0 &&
374
+ resolution.evaluate(side, atWrite).every((each) => nativeReads(each) !== undefined));
375
+ const identity = conjuncts.some((test) => {
376
+ if (test.type !== 'BinaryExpression' || (test['operator'] !== '===' && test['operator'] !== '=='))
377
+ return false;
378
+ const left = test['left'];
379
+ const right = test['right'];
380
+ return (slotRead(left) && !slotRead(right)) || (slotRead(right) && !slotRead(left));
381
+ });
382
+ return {
383
+ proven: others.length === 0,
384
+ reads: natives.flat(),
385
+ guard: identity ? 'identity' : 'none',
386
+ leavesOwnShadow: slot.shadows !== undefined,
387
+ };
388
+ };
389
+ }
390
+ /** An expression past parentheses and type assertions. */
391
+ function unwrapped(node) {
392
+ let at = node;
393
+ while (at.type === 'ParenthesizedExpression' || at.type === 'TSAsExpression' || at.type === 'TSNonNullExpression') {
394
+ at = at['expression'];
395
+ }
396
+ return at;
397
+ }
398
+ /** The tests an `&&` chain makes, each of which holds when the chain does. */
399
+ function conjunctsOf(test) {
400
+ const node = unwrapped(test);
401
+ if (node.type === 'LogicalExpression' && node['operator'] === '&&') {
402
+ return [...conjunctsOf(node['left']), ...conjunctsOf(node['right'])];
403
+ }
404
+ return [node];
405
+ }
406
+ /** How a test narrows the variable `id` reads: `id`, `id != null`, `id !== undefined`, `typeof id === 'function'`. */
407
+ function narrowingOf(test, id, definitions) {
408
+ const key = definitions.keyOf(id);
409
+ const isId = (node) => {
410
+ const each = unwrapped(node);
411
+ return (each.type === 'Identifier' &&
412
+ each['name'] === id['name'] &&
413
+ definitions.keyOf(each)?.scope === key?.scope);
414
+ };
415
+ if (isId(test))
416
+ return ['truthy'];
417
+ if (test.type !== 'BinaryExpression')
418
+ return [];
419
+ const operator = test['operator'];
420
+ const left = unwrapped(test['left']);
421
+ const right = unwrapped(test['right']);
422
+ if (operator === '===' || operator === '==') {
423
+ const typeofId = (node) => node.type === 'UnaryExpression' && node['operator'] === 'typeof' && isId(node['argument']);
424
+ const fn = (node) => node.type === 'Literal' && node['value'] === 'function';
425
+ return (typeofId(left) && fn(right)) || (typeofId(right) && fn(left)) ? ['function'] : [];
426
+ }
427
+ if (operator !== '!==' && operator !== '!=')
428
+ return [];
429
+ const other = isId(left) ? right : isId(right) ? left : undefined;
430
+ if (other === undefined)
431
+ return [];
432
+ const isNull = other.type === 'Literal' && other['value'] === null && other['regex'] === undefined;
433
+ const isUndefined = (other.type === 'Identifier' && other['name'] === 'undefined') ||
434
+ (other.type === 'UnaryExpression' && other['operator'] === 'void');
435
+ if (operator === '!=')
436
+ return isNull || isUndefined ? ['non-nullish'] : [];
437
+ return isNull ? ['non-null'] : isUndefined ? ['non-undefined'] : [];
438
+ }
439
+ /** Whether a narrowing removes a value from the saved variable's alternatives. */
440
+ function removes(narrowing, value) {
441
+ switch (narrowing) {
442
+ case 'truthy':
443
+ return value.kind === 'constant' && !value.value;
444
+ case 'non-nullish':
445
+ return value.kind === 'constant' && value.value == null;
446
+ case 'non-null':
447
+ return value.kind === 'constant' && value.value === null;
448
+ case 'non-undefined':
449
+ return value.kind === 'constant' && value.value === undefined;
450
+ case 'function':
451
+ return (value.kind === 'constant' ||
452
+ (value.kind === 'object' && value.heap !== undefined && value.heap.kind !== 'function' && value.heap.kind !== 'bound'));
453
+ }
454
+ }
455
+ /** An expression's source text with no whitespace, to compare two spellings of one slot. */
456
+ function squeezed(text, node) {
457
+ return text.slice(node.start, node.end).replace(/\s+/g, '');
458
+ }
459
+ /**
460
+ * Whether two code positions are in one instantiated call chain (§3 Restores): their functions (undefined at the top
461
+ * level) are the same, or one is reached from the other along resolved calls, or, for a function no resolved call
462
+ * reaches, from where it was created.
463
+ */
464
+ export function sameChain(a, b, outward) {
465
+ const reach = (from) => {
466
+ const seen = new Set([from]);
467
+ const queue = [from];
468
+ while (queue.length > 0) {
469
+ const fn = queue.pop();
470
+ if (fn === undefined)
471
+ continue;
472
+ for (const next of outward(fn)) {
473
+ if (seen.has(next))
474
+ continue;
475
+ seen.add(next);
476
+ queue.push(next);
477
+ }
478
+ }
479
+ return seen;
480
+ };
481
+ return reach(a).has(b) || reach(b).has(a);
482
+ }
@@ -0,0 +1,6 @@
1
+ import { type AstNode } from './ast.ts';
2
+ /**
3
+ * Whether every path from the delete `site` to the end of its function, `fn`, runs one of the nodes `writes` accepts,
4
+ * in `fn`'s own code, before control can leave. `parentOf` gives each node's parent.
5
+ */
6
+ export declare function alwaysRewritten(site: AstNode, fn: AstNode, writes: (node: AstNode) => boolean, parentOf: (node: AstNode) => AstNode | undefined): boolean;