@dreamforgestudiollc/emet 0.0.0-stage → 0.2.6
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/.env.example +189 -0
- package/CHANGELOG.md +360 -0
- package/CHARTER.md +137 -0
- package/LICENSE +22 -0
- package/README.md +868 -2
- package/docs/AUTHORSHIP.md +26 -0
- package/docs/BENCHMARKS.md +261 -0
- package/docs/BOOTSTRAP.md +127 -0
- package/docs/BOTS.md +83 -0
- package/docs/COVERAGE.md +64 -0
- package/docs/DEPLOY.md +251 -0
- package/docs/DISCIPLINES.md +29 -0
- package/docs/GUARDRAILS-AND-MONORAILS.md +108 -0
- package/docs/GUIDED-SETUP.md +301 -0
- package/docs/PERMISSIONS.md +121 -0
- package/docs/ROADMAP.md +62 -0
- package/docs/SETUP.md +265 -0
- package/docs/SUBKEYS.md +59 -0
- package/gems/artifact-index.json +12 -0
- package/gems/bot-team-backup/floor.json +15 -0
- package/gems/bot-team-backup/gem.json +43 -0
- package/gems/bot-team-backup/guides/BOT_TEAM_BACKUP.md +27 -0
- package/gems/bot-team-backup/guides/BOT_TEAM_RECOVERY.md +23 -0
- package/gems/fieldnote/floor.json +14 -0
- package/gems/fieldnote/gem.json +70 -0
- package/gems/fieldnote/runes/fieldnote.js +4 -0
- package/gems/lean-six-sigma/CHANGELOG.md +10 -0
- package/gems/lean-six-sigma/README.md +92 -0
- package/gems/lean-six-sigma/floor.json +31 -0
- package/gems/lean-six-sigma/gem.json +1296 -0
- package/gems/lean-six-sigma/guides/ASSUMPTIONS.md +45 -0
- package/gems/lean-six-sigma/guides/BELTS.md +52 -0
- package/gems/lean-six-sigma/guides/DMADV.md +56 -0
- package/gems/lean-six-sigma/guides/DMAIC.md +127 -0
- package/gems/lean-six-sigma/guides/FORMULAS.md +330 -0
- package/gems/lean-six-sigma/guides/LEAN.md +97 -0
- package/gems/lean-six-sigma/guides/TEMPLATES.md +279 -0
- package/gems/lean-six-sigma/runes/lss_capability.js +132 -0
- package/gems/lean-six-sigma/runes/lss_control_limits.js +240 -0
- package/gems/lean-six-sigma/runes/lss_dpmo_sigma.js +81 -0
- package/gems/lean-six-sigma/runes/lss_hypothesis.js +298 -0
- package/gems/lean-six-sigma/runes/lss_sample_size.js +81 -0
- package/gems/neurendipity/floor.json +65 -0
- package/gems/neurendipity/gem.json +42 -0
- package/gems/neurendipity/guides/AUTISTIC_BURNOUT.md +81 -0
- package/gems/neurendipity/guides/NEURODIVERGENT_SUPPORT.md +287 -0
- package/guides/README.md +3 -0
- package/package.json +76 -4
- package/server.json +79 -0
- package/src/aleph.js +395 -0
- package/src/annotations.js +170 -0
- package/src/approve-throttle.js +54 -0
- package/src/atlas-role.js +58 -0
- package/src/attribution.js +555 -0
- package/src/conformance.js +688 -0
- package/src/content_analyzer.js +450 -0
- package/src/database.js +812 -0
- package/src/disciplines.js +26 -0
- package/src/dispatch.js +382 -0
- package/src/env.js +97 -0
- package/src/floor.js +228 -0
- package/src/gaps.js +509 -0
- package/src/gem-check.js +1359 -0
- package/src/guides.js +75 -0
- package/src/host-reach.js +23 -0
- package/src/http-remote.js +313 -0
- package/src/index.js +397 -0
- package/src/invites.js +167 -0
- package/src/layers.js +449 -0
- package/src/lessons-to-checks.js +366 -0
- package/src/logger.js +300 -0
- package/src/metadata.js +61 -0
- package/src/module-path.js +32 -0
- package/src/mongo-connect.js +91 -0
- package/src/named-items.js +208 -0
- package/src/oauth-provider.js +838 -0
- package/src/owner-approvals.js +281 -0
- package/src/persons.js +136 -0
- package/src/provenance.js +257 -0
- package/src/redact.js +237 -0
- package/src/request-log.js +124 -0
- package/src/rune-dispatch.js +360 -0
- package/src/rune-registry.js +71 -0
- package/src/rune-scope.js +42 -0
- package/src/scopes.js +100 -0
- package/src/security-headers.js +26 -0
- package/src/session-graph.js +947 -0
- package/src/session.js +1097 -0
- package/src/setup.js +1580 -0
- package/src/socket.js +545 -0
- package/src/startup-pages.js +371 -0
- package/src/startup-payload.js +129 -0
- package/src/subkey-store.js +357 -0
- package/src/subkeys.js +947 -0
- package/src/tools.js +4508 -0
- package/src/transcript-session.js +106 -0
- package/src/validation.js +1187 -0
- package/src/version.js +2 -0
- package/templates/BOARD.md +72 -0
- package/templates/BOTHANDOFF.md +57 -0
- package/templates/CHARACTER.md +30 -0
- package/templates/HANDOFF.md +63 -0
- package/templates/IDENTITY.md +29 -0
- package/templates/INDEX.md +82 -0
- package/templates/INSTALL.md +29 -0
- package/templates/PROCEDURE.md +39 -0
- package/templates/ROSTER.md +43 -0
- package/templates/SEED.md +65 -0
- package/templates/STAGING.md +37 -0
- package/templates/STANDARD.md +99 -0
- package/templates/THREAD.md +43 -0
- package/templates/USER.md +32 -0
- package/verification/load-gate/corpus.json +1765 -0
- package/verification/load-gate/load-gate.mjs +1246 -0
|
@@ -0,0 +1,1246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMET - Enduring Memory & Epistemic Tiers
|
|
3
|
+
* Copyright (c) 2026 Dreamforge Studio LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the MIT License - see LICENSE
|
|
6
|
+
*
|
|
7
|
+
* Load gate for a client RUNE. The server never runs the file. This module
|
|
8
|
+
* is the scanner EMET-GEMs vendors: keep it byte-identical, including the
|
|
9
|
+
* corpus under verification/load-gate/. acorn 8.19.0 only. A parse error
|
|
10
|
+
* fails closed. There is no second scan and no child process.
|
|
11
|
+
*
|
|
12
|
+
* Allowlist, not a denylist of tricks:
|
|
13
|
+
* - Only the AST node types this walker knows are admitted. Anything else
|
|
14
|
+
* (including a future node) is refused.
|
|
15
|
+
* - No `var`. No binding anywhere may reuse a top-level name.
|
|
16
|
+
* - `Math` is only a direct read or call of a listed member. Never `random`.
|
|
17
|
+
* The Math object is never stored, passed, returned, thrown, or spread.
|
|
18
|
+
* - A computed member key is a string/number literal or a `const` bound
|
|
19
|
+
* directly to a primitive literal, and that literal is not
|
|
20
|
+
* constructor, __proto__, or prototype. The one other form is a read of a
|
|
21
|
+
* top-level frozen null-prototype table inside an Object.hasOwn guard.
|
|
22
|
+
* The key is one `const` binding, the same binding in the guard and the
|
|
23
|
+
* read, and that binding is a primitive: `const n = String(input.n)` or
|
|
24
|
+
* `Number(...)`, or a `typeof` check for `'string'` or `'number'` before
|
|
25
|
+
* the guard. That includes `for (const k of ...)`. A member, a `let`, a
|
|
26
|
+
* parameter, a shadowed `String`/`Number`, a second const of the same
|
|
27
|
+
* name, or a const object does not count. A computed key in a binding or
|
|
28
|
+
* assignment pattern is a string or number literal only; concatenation, a
|
|
29
|
+
* member, and a const name are refused. See pass-guarded-factors.
|
|
30
|
+
* - The guard is safe for a non-own key only because the table is frozen
|
|
31
|
+
* and its prototype is null. The primitive coercion is what keeps the
|
|
32
|
+
* checked key the same as the key that is read. `RegExp` has no admitted
|
|
33
|
+
* members (`RegExp.$1` fails). `Buffer` and locale members fail.
|
|
34
|
+
* - A table is `Object.freeze` of an object literal whose own `__proto__`
|
|
35
|
+
* is null, or `Object.freeze` of an array, nested the same way, values
|
|
36
|
+
* primitive or the same pattern. No other top-level call is admitted.
|
|
37
|
+
* A table is never passed, spread, returned, or method-called.
|
|
38
|
+
* - A member write is admitted only on an object or array literal created
|
|
39
|
+
* in that function. `prototype`, `__proto__`, and `Array.prototype` are
|
|
40
|
+
* refused wherever they appear.
|
|
41
|
+
* - An import specifier is a relative path under the same GEM's `runes/`.
|
|
42
|
+
* `node:`, a bare name, a URL, `emet/rune`, and a path that normalizes
|
|
43
|
+
* outside the GEM or outside `runes/` are refused. `../` with no file
|
|
44
|
+
* list is refused. A `./` import is refused when the caller passes
|
|
45
|
+
* `files` and the target is not listed. A resolved real path that leaves
|
|
46
|
+
* `runes/` is refused. A file under `runes/` that is not `.js` or `.mjs`
|
|
47
|
+
* is refused.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
import { createRequire } from 'node:module';
|
|
51
|
+
import path from 'node:path';
|
|
52
|
+
import * as acorn from 'acorn';
|
|
53
|
+
|
|
54
|
+
const acornVersion = createRequire(import.meta.url)('acorn/package.json').version;
|
|
55
|
+
if (acornVersion !== '8.19.0') {
|
|
56
|
+
throw new Error('acorn pin: expected 8.19.0, got ' + acornVersion);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Same cap as a listed file in gem.schema.json. Larger input is not parsed. */
|
|
60
|
+
export const MAX_SOURCE_BYTES = 65536;
|
|
61
|
+
|
|
62
|
+
/** Bare identifiers. Each key is its own rule. */
|
|
63
|
+
export const HOST_GLOBALS = Object.freeze({
|
|
64
|
+
globalThis: 'global',
|
|
65
|
+
global: 'global',
|
|
66
|
+
self: 'global',
|
|
67
|
+
window: 'global',
|
|
68
|
+
process: 'env',
|
|
69
|
+
Date: 'clock',
|
|
70
|
+
performance: 'clock',
|
|
71
|
+
crypto: 'random',
|
|
72
|
+
setTimeout: 'timer',
|
|
73
|
+
setInterval: 'timer',
|
|
74
|
+
setImmediate: 'timer',
|
|
75
|
+
clearTimeout: 'timer',
|
|
76
|
+
clearInterval: 'timer',
|
|
77
|
+
clearImmediate: 'timer',
|
|
78
|
+
queueMicrotask: 'timer',
|
|
79
|
+
requestAnimationFrame: 'timer',
|
|
80
|
+
cancelAnimationFrame: 'timer',
|
|
81
|
+
Function: 'Function',
|
|
82
|
+
eval: 'eval',
|
|
83
|
+
require: 'require',
|
|
84
|
+
Reflect: 'reflect',
|
|
85
|
+
Intl: 'intl',
|
|
86
|
+
navigator: 'navigator',
|
|
87
|
+
WebAssembly: 'wasm',
|
|
88
|
+
Atomics: 'atomics',
|
|
89
|
+
Proxy: 'proxy',
|
|
90
|
+
WeakRef: 'weakref',
|
|
91
|
+
FinalizationRegistry: 'finalizer',
|
|
92
|
+
SharedArrayBuffer: 'memory',
|
|
93
|
+
Buffer: 'memory',
|
|
94
|
+
structuredClone: 'intrinsic'
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/** Members of Object that walk or rewrite the object graph. Each key is its own rule. */
|
|
98
|
+
export const OBJECT_METHODS = Object.freeze({
|
|
99
|
+
getOwnPropertyDescriptor: 'intrinsic',
|
|
100
|
+
getOwnPropertyDescriptors: 'intrinsic',
|
|
101
|
+
getPrototypeOf: 'intrinsic',
|
|
102
|
+
setPrototypeOf: 'intrinsic',
|
|
103
|
+
defineProperty: 'intrinsic',
|
|
104
|
+
defineProperties: 'intrinsic'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/** Member names refused on every object. */
|
|
108
|
+
export const MEMBER_NAMES = Object.freeze(Object.assign(Object.create(null), {
|
|
109
|
+
constructor: 'Function',
|
|
110
|
+
['__proto__']: 'prototype',
|
|
111
|
+
prototype: 'prototype'
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
/** Direct Math.x reads and calls. `random` is absent on purpose. */
|
|
115
|
+
export const MATH_MEMBERS = Object.freeze(new Set([
|
|
116
|
+
'abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh',
|
|
117
|
+
'cbrt', 'ceil', 'clz32', 'cos', 'cosh', 'exp', 'expm1', 'floor', 'fround',
|
|
118
|
+
'hypot', 'imul', 'log', 'log1p', 'log10', 'log2', 'max', 'min', 'pow',
|
|
119
|
+
'round', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc',
|
|
120
|
+
'E', 'LN10', 'LN2', 'LOG10E', 'LOG2E', 'PI', 'SQRT1_2', 'SQRT2'
|
|
121
|
+
]));
|
|
122
|
+
|
|
123
|
+
const STRUCTURAL_RULES = Object.freeze([
|
|
124
|
+
'math', 'intrinsic', 'prototype', 'constructor', 'stack', 'computed',
|
|
125
|
+
'table', 'member', 'callbind', 'var', 'shadow', 'for-in', 'module', 'new',
|
|
126
|
+
'node', 'import', 'meta', 'bytes', 'parse', 'locale', 'specifier', 'extension'
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const FORBIDDEN_SPECIFIERS = new Set([
|
|
130
|
+
'fs', 'node:fs', 'net', 'node:net', 'dgram', 'dns', 'tls', 'http', 'https',
|
|
131
|
+
'child_process', 'worker_threads', 'cluster', 'vm', 'module', 'mongodb'
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
/** A file under runes/ that is not a .js or .mjs module. Null means scan it. */
|
|
135
|
+
export function runeExtensionReason(rel) {
|
|
136
|
+
if (typeof rel !== 'string' || !rel.startsWith('runes/') || rel.endsWith('/')) return null;
|
|
137
|
+
const base = rel.slice(rel.lastIndexOf('/') + 1);
|
|
138
|
+
const dot = base.lastIndexOf('.');
|
|
139
|
+
const ext = dot <= 0 ? '' : base.slice(dot).toLowerCase();
|
|
140
|
+
if (ext === '.js' || ext === '.mjs') return null;
|
|
141
|
+
return 'rune extension'; // rune-ext
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Why this import specifier is refused, or null when it is allowed.
|
|
146
|
+
* mode 'gc16' still allows `emet/rune` so GC-19 can name that failure.
|
|
147
|
+
* `listed` is the GEM's file paths. Omit it on a source-only scan: a `./`
|
|
148
|
+
* import is not a host reach, and `../` is refused unless the list proves it.
|
|
149
|
+
* The normalized target must stay under `runes/`. `resolved` maps that
|
|
150
|
+
* normalized path to its real path relative to the GEM. A real path outside
|
|
151
|
+
* `runes/` is refused, which is how a symlink that leaves `runes/` fails.
|
|
152
|
+
*/
|
|
153
|
+
export function runeSpecifierReason(spec, fileRel, listed, mode, resolved) {
|
|
154
|
+
if (spec === 'emet/rune') {
|
|
155
|
+
if (mode === 'gc16') return null;
|
|
156
|
+
return 'emet/rune'; // import-emet
|
|
157
|
+
}
|
|
158
|
+
if (typeof spec !== 'string') return 'specifier not allowed';
|
|
159
|
+
if (spec.startsWith('node:')) return 'specifier not allowed'; // import-node
|
|
160
|
+
if (FORBIDDEN_SPECIFIERS.has(spec) || spec.includes('mongodb')) return 'specifier not allowed'; // import-forbidden
|
|
161
|
+
if (spec.includes('://')) return 'specifier not allowed'; // import-url
|
|
162
|
+
if (!spec.startsWith('./') && !spec.startsWith('../')) return 'specifier not allowed'; // import-bare
|
|
163
|
+
const hasList = Array.isArray(listed);
|
|
164
|
+
const base = typeof fileRel === 'string' && fileRel.length > 0 ? fileRel : 'runes/module.js';
|
|
165
|
+
const dir = path.posix.dirname(base);
|
|
166
|
+
const norm = path.posix.normalize(path.posix.join(dir, spec));
|
|
167
|
+
if (norm.startsWith('../') || norm === '..' || norm.startsWith('/')) return 'specifier not allowed'; // import-escape
|
|
168
|
+
if (mode !== 'gc16' && !hasList && spec.startsWith('../')) return 'specifier not allowed'; // import-dotdot
|
|
169
|
+
if (!norm.startsWith('runes/')) return 'specifier not allowed'; // import-runes
|
|
170
|
+
if (hasList && !listed.includes(norm)) return 'specifier not allowed'; // import-unlisted
|
|
171
|
+
if (resolved && typeof resolved === 'object' && Object.prototype.hasOwnProperty.call(resolved, norm)) {
|
|
172
|
+
const real = resolved[norm];
|
|
173
|
+
if (typeof real !== 'string' || !real.startsWith('runes/') || real.split('/').includes('..')) {
|
|
174
|
+
return 'specifier not allowed'; // import-symlink
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const INTRINSICS = new Set(['Object', 'Array', 'JSON', 'Number', 'String', 'Boolean', 'RegExp', 'Error', 'TypeError', 'RangeError', 'EvalError', 'ReferenceError', 'SyntaxError', 'URIError']);
|
|
181
|
+
const ERRORS = new Set(['Error', 'TypeError', 'RangeError', 'EvalError', 'ReferenceError', 'SyntaxError', 'URIError']);
|
|
182
|
+
const STACK_NAMES = new Set(['prepareStackTrace', 'stackTraceLimit', 'captureStackTrace', 'stack']);
|
|
183
|
+
const ALLOWED_MEMBERS = Object.freeze({
|
|
184
|
+
Object: new Set(['hasOwn', 'freeze', 'is']),
|
|
185
|
+
Array: new Set(['isArray']),
|
|
186
|
+
JSON: new Set(['parse', 'stringify']),
|
|
187
|
+
Number: new Set(['isFinite', 'isNaN', 'isInteger', 'isSafeInteger', 'parseFloat', 'parseInt']),
|
|
188
|
+
String: new Set(['fromCharCode', 'fromCodePoint']),
|
|
189
|
+
Boolean: new Set(),
|
|
190
|
+
RegExp: new Set(),
|
|
191
|
+
Error: new Set(),
|
|
192
|
+
TypeError: new Set(),
|
|
193
|
+
RangeError: new Set(),
|
|
194
|
+
EvalError: new Set(),
|
|
195
|
+
ReferenceError: new Set(),
|
|
196
|
+
SyntaxError: new Set(),
|
|
197
|
+
URIError: new Set()
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const PARSE_OPTIONS = Object.freeze({
|
|
201
|
+
ecmaVersion: 'latest',
|
|
202
|
+
sourceType: 'module',
|
|
203
|
+
allowHashBang: true
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const VALUE = Object.freeze({ kind: 'value' });
|
|
207
|
+
const POISON = Object.freeze({ kind: 'poison' });
|
|
208
|
+
|
|
209
|
+
export function hostRules() {
|
|
210
|
+
const rules = Object.create(null);
|
|
211
|
+
for (const name of Object.keys(HOST_GLOBALS)) rules[name] = true;
|
|
212
|
+
for (const name of Object.keys(OBJECT_METHODS)) rules[name] = true;
|
|
213
|
+
for (const name of STRUCTURAL_RULES) rules[name] = true;
|
|
214
|
+
return rules;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function normalizeOff(rules) {
|
|
218
|
+
const off = new Set();
|
|
219
|
+
if (!rules) return off;
|
|
220
|
+
if (rules.off instanceof Set) {
|
|
221
|
+
for (const id of rules.off) off.add(id);
|
|
222
|
+
return off;
|
|
223
|
+
}
|
|
224
|
+
for (const [key, value] of Object.entries(rules)) {
|
|
225
|
+
if (value === false) off.add(key);
|
|
226
|
+
}
|
|
227
|
+
return off;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Throw when the source is not an admitted RUNE. `rules` is a hostRules()
|
|
232
|
+
* object with some keys set false, or `{ off: Set }`. A false rule is the
|
|
233
|
+
* mutation hook used to prove that rule still has a witness.
|
|
234
|
+
*/
|
|
235
|
+
export function refuseHostAccess(source, rules, options) {
|
|
236
|
+
const off = normalizeOff(rules);
|
|
237
|
+
const importMode = options && options.importMode === 'gc16' ? 'gc16' : 'strict';
|
|
238
|
+
const importFrom = options && typeof options.from === 'string' ? options.from : undefined;
|
|
239
|
+
const importFiles = options && Array.isArray(options.files) ? options.files : undefined;
|
|
240
|
+
const importResolved = options && options.resolved && typeof options.resolved === 'object' && !Array.isArray(options.resolved)
|
|
241
|
+
? options.resolved
|
|
242
|
+
: undefined;
|
|
243
|
+
function fail(branch, rule, reason) {
|
|
244
|
+
if (off.has(rule)) return;
|
|
245
|
+
const err = new Error('rune reaches the host');
|
|
246
|
+
err.reason = reason;
|
|
247
|
+
err.rule = rule;
|
|
248
|
+
err.branch = branch;
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
function failMathValue() {
|
|
252
|
+
fail('math-value', 'math', 'random'); // branch:math-value
|
|
253
|
+
}
|
|
254
|
+
function failMathMember() {
|
|
255
|
+
fail('math-member', 'math', 'random'); // branch:math-member
|
|
256
|
+
}
|
|
257
|
+
function failMathWrite() {
|
|
258
|
+
fail('math-write', 'math', 'random'); // branch:math-write
|
|
259
|
+
}
|
|
260
|
+
function failIntrinsic() {
|
|
261
|
+
fail('intrinsic', 'intrinsic', 'intrinsic'); // branch:intrinsic
|
|
262
|
+
}
|
|
263
|
+
function failPrototype() {
|
|
264
|
+
fail('prototype', 'prototype', 'prototype'); // branch:prototype
|
|
265
|
+
}
|
|
266
|
+
function failConstructor() {
|
|
267
|
+
fail('constructor', 'constructor', 'Function'); // branch:constructor
|
|
268
|
+
}
|
|
269
|
+
function failStack() {
|
|
270
|
+
fail('stack', 'stack', 'intrinsic'); // branch:stack
|
|
271
|
+
}
|
|
272
|
+
function failComputed() {
|
|
273
|
+
fail('computed', 'computed', 'computed key'); // branch:computed
|
|
274
|
+
}
|
|
275
|
+
function failTable() {
|
|
276
|
+
fail('table', 'table', 'module state'); // branch:table
|
|
277
|
+
}
|
|
278
|
+
function failMember() {
|
|
279
|
+
fail('member', 'member', 'module state'); // branch:member
|
|
280
|
+
}
|
|
281
|
+
function failCallBind() {
|
|
282
|
+
fail('callbind', 'callbind', 'module state'); // branch:callbind
|
|
283
|
+
}
|
|
284
|
+
function failVar() {
|
|
285
|
+
fail('var', 'var', 'syntax'); // branch:var
|
|
286
|
+
}
|
|
287
|
+
function failShadow() {
|
|
288
|
+
fail('shadow', 'shadow', 'syntax'); // branch:shadow
|
|
289
|
+
}
|
|
290
|
+
function failForIn() {
|
|
291
|
+
fail('for-in', 'for-in', 'syntax'); // branch:for-in
|
|
292
|
+
}
|
|
293
|
+
function failModule() {
|
|
294
|
+
fail('module', 'module', 'module state'); // branch:module
|
|
295
|
+
}
|
|
296
|
+
function failNew() {
|
|
297
|
+
fail('new', 'new', 'module state'); // branch:new
|
|
298
|
+
}
|
|
299
|
+
function failNode() {
|
|
300
|
+
fail('node', 'node', 'syntax'); // branch:node
|
|
301
|
+
}
|
|
302
|
+
function failImport() {
|
|
303
|
+
fail('import', 'import', 'syntax'); // branch:import
|
|
304
|
+
}
|
|
305
|
+
function failMeta() {
|
|
306
|
+
fail('meta', 'meta', 'env'); // branch:meta
|
|
307
|
+
}
|
|
308
|
+
function failLocale() {
|
|
309
|
+
fail('locale', 'locale', 'intl'); // branch:locale
|
|
310
|
+
}
|
|
311
|
+
function failObjectMethod(name) {
|
|
312
|
+
fail('object-method', name, 'intrinsic'); // branch:object-method
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function noteImport(sourceNode) {
|
|
316
|
+
if (!sourceNode || sourceNode.type !== 'Literal' || typeof sourceNode.value !== 'string') {
|
|
317
|
+
fail('specifier', 'specifier', 'specifier not allowed');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const reason = runeSpecifierReason(sourceNode.value, importFrom, importFiles, importMode, importResolved);
|
|
321
|
+
if (reason) fail('specifier', 'specifier', reason);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (options && typeof options.path === 'string') {
|
|
325
|
+
const extReason = runeExtensionReason(options.path);
|
|
326
|
+
if (extReason) fail('extension', 'extension', extReason);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (typeof source !== 'string') source = String(source);
|
|
330
|
+
if (Buffer.byteLength(source) > MAX_SOURCE_BYTES) {
|
|
331
|
+
fail('bytes', 'bytes', 'too large'); // branch:bytes
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
let ast;
|
|
335
|
+
try {
|
|
336
|
+
ast = acorn.parse(source, PARSE_OPTIONS);
|
|
337
|
+
} catch {
|
|
338
|
+
fail('parse', 'parse', 'parse'); // branch:parse
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const root = {
|
|
343
|
+
parent: null,
|
|
344
|
+
map: new Map(),
|
|
345
|
+
fnDepth: 0,
|
|
346
|
+
guards: [],
|
|
347
|
+
prims: [],
|
|
348
|
+
topNames: new Set(),
|
|
349
|
+
imports: new Set()
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
function lookup(ctx, name) {
|
|
353
|
+
for (let s = ctx; s; s = s.parent) {
|
|
354
|
+
if (s.map.has(name)) return s.map.get(name);
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
function child(ctx, fn) {
|
|
359
|
+
return {
|
|
360
|
+
parent: ctx,
|
|
361
|
+
map: new Map(),
|
|
362
|
+
fnDepth: fn ? ctx.fnDepth + 1 : ctx.fnDepth,
|
|
363
|
+
guards: fn ? [] : ctx.guards,
|
|
364
|
+
prims: fn ? [] : ctx.prims,
|
|
365
|
+
topNames: ctx.topNames,
|
|
366
|
+
imports: ctx.imports
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function withGuards(ctx, guards) {
|
|
370
|
+
return {
|
|
371
|
+
parent: ctx.parent,
|
|
372
|
+
map: ctx.map,
|
|
373
|
+
fnDepth: ctx.fnDepth,
|
|
374
|
+
guards,
|
|
375
|
+
prims: ctx.prims,
|
|
376
|
+
topNames: ctx.topNames,
|
|
377
|
+
imports: ctx.imports
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function withPrims(ctx, prims) {
|
|
381
|
+
return {
|
|
382
|
+
parent: ctx.parent,
|
|
383
|
+
map: ctx.map,
|
|
384
|
+
fnDepth: ctx.fnDepth,
|
|
385
|
+
guards: ctx.guards,
|
|
386
|
+
prims,
|
|
387
|
+
topNames: ctx.topNames,
|
|
388
|
+
imports: ctx.imports
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function fork(ctx, guards, prims) {
|
|
392
|
+
if (guards === ctx.guards && prims === ctx.prims) return ctx;
|
|
393
|
+
return {
|
|
394
|
+
parent: ctx.parent,
|
|
395
|
+
map: ctx.map,
|
|
396
|
+
fnDepth: ctx.fnDepth,
|
|
397
|
+
guards,
|
|
398
|
+
prims,
|
|
399
|
+
topNames: ctx.topNames,
|
|
400
|
+
imports: ctx.imports
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function bind(ctx, name, info) {
|
|
404
|
+
if (ctx.fnDepth > 0 && ctx.topNames.has(name)) failShadow();
|
|
405
|
+
ctx.map.set(name, info);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function directPrim(node) {
|
|
409
|
+
if (!node) return null;
|
|
410
|
+
if (node.type === 'Literal' && !node.regex) {
|
|
411
|
+
if (node.value === null) return { value: null, vtype: 'null' };
|
|
412
|
+
if (typeof node.value === 'string' || typeof node.value === 'number' || typeof node.value === 'boolean') {
|
|
413
|
+
return { value: node.value, vtype: typeof node.value };
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
417
|
+
return { value: node.quasis.map((q) => q.value.cooked).join(''), vtype: 'string' };
|
|
418
|
+
}
|
|
419
|
+
if (node.type === 'UnaryExpression' && (node.operator === '-' || node.operator === '+') && node.argument.type === 'Literal' && typeof node.argument.value === 'number') {
|
|
420
|
+
const n = node.operator === '-' ? -node.argument.value : node.argument.value;
|
|
421
|
+
return { value: n, vtype: 'number' };
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
function evalPrim(node, ctx) {
|
|
426
|
+
const direct = directPrim(node);
|
|
427
|
+
if (direct) return direct;
|
|
428
|
+
if (node && node.type === 'Identifier' && ctx) {
|
|
429
|
+
const b = lookup(ctx, node.name);
|
|
430
|
+
if (b && b.kind === 'prim') return { value: b.value, vtype: b.vtype };
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
function primName(prim) {
|
|
435
|
+
if (!prim) return null;
|
|
436
|
+
if (prim.vtype === 'null') return 'null';
|
|
437
|
+
return String(prim.value);
|
|
438
|
+
}
|
|
439
|
+
function bannedKey(name) {
|
|
440
|
+
if (name === 'constructor') return 'constructor';
|
|
441
|
+
if (name === '__proto__' || name === 'prototype') return 'prototype';
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function isObjectGlobal(node, ctx) {
|
|
446
|
+
return !!(node && node.type === 'Identifier' && node.name === 'Object' && ctx && !lookup(ctx, node.name));
|
|
447
|
+
}
|
|
448
|
+
function isFreezeCall(node, ctx) {
|
|
449
|
+
return node && node.type === 'CallExpression' && node.arguments.length === 1
|
|
450
|
+
&& node.callee.type === 'MemberExpression' && !node.callee.computed && !node.callee.optional
|
|
451
|
+
&& isObjectGlobal(node.callee.object, ctx)
|
|
452
|
+
&& node.callee.property.type === 'Identifier' && node.callee.property.name === 'freeze';
|
|
453
|
+
}
|
|
454
|
+
function isLocaleName(name) {
|
|
455
|
+
return name === 'toLocaleString' || name === 'localeCompare' || name === 'toLocaleDateString' || name === 'toLocaleTimeString' || name === 'toLocaleUpperCase' || name === 'toLocaleLowerCase';
|
|
456
|
+
}
|
|
457
|
+
function refuseLocaleKey(name) {
|
|
458
|
+
if (isLocaleName(name)) failLocale();
|
|
459
|
+
}
|
|
460
|
+
// Direct string/number literal, including `1`, `-1`, and a template with no holes.
|
|
461
|
+
function literalKey(node) {
|
|
462
|
+
const prim = directPrim(node);
|
|
463
|
+
if (!prim || (prim.vtype !== 'string' && prim.vtype !== 'number')) return null;
|
|
464
|
+
return primName(prim);
|
|
465
|
+
}
|
|
466
|
+
// constructor / prototype / locale for every resolved key name.
|
|
467
|
+
// Returns 'poison' or 'locale' when that name is one of those, even if the
|
|
468
|
+
// rule is off, so a later check does not replace the refusal.
|
|
469
|
+
// Object-literal `__proto__` is the one exception: the value check stays with the literal.
|
|
470
|
+
function screenKey(name, role) {
|
|
471
|
+
if (name == null) return null;
|
|
472
|
+
const ban = bannedKey(name);
|
|
473
|
+
if (ban === 'constructor') { failConstructor(); return 'poison'; }
|
|
474
|
+
if (ban === 'prototype' && !(role === 'prop' && name === '__proto__')) { failPrototype(); return 'poison'; }
|
|
475
|
+
if (role !== 'prop' && isLocaleName(name)) { refuseLocaleKey(name); return 'locale'; }
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
// Every Property and MemberExpression key comes through here.
|
|
479
|
+
// role 'member' may be dynamic (a guarded table index). 'write' may be a const primitive.
|
|
480
|
+
// 'prop' and 'pattern' accept a computed key only when it is a literal.
|
|
481
|
+
function readKey(node, ctx, role) {
|
|
482
|
+
const computed = !!node.computed;
|
|
483
|
+
const keyNode = node.type === 'MemberExpression' ? node.property : node.key;
|
|
484
|
+
if (!computed) {
|
|
485
|
+
let name = null;
|
|
486
|
+
if (keyNode && keyNode.type === 'Identifier') name = keyNode.name;
|
|
487
|
+
else name = literalKey(keyNode);
|
|
488
|
+
if (role === 'pattern' && name == null) failComputed();
|
|
489
|
+
return { name, dynamic: false, stop: screenKey(name, role) };
|
|
490
|
+
}
|
|
491
|
+
expr(keyNode, ctx, 'value');
|
|
492
|
+
if (role === 'pattern' || role === 'prop') {
|
|
493
|
+
const name = literalKey(keyNode);
|
|
494
|
+
if (name == null) failComputed(); // pattern-prop-literal
|
|
495
|
+
return { name, dynamic: false, stop: name == null ? null : screenKey(name, role) };
|
|
496
|
+
}
|
|
497
|
+
const prim = evalPrim(keyNode, ctx);
|
|
498
|
+
if (prim && (prim.vtype === 'string' || prim.vtype === 'number' || prim.vtype === 'boolean' || prim.vtype === 'null')) {
|
|
499
|
+
const name = primName(prim);
|
|
500
|
+
return { name, dynamic: false, stop: screenKey(name, role) };
|
|
501
|
+
}
|
|
502
|
+
if (role === 'member') return { name: null, dynamic: true, stop: null };
|
|
503
|
+
failComputed();
|
|
504
|
+
return { name: null, dynamic: false, stop: null };
|
|
505
|
+
}
|
|
506
|
+
function calleeMethodName(member) {
|
|
507
|
+
if (!member.computed && member.property.type === 'Identifier') return member.property.name;
|
|
508
|
+
if (member.computed) return literalKey(member.property);
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
function freezeSchema(node, ctx) {
|
|
512
|
+
if (!node) return null;
|
|
513
|
+
if (node.type === 'ArrayExpression') {
|
|
514
|
+
const elements = [];
|
|
515
|
+
for (const el of node.elements) {
|
|
516
|
+
if (!el || el.type === 'SpreadElement') return null;
|
|
517
|
+
const prim = directPrim(el);
|
|
518
|
+
if (prim) { elements.push({ kind: 'prim', value: prim.value, vtype: prim.vtype }); continue; }
|
|
519
|
+
if (isFreezeCall(el, ctx)) {
|
|
520
|
+
const inner = freezeSchema(el.arguments[0], ctx);
|
|
521
|
+
if (!inner) return null;
|
|
522
|
+
elements.push(inner);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
return { kind: 'array', elements };
|
|
528
|
+
}
|
|
529
|
+
if (node.type !== 'ObjectExpression') return null;
|
|
530
|
+
let sawNull = false;
|
|
531
|
+
const props = new Map();
|
|
532
|
+
for (const prop of node.properties) {
|
|
533
|
+
if (prop.type === 'SpreadElement' || prop.type !== 'Property') return null;
|
|
534
|
+
if (prop.method || prop.kind !== 'init' || prop.shorthand) return null;
|
|
535
|
+
const name = readKey(prop, ctx, 'prop').name;
|
|
536
|
+
if (name == null) return null;
|
|
537
|
+
if (name === '__proto__') {
|
|
538
|
+
if (prop.value.type === 'Literal' && prop.value.value === null && !sawNull) { sawNull = true; continue; }
|
|
539
|
+
failPrototype();
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
const prim = directPrim(prop.value);
|
|
543
|
+
if (prim) { props.set(name, { kind: 'prim', value: prim.value, vtype: prim.vtype }); continue; }
|
|
544
|
+
if (isFreezeCall(prop.value, ctx)) {
|
|
545
|
+
const inner = freezeSchema(prop.value.arguments[0], ctx);
|
|
546
|
+
if (!inner) return null;
|
|
547
|
+
props.set(name, inner);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
if (!sawNull) return null;
|
|
553
|
+
return { kind: 'object', props };
|
|
554
|
+
}
|
|
555
|
+
function tryFreeze(node, ctx) {
|
|
556
|
+
if (!isFreezeCall(node, ctx)) return null;
|
|
557
|
+
const arg = node.arguments[0];
|
|
558
|
+
if (arg.type === 'SpreadElement') return null;
|
|
559
|
+
return freezeSchema(arg, ctx);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function sameExpr(a, b) {
|
|
563
|
+
if (!a || !b || a.type !== b.type) return false;
|
|
564
|
+
if (a.type === 'Identifier') return a.name === b.name;
|
|
565
|
+
if (a.type === 'Literal') return !a.regex && !b.regex && a.value === b.value;
|
|
566
|
+
if (a.type === 'UnaryExpression') return a.operator === b.operator && sameExpr(a.argument, b.argument);
|
|
567
|
+
if (a.type === 'MemberExpression') {
|
|
568
|
+
if (!!a.computed !== !!b.computed || !!a.optional !== !!b.optional) return false;
|
|
569
|
+
if (!sameExpr(a.object, b.object)) return false;
|
|
570
|
+
if (!a.computed) return a.property.name === b.property.name;
|
|
571
|
+
return sameExpr(a.property, b.property);
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
function isHasOwnName(name) {
|
|
576
|
+
return name === 'hasOwn';
|
|
577
|
+
}
|
|
578
|
+
function matchHasOwn(node) {
|
|
579
|
+
if (!node) return null;
|
|
580
|
+
let negated = false;
|
|
581
|
+
let call = node;
|
|
582
|
+
if (node.type === 'UnaryExpression' && node.operator === '!') {
|
|
583
|
+
negated = true;
|
|
584
|
+
call = node.argument;
|
|
585
|
+
}
|
|
586
|
+
if (!call || call.type !== 'CallExpression' || call.arguments.length !== 2) return null;
|
|
587
|
+
const callee = call.callee;
|
|
588
|
+
if (callee.type !== 'MemberExpression' || callee.computed || callee.optional) return null;
|
|
589
|
+
if (callee.object.type !== 'Identifier' || callee.object.name !== 'Object') return null;
|
|
590
|
+
if (callee.property.type !== 'Identifier' || !isHasOwnName(callee.property.name)) return null;
|
|
591
|
+
const table = call.arguments[0];
|
|
592
|
+
if (!table || table.type !== 'Identifier') return null;
|
|
593
|
+
return { table: table.name, key: call.arguments[1], negated };
|
|
594
|
+
}
|
|
595
|
+
// ident is the only admitted key. member stays off unless a logic mutant turns it on.
|
|
596
|
+
function keyMode(node) {
|
|
597
|
+
if (!node) return null;
|
|
598
|
+
if (node.type === 'Identifier') return 'ident';
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
function noteGuard(ctx, guard) {
|
|
602
|
+
if (!guard) return null;
|
|
603
|
+
const mode = keyMode(guard.key);
|
|
604
|
+
if (mode === 'member') return { table: guard.table, key: guard.key, binding: null, negated: guard.negated };
|
|
605
|
+
if (mode !== 'ident') return null;
|
|
606
|
+
const binding = lookup(ctx, guard.key.name);
|
|
607
|
+
if (!binding) return null;
|
|
608
|
+
return { table: guard.table, key: guard.key, binding, negated: guard.negated };
|
|
609
|
+
}
|
|
610
|
+
function keysMatch(ctx, guard, keyNode) {
|
|
611
|
+
if (!guard || !keyNode) return false;
|
|
612
|
+
const mode = keyMode(keyNode);
|
|
613
|
+
if (mode === 'member') return sameExpr(guard.key, keyNode);
|
|
614
|
+
if (mode !== 'ident' || guard.key.type !== 'Identifier') return false;
|
|
615
|
+
const bound = lookup(ctx, keyNode.name);
|
|
616
|
+
return !!(bound && bound.snapshot && keyIsPrim(ctx, bound) && guard.binding === bound);
|
|
617
|
+
}
|
|
618
|
+
function keyIsPrim(ctx, bound) {
|
|
619
|
+
if (!bound) return false;
|
|
620
|
+
if (bound.kind === 'prim' || bound.primKey) return true;
|
|
621
|
+
return ctx.prims.some((item) => item === bound);
|
|
622
|
+
}
|
|
623
|
+
function isPrimCoercion(node, ctx) {
|
|
624
|
+
if (!node || node.type !== 'CallExpression' || node.optional) return false;
|
|
625
|
+
if (node.arguments.length !== 1 || !node.arguments[0] || node.arguments[0].type === 'SpreadElement') return false;
|
|
626
|
+
const callee = node.callee;
|
|
627
|
+
if (callee.type !== 'Identifier') return false;
|
|
628
|
+
if (callee.name !== 'String' && callee.name !== 'Number') return false;
|
|
629
|
+
return !lookup(ctx, callee.name);
|
|
630
|
+
}
|
|
631
|
+
function typeofPair(node) {
|
|
632
|
+
if (!node || node.type !== 'BinaryExpression') return null;
|
|
633
|
+
if (node.operator !== '===' && node.operator !== '!==') return null;
|
|
634
|
+
let unary = null;
|
|
635
|
+
let lit = null;
|
|
636
|
+
if (node.left.type === 'UnaryExpression' && node.left.operator === 'typeof') {
|
|
637
|
+
unary = node.left;
|
|
638
|
+
lit = node.right;
|
|
639
|
+
} else if (node.right.type === 'UnaryExpression' && node.right.operator === 'typeof') {
|
|
640
|
+
unary = node.right;
|
|
641
|
+
lit = node.left;
|
|
642
|
+
} else return null;
|
|
643
|
+
if (!unary.argument || unary.argument.type !== 'Identifier') return null;
|
|
644
|
+
if (!lit || lit.type !== 'Literal' || (lit.value !== 'string' && lit.value !== 'number')) return null;
|
|
645
|
+
return { name: unary.argument.name, positive: node.operator === '===', type: lit.value };
|
|
646
|
+
}
|
|
647
|
+
function typeNarrow(node) {
|
|
648
|
+
const one = typeofPair(node);
|
|
649
|
+
if (one) return { name: one.name, polarity: one.positive ? 'include' : 'exclude' };
|
|
650
|
+
if (!node || node.type !== 'LogicalExpression') return null;
|
|
651
|
+
const left = typeofPair(node.left);
|
|
652
|
+
const right = typeofPair(node.right);
|
|
653
|
+
if (!left || !right || left.name !== right.name || left.type === right.type) return null;
|
|
654
|
+
if (node.operator === '||' && left.positive && right.positive) return { name: left.name, polarity: 'include' };
|
|
655
|
+
if (node.operator === '&&' && !left.positive && !right.positive) return { name: left.name, polarity: 'exclude' };
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
function guarded(ctx, tableName, keyNode) {
|
|
659
|
+
return ctx.guards.some((g) => g.table === tableName && keysMatch(ctx, g, keyNode));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function consume(schema, role) {
|
|
663
|
+
if (!schema || schema.kind === 'poison') return schema || VALUE;
|
|
664
|
+
if (role === 'member-object' || role === 'hasown-table' || role === 'callee') return schema;
|
|
665
|
+
if (schema.kind === 'math') { failMathValue(); return VALUE; }
|
|
666
|
+
if (schema.kind === 'intrinsic') { failIntrinsic(); return VALUE; }
|
|
667
|
+
if (schema.kind === 'table') { failTable(); return VALUE; }
|
|
668
|
+
return schema.kind === 'prim' ? schema : VALUE;
|
|
669
|
+
}
|
|
670
|
+
function expr(node, ctx, role) {
|
|
671
|
+
if (!node) return VALUE;
|
|
672
|
+
return consume(raw(node, ctx), role || 'value');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function propSchema(schema, name) {
|
|
676
|
+
if (!schema) return VALUE;
|
|
677
|
+
if (schema.kind === 'array') {
|
|
678
|
+
if (!/^(0|[1-9][0-9]*)$/.test(name)) return VALUE;
|
|
679
|
+
const el = schema.elements[Number(name)];
|
|
680
|
+
if (!el) return VALUE;
|
|
681
|
+
if (el.kind === 'prim') return el;
|
|
682
|
+
return { kind: 'table', schema: el, name: null };
|
|
683
|
+
}
|
|
684
|
+
if (schema.kind !== 'object') return VALUE;
|
|
685
|
+
const prop = schema.props.get(name);
|
|
686
|
+
if (!prop) return VALUE;
|
|
687
|
+
if (prop.kind === 'prim') return prop;
|
|
688
|
+
if (prop.kind === 'object' || prop.kind === 'array') return { kind: 'table', schema: prop, name: null };
|
|
689
|
+
return VALUE;
|
|
690
|
+
}
|
|
691
|
+
function unionObject(schema) {
|
|
692
|
+
const vals = [...schema.props.values()];
|
|
693
|
+
if (vals.every((v) => v.kind === 'prim')) return { kind: 'prim', value: null, vtype: 'null' };
|
|
694
|
+
if (vals.every((v) => v.kind === 'object')) {
|
|
695
|
+
const keys = new Set();
|
|
696
|
+
for (const v of vals) for (const k of v.props.keys()) keys.add(k);
|
|
697
|
+
const props = new Map();
|
|
698
|
+
for (const key of keys) {
|
|
699
|
+
const kids = vals.map((v) => v.props.get(key));
|
|
700
|
+
if (kids.length === vals.length && kids.every((k) => k && k.kind === 'prim')) {
|
|
701
|
+
props.set(key, { kind: 'prim', value: null, vtype: 'null' });
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return { kind: 'table', schema: { kind: 'object', props }, name: null };
|
|
705
|
+
}
|
|
706
|
+
return { kind: 'table', schema: { kind: 'object', props: new Map() }, name: null };
|
|
707
|
+
}
|
|
708
|
+
function dynamicTable(schema) {
|
|
709
|
+
if (schema && schema.kind === 'object') return unionObject(schema);
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function resolveMember(member, ctx) {
|
|
714
|
+
const obj = expr(member.object, ctx, 'member-object');
|
|
715
|
+
if (obj.kind === 'poison') return { obj, schema: POISON };
|
|
716
|
+
const key = readKey(member, ctx, 'member');
|
|
717
|
+
const keyName = key.name;
|
|
718
|
+
const dynamic = key.dynamic;
|
|
719
|
+
if (key.stop === 'poison') return { obj, schema: POISON };
|
|
720
|
+
if (key.stop === 'locale') return { obj, schema: VALUE };
|
|
721
|
+
if (keyName && Object.hasOwn(OBJECT_METHODS, keyName)) {
|
|
722
|
+
failObjectMethod(keyName);
|
|
723
|
+
return { obj, schema: VALUE };
|
|
724
|
+
}
|
|
725
|
+
if (keyName && STACK_NAMES.has(keyName)) { failStack(); return { obj, schema: VALUE }; }
|
|
726
|
+
if (obj.kind === 'math') {
|
|
727
|
+
if (!keyName || !MATH_MEMBERS.has(keyName)) failMathMember();
|
|
728
|
+
return { obj, schema: VALUE };
|
|
729
|
+
}
|
|
730
|
+
if (obj.kind === 'intrinsic') {
|
|
731
|
+
const allowed = ALLOWED_MEMBERS[obj.name] || new Set();
|
|
732
|
+
if (!keyName || !allowed.has(keyName)) failIntrinsic();
|
|
733
|
+
return { obj, schema: { kind: 'intrinsic-member', name: obj.name, member: keyName } };
|
|
734
|
+
}
|
|
735
|
+
if (obj.kind === 'table') {
|
|
736
|
+
if (keyName != null && !dynamic) return { obj, schema: propSchema(obj.schema, keyName) };
|
|
737
|
+
if (dynamic && obj.name && guarded(ctx, obj.name, member.property)) {
|
|
738
|
+
const next = dynamicTable(obj.schema);
|
|
739
|
+
if (next) return { obj, schema: next };
|
|
740
|
+
}
|
|
741
|
+
failComputed();
|
|
742
|
+
return { obj, schema: VALUE };
|
|
743
|
+
}
|
|
744
|
+
if (dynamic) { failComputed(); return { obj, schema: VALUE }; }
|
|
745
|
+
if (obj.kind === 'local') return { obj, schema: { kind: 'local' } };
|
|
746
|
+
return { obj, schema: VALUE };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function checkMemberWrite(member, ctx) {
|
|
750
|
+
const key = readKey(member, ctx, 'write');
|
|
751
|
+
if (key.stop) return;
|
|
752
|
+
const keyName = key.name;
|
|
753
|
+
if (keyName && Object.hasOwn(OBJECT_METHODS, keyName)) {
|
|
754
|
+
failObjectMethod(keyName);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (keyName && STACK_NAMES.has(keyName)) { failStack(); return; }
|
|
758
|
+
const obj = expr(member.object, ctx, 'member-object');
|
|
759
|
+
if (!obj || obj.kind === 'poison') return;
|
|
760
|
+
if (obj.kind === 'math') { failMathWrite(); return; }
|
|
761
|
+
if (obj.kind === 'intrinsic' || obj.kind === 'intrinsic-member') { failIntrinsic(); return; }
|
|
762
|
+
if (obj.kind === 'table') { failTable(); return; }
|
|
763
|
+
if (obj.kind !== 'local') failMember();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function visitArgs(args, ctx, firstRole) {
|
|
767
|
+
for (let i = 0; i < args.length; i++) {
|
|
768
|
+
const arg = args[i];
|
|
769
|
+
if (!arg) continue;
|
|
770
|
+
if (arg.type === 'SpreadElement') expr(arg.argument, ctx, 'value');
|
|
771
|
+
else expr(arg, ctx, i === 0 && firstRole ? firstRole : 'value');
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function rawIdent(node, ctx) {
|
|
776
|
+
const bound = lookup(ctx, node.name);
|
|
777
|
+
if (bound) {
|
|
778
|
+
if (bound.kind === 'table') return { kind: 'table', schema: bound.schema, name: node.name };
|
|
779
|
+
if (bound.kind === 'prim') return { kind: 'prim', value: bound.value, vtype: bound.vtype };
|
|
780
|
+
if (bound.kind === 'local') return { kind: 'local' };
|
|
781
|
+
return VALUE;
|
|
782
|
+
}
|
|
783
|
+
if (HOST_GLOBALS[node.name]) {
|
|
784
|
+
fail('global', node.name, HOST_GLOBALS[node.name]); // branch:global
|
|
785
|
+
return VALUE;
|
|
786
|
+
}
|
|
787
|
+
if (node.name === 'Math') return { kind: 'math' };
|
|
788
|
+
if (INTRINSICS.has(node.name)) return { kind: 'intrinsic', name: node.name };
|
|
789
|
+
return VALUE;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function raw(node, ctx) {
|
|
793
|
+
if (!node || typeof node.type !== 'string') { failNode(); return VALUE; }
|
|
794
|
+
switch (node.type) {
|
|
795
|
+
case 'Identifier':
|
|
796
|
+
return rawIdent(node, ctx);
|
|
797
|
+
case 'Literal':
|
|
798
|
+
return directPrim(node) || VALUE;
|
|
799
|
+
case 'TemplateLiteral':
|
|
800
|
+
for (const e of node.expressions) expr(e, ctx, 'value');
|
|
801
|
+
return directPrim(node) || VALUE;
|
|
802
|
+
case 'UnaryExpression':
|
|
803
|
+
if (node.operator === 'delete' && node.argument.type === 'MemberExpression') checkMemberWrite(node.argument, ctx); // delete-member
|
|
804
|
+
else expr(node.argument, ctx, 'value');
|
|
805
|
+
return VALUE;
|
|
806
|
+
case 'UpdateExpression':
|
|
807
|
+
if (node.argument.type === 'MemberExpression') checkMemberWrite(node.argument, ctx);
|
|
808
|
+
else expr(node.argument, ctx, 'value');
|
|
809
|
+
return VALUE;
|
|
810
|
+
case 'BinaryExpression':
|
|
811
|
+
expr(node.left, ctx, 'value');
|
|
812
|
+
expr(node.right, ctx, 'value');
|
|
813
|
+
return VALUE;
|
|
814
|
+
case 'LogicalExpression': {
|
|
815
|
+
const guard = noteGuard(ctx, matchHasOwn(node.left));
|
|
816
|
+
expr(node.left, ctx, 'value');
|
|
817
|
+
let rightCtx = ctx;
|
|
818
|
+
if (guard && ((node.operator === '&&' && !guard.negated) || (node.operator === '||' && guard.negated))) {
|
|
819
|
+
rightCtx = withGuards(ctx, ctx.guards.concat(guard));
|
|
820
|
+
}
|
|
821
|
+
expr(node.right, rightCtx, 'value');
|
|
822
|
+
return VALUE;
|
|
823
|
+
}
|
|
824
|
+
case 'AssignmentExpression':
|
|
825
|
+
assignTarget(node.left, ctx);
|
|
826
|
+
expr(node.right, ctx, 'value');
|
|
827
|
+
return VALUE;
|
|
828
|
+
case 'ConditionalExpression': {
|
|
829
|
+
const guard = noteGuard(ctx, matchHasOwn(node.test));
|
|
830
|
+
expr(node.test, ctx, 'value');
|
|
831
|
+
const cons = guard && !guard.negated ? withGuards(ctx, ctx.guards.concat(guard)) : ctx; // ternary-cons
|
|
832
|
+
const alt = guard && guard.negated ? withGuards(ctx, ctx.guards.concat(guard)) : ctx; // ternary-alt
|
|
833
|
+
expr(node.consequent, cons, 'value');
|
|
834
|
+
expr(node.alternate, alt, 'value');
|
|
835
|
+
return VALUE;
|
|
836
|
+
}
|
|
837
|
+
case 'SequenceExpression':
|
|
838
|
+
for (const e of node.expressions) expr(e, ctx, 'value');
|
|
839
|
+
return VALUE;
|
|
840
|
+
case 'ArrayExpression':
|
|
841
|
+
for (const el of node.elements) if (el) expr(el.type === 'SpreadElement' ? el.argument : el, ctx, 'value');
|
|
842
|
+
return { kind: 'local' };
|
|
843
|
+
case 'ObjectExpression':
|
|
844
|
+
for (const prop of node.properties) {
|
|
845
|
+
if (prop.type === 'SpreadElement') { expr(prop.argument, ctx, 'value'); continue; }
|
|
846
|
+
if (prop.type !== 'Property') { failNode(); continue; }
|
|
847
|
+
const name = readKey(prop, ctx, 'prop').name;
|
|
848
|
+
if (name === '__proto__') {
|
|
849
|
+
const ok = prop.value.type === 'Literal' && prop.value.value === null;
|
|
850
|
+
if (!ok) failPrototype();
|
|
851
|
+
}
|
|
852
|
+
if (prop.kind === 'init' && !prop.method) expr(prop.value, ctx, 'value');
|
|
853
|
+
else visitFunction(prop.value, ctx);
|
|
854
|
+
}
|
|
855
|
+
return { kind: 'local' };
|
|
856
|
+
case 'MemberExpression':
|
|
857
|
+
return resolveMember(node, ctx).schema;
|
|
858
|
+
case 'ChainExpression':
|
|
859
|
+
return raw(node.expression, ctx);
|
|
860
|
+
case 'CallExpression':
|
|
861
|
+
return rawCall(node, ctx);
|
|
862
|
+
case 'NewExpression':
|
|
863
|
+
return rawNew(node, ctx);
|
|
864
|
+
case 'FunctionExpression':
|
|
865
|
+
case 'ArrowFunctionExpression':
|
|
866
|
+
visitFunction(node, ctx);
|
|
867
|
+
return VALUE;
|
|
868
|
+
case 'YieldExpression':
|
|
869
|
+
if (node.argument) expr(node.argument, ctx, 'value');
|
|
870
|
+
return VALUE;
|
|
871
|
+
case 'AwaitExpression':
|
|
872
|
+
expr(node.argument, ctx, 'value');
|
|
873
|
+
return VALUE;
|
|
874
|
+
case 'ThisExpression':
|
|
875
|
+
return VALUE;
|
|
876
|
+
case 'MetaProperty':
|
|
877
|
+
failMeta();
|
|
878
|
+
return VALUE;
|
|
879
|
+
case 'ImportExpression':
|
|
880
|
+
failImport();
|
|
881
|
+
if (node.source) expr(node.source, ctx, 'value');
|
|
882
|
+
return VALUE;
|
|
883
|
+
case 'TaggedTemplateExpression':
|
|
884
|
+
failNode();
|
|
885
|
+
return VALUE;
|
|
886
|
+
default:
|
|
887
|
+
failNode();
|
|
888
|
+
return VALUE;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function rawCall(node, ctx) {
|
|
893
|
+
const callee = node.callee;
|
|
894
|
+
if (callee.type === 'MemberExpression') {
|
|
895
|
+
const keyName = calleeMethodName(callee);
|
|
896
|
+
if (keyName === 'call' || keyName === 'apply' || keyName === 'bind') failCallBind();
|
|
897
|
+
const resolved = resolveMember(callee, ctx);
|
|
898
|
+
if (resolved.schema && resolved.schema.kind === 'intrinsic-member' && resolved.schema.name === 'Object' && isHasOwnName(resolved.schema.member)) {
|
|
899
|
+
visitArgs(node.arguments, ctx, 'hasown-table');
|
|
900
|
+
return VALUE;
|
|
901
|
+
}
|
|
902
|
+
if (resolved.obj && resolved.obj.kind === 'table') failTable();
|
|
903
|
+
visitArgs(node.arguments, ctx, null);
|
|
904
|
+
return VALUE;
|
|
905
|
+
}
|
|
906
|
+
const schema = expr(callee, ctx, 'callee');
|
|
907
|
+
if (schema.kind === 'math') failMathValue();
|
|
908
|
+
if (schema.kind === 'intrinsic' && !ERRORS.has(schema.name)) {
|
|
909
|
+
const asCall = schema.name === 'Array' || schema.name === 'Object' || schema.name === 'Number' || schema.name === 'String' || schema.name === 'Boolean';
|
|
910
|
+
if (!asCall) failIntrinsic();
|
|
911
|
+
}
|
|
912
|
+
if (schema.kind === 'table') failTable();
|
|
913
|
+
visitArgs(node.arguments, ctx, null);
|
|
914
|
+
return VALUE;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function rawNew(node, ctx) {
|
|
918
|
+
visitArgs(node.arguments, ctx, null);
|
|
919
|
+
const callee = node.callee;
|
|
920
|
+
if (callee.type === 'Identifier' && ERRORS.has(callee.name) && !lookup(ctx, callee.name)) return VALUE; // new-intrinsic
|
|
921
|
+
expr(callee, ctx, 'callee');
|
|
922
|
+
failNew();
|
|
923
|
+
return VALUE;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function assignTarget(node, ctx) {
|
|
927
|
+
if (!node) return;
|
|
928
|
+
if (node.type === 'Identifier') return;
|
|
929
|
+
if (node.type === 'MemberExpression') { checkMemberWrite(node, ctx); return; }
|
|
930
|
+
if (node.type === 'AssignmentPattern') {
|
|
931
|
+
assignTarget(node.left, ctx);
|
|
932
|
+
expr(node.right, ctx, 'value');
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (node.type === 'RestElement') { assignTarget(node.argument, ctx); return; }
|
|
936
|
+
if (node.type === 'ObjectPattern') {
|
|
937
|
+
for (const prop of node.properties) {
|
|
938
|
+
if (prop.type === 'RestElement') { assignTarget(prop.argument, ctx); continue; }
|
|
939
|
+
if (prop.type !== 'Property') { failNode(); continue; }
|
|
940
|
+
readKey(prop, ctx, 'pattern');
|
|
941
|
+
assignTarget(prop.value, ctx);
|
|
942
|
+
}
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (node.type === 'ArrayPattern') {
|
|
946
|
+
for (const el of node.elements) if (el) assignTarget(el, ctx);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
failNode();
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function bindPattern(node, ctx, kind) {
|
|
953
|
+
if (!node) return;
|
|
954
|
+
if (node.type === 'Identifier') { bind(ctx, node.name, { kind: 'value', snapshot: kind === 'const' }); return; }
|
|
955
|
+
if (node.type === 'AssignmentPattern') {
|
|
956
|
+
if (node.right) expr(node.right, ctx, 'value');
|
|
957
|
+
bindPattern(node.left, ctx, kind);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (node.type === 'RestElement') { bindPattern(node.argument, ctx, kind); return; }
|
|
961
|
+
if (node.type === 'ObjectPattern') {
|
|
962
|
+
for (const prop of node.properties) {
|
|
963
|
+
if (prop.type === 'RestElement') { bindPattern(prop.argument, ctx, kind); continue; }
|
|
964
|
+
if (prop.type !== 'Property') { failNode(); continue; }
|
|
965
|
+
readKey(prop, ctx, 'pattern');
|
|
966
|
+
bindPattern(prop.value, ctx, kind);
|
|
967
|
+
}
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
if (node.type === 'ArrayPattern') {
|
|
971
|
+
for (const el of node.elements) if (el) bindPattern(el, ctx, kind);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
failNode();
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function isFn(node) {
|
|
978
|
+
return node && (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression');
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function bindTop(name, init, ctx) {
|
|
982
|
+
if (!init) { failModule(); bind(ctx, name, { kind: 'value' }); return; }
|
|
983
|
+
if (isFn(init)) {
|
|
984
|
+
if (init.generator) failNode();
|
|
985
|
+
visitFunction(init, ctx);
|
|
986
|
+
bind(ctx, name, { kind: 'function' });
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const prim = directPrim(init);
|
|
990
|
+
if (prim) { bind(ctx, name, { kind: 'prim', value: prim.value, vtype: prim.vtype }); return; }
|
|
991
|
+
const frozen = tryFreeze(init, ctx);
|
|
992
|
+
if (frozen) { bind(ctx, name, { kind: 'table', schema: frozen }); return; }
|
|
993
|
+
if (init.type === 'Identifier' && ctx.imports.has(init.name)) {
|
|
994
|
+
bind(ctx, name, { kind: 'value' });
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
expr(init, ctx, 'value');
|
|
998
|
+
failModule();
|
|
999
|
+
bind(ctx, name, { kind: 'value' });
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function visitVar(node, ctx, top) {
|
|
1003
|
+
if (node.kind === 'var') failVar();
|
|
1004
|
+
if (node.kind !== 'const' && node.kind !== 'let' && node.kind !== 'var') failNode();
|
|
1005
|
+
if (top && node.kind !== 'const') failModule();
|
|
1006
|
+
for (const decl of node.declarations) {
|
|
1007
|
+
if (decl.id.type !== 'Identifier') {
|
|
1008
|
+
if (decl.init) expr(decl.init, ctx, 'value');
|
|
1009
|
+
bindPattern(decl.id, ctx, node.kind);
|
|
1010
|
+
if (top) failModule();
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
if (top) { bindTop(decl.id.name, decl.init, ctx); continue; }
|
|
1014
|
+
let info = { kind: 'value' };
|
|
1015
|
+
if (decl.init && node.kind === 'const') {
|
|
1016
|
+
const prim = directPrim(decl.init);
|
|
1017
|
+
if (prim) info = { kind: 'prim', value: prim.value, vtype: prim.vtype };
|
|
1018
|
+
}
|
|
1019
|
+
if (decl.init && (decl.init.type === 'ObjectExpression' || decl.init.type === 'ArrayExpression')) {
|
|
1020
|
+
info = { kind: 'local' };
|
|
1021
|
+
}
|
|
1022
|
+
if (node.kind === 'const') {
|
|
1023
|
+
info.snapshot = true;
|
|
1024
|
+
if (info.kind === 'prim' || isPrimCoercion(decl.init, ctx)) info.primKey = true;
|
|
1025
|
+
}
|
|
1026
|
+
if (decl.init && info.kind !== 'prim') expr(decl.init, ctx, 'value');
|
|
1027
|
+
bind(ctx, decl.id.name, info);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function visitFunction(node, ctx) {
|
|
1032
|
+
if (!node) return;
|
|
1033
|
+
if (node.generator) failNode();
|
|
1034
|
+
const scope = child(ctx, true);
|
|
1035
|
+
for (const param of node.params || []) bindPattern(param, scope, 'param');
|
|
1036
|
+
if (node.body && node.body.type === 'BlockStatement') visitBlock(node.body, scope, true);
|
|
1037
|
+
else if (node.body) expr(node.body, scope, 'value');
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function hoist(body, scope) {
|
|
1041
|
+
for (const stmt of body) {
|
|
1042
|
+
if (stmt.type === 'FunctionDeclaration' && stmt.id) bind(scope, stmt.id.name, { kind: 'function' });
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function abrupt(stmt) {
|
|
1047
|
+
if (!stmt) return false;
|
|
1048
|
+
if (stmt.type === 'ReturnStatement' || stmt.type === 'ThrowStatement' || stmt.type === 'BreakStatement' || stmt.type === 'ContinueStatement') return true;
|
|
1049
|
+
if (stmt.type === 'BlockStatement' && stmt.body.length === 1) return abrupt(stmt.body[0]);
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
function earlyGuard(stmt) {
|
|
1053
|
+
if (stmt.type !== 'IfStatement' || stmt.alternate) return null;
|
|
1054
|
+
const guard = matchHasOwn(stmt.test);
|
|
1055
|
+
if (!guard || !guard.negated || !abrupt(stmt.consequent)) return null;
|
|
1056
|
+
return guard;
|
|
1057
|
+
}
|
|
1058
|
+
function earlyPrim(stmt, ctx) {
|
|
1059
|
+
if (!stmt || stmt.type !== 'IfStatement' || stmt.alternate) return null;
|
|
1060
|
+
const info = typeNarrow(stmt.test);
|
|
1061
|
+
if (!info || info.polarity !== 'exclude' || !abrupt(stmt.consequent)) return null;
|
|
1062
|
+
const binding = lookup(ctx, info.name);
|
|
1063
|
+
if (!binding || !binding.snapshot) return null;
|
|
1064
|
+
return binding;
|
|
1065
|
+
}
|
|
1066
|
+
function visitSeq(body, scope) {
|
|
1067
|
+
let guards = scope.guards;
|
|
1068
|
+
let prims = scope.prims;
|
|
1069
|
+
for (const stmt of body) {
|
|
1070
|
+
const here = fork(scope, guards, prims);
|
|
1071
|
+
const extra = noteGuard(here, earlyGuard(stmt));
|
|
1072
|
+
const prim = earlyPrim(stmt, here);
|
|
1073
|
+
visitStmt(stmt, here);
|
|
1074
|
+
if (extra) guards = guards.concat(extra);
|
|
1075
|
+
if (prim) prims = prims.concat(prim);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function visitBlock(block, ctx, same) {
|
|
1080
|
+
const scope = same ? ctx : child(ctx, false);
|
|
1081
|
+
hoist(block.body, scope);
|
|
1082
|
+
visitSeq(block.body, scope);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function visitStmt(stmt, ctx) {
|
|
1086
|
+
switch (stmt.type) {
|
|
1087
|
+
case 'EmptyStatement':
|
|
1088
|
+
case 'DebuggerStatement':
|
|
1089
|
+
return;
|
|
1090
|
+
case 'BlockStatement':
|
|
1091
|
+
visitBlock(stmt, ctx, false);
|
|
1092
|
+
return;
|
|
1093
|
+
case 'ExpressionStatement':
|
|
1094
|
+
expr(stmt.expression, ctx, 'value');
|
|
1095
|
+
return;
|
|
1096
|
+
case 'ReturnStatement':
|
|
1097
|
+
if (stmt.argument) expr(stmt.argument, ctx, 'value');
|
|
1098
|
+
return;
|
|
1099
|
+
case 'ThrowStatement':
|
|
1100
|
+
expr(stmt.argument, ctx, 'value');
|
|
1101
|
+
return;
|
|
1102
|
+
case 'LabeledStatement':
|
|
1103
|
+
visitStmt(stmt.body, ctx);
|
|
1104
|
+
return;
|
|
1105
|
+
case 'BreakStatement':
|
|
1106
|
+
case 'ContinueStatement':
|
|
1107
|
+
return;
|
|
1108
|
+
case 'IfStatement': {
|
|
1109
|
+
const guard = noteGuard(ctx, matchHasOwn(stmt.test));
|
|
1110
|
+
const narrow = typeNarrow(stmt.test);
|
|
1111
|
+
expr(stmt.test, ctx, 'value');
|
|
1112
|
+
const cons0 = guard && !guard.negated ? withGuards(ctx, ctx.guards.concat(guard)) : ctx; // if-cons
|
|
1113
|
+
const alt0 = guard && guard.negated ? withGuards(ctx, ctx.guards.concat(guard)) : ctx; // if-alt
|
|
1114
|
+
const binding = narrow && lookup(ctx, narrow.name);
|
|
1115
|
+
const primed = binding && binding.snapshot ? binding : null;
|
|
1116
|
+
const cons = primed && narrow.polarity === 'include' ? withPrims(cons0, cons0.prims.concat(primed)) : cons0;
|
|
1117
|
+
const alt = primed && narrow.polarity === 'exclude' ? withPrims(alt0, alt0.prims.concat(primed)) : alt0;
|
|
1118
|
+
visitStmt(stmt.consequent, cons);
|
|
1119
|
+
if (stmt.alternate) visitStmt(stmt.alternate, alt);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
case 'SwitchStatement':
|
|
1123
|
+
expr(stmt.discriminant, ctx, 'value');
|
|
1124
|
+
for (const c of stmt.cases) {
|
|
1125
|
+
if (c.test) expr(c.test, ctx, 'value');
|
|
1126
|
+
const scope = child(ctx, false);
|
|
1127
|
+
hoist(c.consequent, scope);
|
|
1128
|
+
visitSeq(c.consequent, scope);
|
|
1129
|
+
}
|
|
1130
|
+
return;
|
|
1131
|
+
case 'WhileStatement':
|
|
1132
|
+
case 'DoWhileStatement':
|
|
1133
|
+
expr(stmt.test, ctx, 'value');
|
|
1134
|
+
visitStmt(stmt.body, ctx);
|
|
1135
|
+
return;
|
|
1136
|
+
case 'ForStatement': {
|
|
1137
|
+
const scope = child(ctx, false);
|
|
1138
|
+
if (stmt.init) {
|
|
1139
|
+
if (stmt.init.type === 'VariableDeclaration') visitVar(stmt.init, scope, false);
|
|
1140
|
+
else expr(stmt.init, scope, 'value');
|
|
1141
|
+
}
|
|
1142
|
+
if (stmt.test) expr(stmt.test, scope, 'value');
|
|
1143
|
+
if (stmt.update) expr(stmt.update, scope, 'value');
|
|
1144
|
+
visitStmt(stmt.body, scope);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
case 'ForOfStatement':
|
|
1148
|
+
if (stmt.await) failNode();
|
|
1149
|
+
{
|
|
1150
|
+
const scope = child(ctx, false);
|
|
1151
|
+
if (stmt.left.type === 'VariableDeclaration') visitVar(stmt.left, scope, false);
|
|
1152
|
+
else expr(stmt.left, scope, 'value');
|
|
1153
|
+
expr(stmt.right, ctx, 'value');
|
|
1154
|
+
visitStmt(stmt.body, scope);
|
|
1155
|
+
}
|
|
1156
|
+
return;
|
|
1157
|
+
case 'ForInStatement':
|
|
1158
|
+
if (stmt.left.type === 'VariableDeclaration' && stmt.left.kind === 'var') failVar();
|
|
1159
|
+
failForIn();
|
|
1160
|
+
return;
|
|
1161
|
+
case 'TryStatement':
|
|
1162
|
+
visitBlock(stmt.block, ctx, false);
|
|
1163
|
+
if (stmt.handler) {
|
|
1164
|
+
const scope = child(ctx, false);
|
|
1165
|
+
if (stmt.handler.param) bindPattern(stmt.handler.param, scope, 'catch');
|
|
1166
|
+
visitBlock(stmt.handler.body, scope, true);
|
|
1167
|
+
}
|
|
1168
|
+
if (stmt.finalizer) visitBlock(stmt.finalizer, ctx, false);
|
|
1169
|
+
return;
|
|
1170
|
+
case 'VariableDeclaration':
|
|
1171
|
+
visitVar(stmt, ctx, ctx.fnDepth === 0);
|
|
1172
|
+
return;
|
|
1173
|
+
case 'FunctionDeclaration':
|
|
1174
|
+
if (stmt.generator) failNode();
|
|
1175
|
+
if (stmt.id && ctx.fnDepth === 0) bind(ctx, stmt.id.name, { kind: 'function' });
|
|
1176
|
+
visitFunction(stmt, ctx);
|
|
1177
|
+
return;
|
|
1178
|
+
case 'ImportDeclaration':
|
|
1179
|
+
noteImport(stmt.source);
|
|
1180
|
+
for (const spec of stmt.specifiers) {
|
|
1181
|
+
ctx.topNames.add(spec.local.name);
|
|
1182
|
+
ctx.imports.add(spec.local.name);
|
|
1183
|
+
bind(ctx, spec.local.name, { kind: 'import' });
|
|
1184
|
+
}
|
|
1185
|
+
return;
|
|
1186
|
+
case 'ExportNamedDeclaration':
|
|
1187
|
+
if (stmt.source) noteImport(stmt.source);
|
|
1188
|
+
if (stmt.declaration) visitStmt(stmt.declaration, ctx);
|
|
1189
|
+
return;
|
|
1190
|
+
case 'ExportAllDeclaration':
|
|
1191
|
+
noteImport(stmt.source);
|
|
1192
|
+
failNode();
|
|
1193
|
+
return;
|
|
1194
|
+
case 'ExportDefaultDeclaration':
|
|
1195
|
+
if (stmt.declaration.type === 'FunctionDeclaration') {
|
|
1196
|
+
if (stmt.declaration.id) {
|
|
1197
|
+
ctx.topNames.add(stmt.declaration.id.name);
|
|
1198
|
+
bind(ctx, stmt.declaration.id.name, { kind: 'function' });
|
|
1199
|
+
}
|
|
1200
|
+
visitFunction(stmt.declaration, ctx);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (isFn(stmt.declaration)) { visitFunction(stmt.declaration, ctx); return; }
|
|
1204
|
+
expr(stmt.declaration, ctx, 'value');
|
|
1205
|
+
return;
|
|
1206
|
+
default:
|
|
1207
|
+
failNode();
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function addTop(stmt, ctx) {
|
|
1212
|
+
if (!stmt) return;
|
|
1213
|
+
if (stmt.type === 'FunctionDeclaration' && stmt.id) ctx.topNames.add(stmt.id.name);
|
|
1214
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
1215
|
+
for (const decl of stmt.declarations) addPatternNames(decl.id, ctx);
|
|
1216
|
+
}
|
|
1217
|
+
if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration) addTop(stmt.declaration, ctx);
|
|
1218
|
+
if (stmt.type === 'ExportDefaultDeclaration' && stmt.declaration.type === 'FunctionDeclaration' && stmt.declaration.id) {
|
|
1219
|
+
ctx.topNames.add(stmt.declaration.id.name);
|
|
1220
|
+
}
|
|
1221
|
+
if (stmt.type === 'ImportDeclaration') {
|
|
1222
|
+
for (const spec of stmt.specifiers) ctx.topNames.add(spec.local.name);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
function addPatternNames(node, ctx) {
|
|
1226
|
+
if (!node) return;
|
|
1227
|
+
if (node.type === 'Identifier') ctx.topNames.add(node.name);
|
|
1228
|
+
else if (node.type === 'AssignmentPattern') addPatternNames(node.left, ctx);
|
|
1229
|
+
else if (node.type === 'RestElement') addPatternNames(node.argument, ctx);
|
|
1230
|
+
else if (node.type === 'ObjectPattern') for (const p of node.properties) addPatternNames(p.type === 'RestElement' ? p.argument : p.value, ctx);
|
|
1231
|
+
else if (node.type === 'ArrayPattern') for (const el of node.elements) addPatternNames(el, ctx);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
for (const stmt of ast.body) addTop(stmt, root);
|
|
1235
|
+
for (const stmt of ast.body) visitStmt(stmt, root);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/** Same scan, returning a verdict instead of throwing. GEMs' caller can use either. */
|
|
1239
|
+
export function scanSource(source, rules, options) {
|
|
1240
|
+
try {
|
|
1241
|
+
refuseHostAccess(source, rules, options);
|
|
1242
|
+
return { ok: true, rules: [] };
|
|
1243
|
+
} catch (e) {
|
|
1244
|
+
return { ok: false, reason: e.reason || 'parse', rule: e.rule || null, rules: e.rule ? [e.rule] : [] };
|
|
1245
|
+
}
|
|
1246
|
+
}
|